All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.rpm.BuildRpmMojo Maven / Gradle / Ivy

The newest version!
package org.codehaus.mojo.rpm;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.mojo.tools.cli.CommandLineManager;
import org.codehaus.mojo.tools.rpm.RpmFormattingException;
import org.codehaus.mojo.tools.rpm.RpmInfoFormatter;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import org.codehaus.plexus.util.cli.shell.BourneShell;
import org.codehaus.plexus.util.cli.shell.Shell;

/**
 * Harvest a RPM from the project binaries and the generated spec file.
 * 
 * @goal build
 * @phase package
 */
public class BuildRpmMojo
    extends AbstractMojo
{

    private static final String USER_HOME = System.getProperty( "user.home" );
    private static final String LINE_SEPARATOR = System.getProperty( "line.separator" );

    /**
     * This list was taken from the RPM(8) manpage for rpm version 4.4.1
     */
    public static final String[] DEFAULT_RPMRC_INCLUDES = {
        "/usr/lib/rpm/rpmrc",
        "/usr/lib/rpm/redhat/rpmrc",
        "/etc/rpmrc",
        USER_HOME + "/.rpmrc"
    };

    public static final String MACROFILES_HEADER = "macrofiles";

    /**
     * This is a list of locations to include in the generated rpmrc file that contains the ref to
     * the macro definitions that specify the topdir to be used here.
     * 
     * @parameter
     * @since 1.0-alpha-1
     */
    private List < String > rpmrcIncludes;

    /**
     * @parameter default-value="false" alias="rpm.build.skip"
     * @since 1.0-alpha-1
     */
    private boolean skipBuild;

    /**
     * The configure prefix, to let the RPM harvester know how to build the dir structure.
     * 
     * @parameter
     * @required
     * @since 1.0-alpha-1
     */
    private String prefix;

    /**
     *Override for platform postfix on RPM release number
     *
     * @parameter expression="${DYNAMIC.CBUILDPROP.OS}" alias="rpm.genspec.platformPostfix"
     * @since 1.0-alpha-1
     */
     private String platformPostfix;

    /**
     * Whether to skip postfix on RPM release number
     * rhoover - we can't use null on platformPostfix as an indication to skip the postfix
     *           until this bug is fixed (http://jira.codehaus.org/browse/MNG-1959)
     *           because currently specifying an empty string for a parameter in the POM yields null instead
     *           of an empty string.
     *
     * @parameter expression="${skipPlatformPostfix}" default-value="false" alias="rpm.genspec.skipPlatformPostfix"
     * @since 1.0-alpha-1
     */
     private boolean skipPlatformPostfix;

    /**
     * Override parameter for the name of this RPM
     * 
     * @parameter
     * @since 1.0-alpha-1
     */
    private String rpmName;

    /**
     * The RPM version, typically set in a Dynamic Maven Property in the platform-detect
     * goal during the validate phase.
     * 
     * @parameter expression="${DYNAMIC.CBUILDPROP.RPM.VERSION}"
     * @since 1.0-beta-1
     */
    private String rpmVersion;

    /**
     * MavenProject instance used to furnish information required to construct the RPM name in the
     * event the rpmName parameter is not specified.
     * 
     * @parameter expression="${project}"
     * @required
     * @readonly
     * @since 1.0-alpha-1
     */
    private MavenProject project;

    /**
     * Top directory of the RPM filesystem structure.
     * 
     * @parameter default-value="${project.build.directory}/rpm-topdir"
     * @required
     * @since 1.0-alpha-1
     */
    private File topDir;

     /**
     * The Make DESTDIR, to let the RPM harvester know where the staged installation
     * directory is located for packaging
     * 
     * @parameter default-value="${project.build.directory}/rpm-basedir"
     * @required
     * @since 1.0-alpha-2
     */
    private File destDir;

    /**
     * Directory, inside the RPM filesystem structure, where the RPM will be written. 
     * @since 1.0-alpha-2
     */
    private File rpmsDir;

    /**
     * The RPM releaes level or build number
     * 
     * @parameter expression="${DYNAMIC.CBUILDPROP.RPM.RELEASE}"
     * @since 1.0-alpha-2
     */
    private String release;

    /**
     * @component role-hint="default"
     */
    private CommandLineManager cliManager;

    /**
     * @component
     */
    private RpmInfoFormatter rpmInfoFormatter;

    /**
     * @component
     */
    private ProjectRpmFileManager projectRpmFileManager;

    /**
     * @component
     */
    private MavenProjectHelper projectHelper;    

    /**
     * Build the RPM filesystem structure, setup the Rpm Ant task, and execute. Then, set the File for the
     * project's Artifact instance to the generated RPM for use in the install and deploy phases.
     */
    public void execute()
        throws MojoExecutionException
    {
        if ( skipBuild )
        {
            getLog().info( "Skipping RPM build (per configuration)." );
            return;
        }

        String rpmBaseName = calculateRpmBaseName();

        buildRpmDirectoryStructure();
        
        File rpmrc = createRpmConfigurationFiles();

        buildRpm( rpmBaseName, rpmrc );
        
        chmodTopDirContents();

        setProjectArtifactFile( rpmBaseName );
    }

    private File createRpmConfigurationFiles() throws MojoExecutionException
    {
        File rpmConfigDir = new File( topDir, "maven-rpm-config" );
        rpmConfigDir.mkdirs();
        
        File tmpDir = new File( topDir, "tmp" );
        tmpDir.mkdirs();
        
        File rpmrc = new File( rpmConfigDir, "rpmrc" );
        File macros = new File( rpmConfigDir, "macros" );
        
        List < String > includes = getRpmrcIncludes();
        String macroFiles = getMacroFiles();
        
        FileWriter writer = null;
        
        // write a custom rpmrc that points to a custom macro file
        try
        {
            writer = new FileWriter( rpmrc );
            
            for ( Iterator < String > it = includes.iterator(); it.hasNext(); )
            {
                String include = it.next();
                
                writer.write( "include : " );
                writer.write( include );
                writer.write( LINE_SEPARATOR );
            }
            
            writer.write( "macrofiles : " );
            writer.write( macroFiles );
            if ( macroFiles.length() > 0 )
            {
                writer.write( ':' );
            }
            writer.write( macros.getCanonicalPath() );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "Failed to write rpmrc file in: " + rpmrc, e );
        }
        finally
        {
            IOUtil.close( writer );
        }
        
        writer = null;
        
        // write a custom macro file that defines topdir (and possibly other things)
        try
        {
            writer = new FileWriter( macros );
            
            writer.write( "%_topdir\t\t " );
            writer.write( topDir.getCanonicalPath() );
            writer.write( LINE_SEPARATOR );
            
            writer.write( "%_tmppath\t\t " );
            writer.write( tmpDir.getCanonicalPath() );
            writer.write( LINE_SEPARATOR );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "Failed to write RPM macros file in: " + macros, e );
        }
        finally
        {
            IOUtil.close( writer );
        }
        
        return rpmrc;
    }

    private List < String > getRpmrcIncludes() throws MojoExecutionException
    {
        List < String > includes = this.rpmrcIncludes;
        
        if ( includes == null || includes.isEmpty() )
        {
            List < String > defaultLocations = Arrays.asList( DEFAULT_RPMRC_INCLUDES );
            
            getLog().debug( "Using default rpmrc locations: " + defaultLocations );
            
            includes = new ArrayList < String > ( defaultLocations );
        }
        
        if ( includes != null && !includes.isEmpty() )
        {
            for ( Iterator < String > it = includes.iterator(); it.hasNext(); )
            {
                String include = (String) it.next();
                
                include = StringUtils.replace( include, "~", USER_HOME );
                
                File includeFile = new File( include );
                
                if ( !includeFile.exists() || !includeFile.isFile() )
                {
                    getLog().debug( "rpmrc file: " + includeFile + " does not exist. It will be excluded." );
                    it.remove();
                }
            }
        }
        
        return includes;
    }

    private String getMacroFiles() throws MojoExecutionException
    {
        RpmrcParsingConsumer consumer = new RpmrcParsingConsumer( MACROFILES_HEADER );
        
        Commandline cli = new Commandline();
        
        Shell shell = new BourneShell( true );
        shell.setQuotedArgumentsEnabled( false );

        cli.setShell( shell );
        
        cli.setExecutable( "rpm" );
        cli.createArg().setLine( "--showrc" );
        
        try
        {
            int result = cliManager.execute( cli, consumer, cliManager.newDebugStreamConsumer() );
            
            if ( result != 0 )
            {
                throw new MojoExecutionException( "rpm command returned an exit value != 0. "
                    + "Aborting build; see debug output for more information." );
            }
        }
        catch ( CommandLineException e )
        {
            throw new MojoExecutionException( "Error reading rc info from rpm.", e );
        }
        
        return consumer.getValue();
    }

    private void setProjectArtifactFile( String rpmBaseName ) throws MojoExecutionException
    {
        if ( RpmInfoFormatter.getUseRpmFinalName( project ) )
        {
            rpmBaseName = rpmBaseName + "-" + project.getVersion() + "-" + release;
        }
        
        getLog().debug( "Just before setting the final RPM project-artifact, release is: "
            + release + "; rpmBaseName is: " + rpmBaseName );
        
        projectRpmFileManager.formatAndSetProjectArtifactFile( project, projectHelper,
            topDir, rpmBaseName, skipPlatformPostfix );
        
        List < Artifact > attachedArtifacts = project.getAttachedArtifacts();
        
        // if this doesn't exist, then we either had a problem building the RPM, or we're pointing
        // at the wrong location. Either way, it's time to take a time-out and look around.
        if ( attachedArtifacts.isEmpty() )
        {
            throw new MojoExecutionException( "RPM file: " + rpmBaseName + " did not get attatched." );
        }
        for ( Iterator < Artifact > it = attachedArtifacts.iterator(); it.hasNext(); )
        {
            Artifact artifact = it.next();
            if ( !artifact.getFile().exists() )
            {
                throw new MojoExecutionException( "RPM artifact: "
                    + artifact.getFile().toString() + " does not exist." );
            }
            // Set up maven-install-plugin to keep the extension "rpm" instead of "jar"
            getLog().info( "extension:  " + artifact.getArtifactHandler().getExtension() );
            getLog().info( "classifier: " + artifact.getArtifactHandler().getClassifier() );
            getLog().info( "packaging:  " + artifact.getArtifactHandler().getPackaging() );
            //DefaultArtifactHandler oldHandler = (DefaultArtifactHandler) artifact.getArtifactHandler();
            //RpmAttachedArtifactHandler myHandler = (RpmAttachedArtifactHandler) oldHandler;
            //artifact.setArtifactHandler(myHandler);
        }

    }

    private String calculateRpmBaseName() throws MojoExecutionException
    {
        String rpmBaseName = null;
        
        if ( rpmName != null && rpmName.trim().length() > 0 )
        {
            rpmBaseName = rpmName;
        }
        else
        {
            try
            {
                rpmBaseName = rpmInfoFormatter.formatRpmName( project, rpmVersion, release,
                    true, platformPostfix, skipPlatformPostfix );
            }
            catch ( RpmFormattingException e )
            {
                throw new MojoExecutionException( "Failed to format RPM name. Reason: " + e.getMessage(), e );
            }
        }
        
        return rpmBaseName;
    }

    private void buildRpm( String rpmBaseName, File rpmrc ) throws MojoExecutionException
    {
        Commandline cli = new Commandline();
        File tmpPath = new File( topDir, "tmp" );
        cli.setShell( new BourneShell( true ) );
        
        cli.setExecutable( "rpmbuild" );

        File specFile = new File( topDir + "/SPECS/" + rpmBaseName + ".spec" );

        // More parameterization to cover MacPorts install of RPM

        cli.createArg().setLine( "-bb "
              + "--macros=" + getMacroFiles()        // compatible with RPM > 4.x
              + " --define \"_tmppath " + tmpPath.getAbsolutePath()
              + "\" --define \"_topdir " + topDir.getAbsolutePath()
              + "\" --define \"buildroot " + destDir.getAbsolutePath() + "\" "
              + specFile.getAbsolutePath() );
        
        StreamConsumer consumer = cliManager.newInfoStreamConsumer();
        
        try
        {
            int result = cliManager.execute( cli, consumer, consumer );
            
            if ( result != 0 )
            {
                throw new MojoExecutionException( "rpmbuild command returned an exit value != 0. "
                    + "Aborting build; see command output above for more information." );
            }
        }
        catch ( CommandLineException e )
        {
            throw new MojoExecutionException( "Failed to build RPM. Reason: " + e.getMessage(), e );
        }
    }

    private void chmodTopDirContents() throws MojoExecutionException
    {
        Commandline chmod = new Commandline();
        chmod.setExecutable( "chmod" );
        
        chmod.createArg().setLine( "-R g+w " + topDir.getAbsolutePath() );
        
        try
        {
            StreamConsumer consumer = cliManager.newDebugStreamConsumer();
            
            int result = cliManager.execute( chmod, consumer, consumer );
            
            if ( result != 0 )
            {
                throw new MojoExecutionException( "chmod command returned an exit value != 0. "
                    + "Aborting build; see debug output for more information." );
            }
        }
        catch ( CommandLineException e )
        {
            throw new MojoExecutionException( "Failed to chmod g+w RPM topdir: " + topDir
                + ". Reason: " + e.getMessage(), e );
        }
    }

    /**
     * Ensure that the RPM filesystem structure exists below the RPM top-directory.
     * @throws MojoExecutionException 
     */
    private void buildRpmDirectoryStructure() throws MojoExecutionException
    {
        topDir.mkdirs();

        // my @subdirs = ('BUILD', File::Spec->catfile('RPMS', SystemProperty->getArchitecture()), 'SOURCES', 
        // 'SPECS', 'SRPMS');
        File build = new File( topDir, "BUILD" );
        build.mkdirs();

        try
        {
            rpmsDir = new File( topDir, "RPMS/" + rpmInfoFormatter.formatPlatformArchitecture() );
        }
        catch ( RpmFormattingException e )
        {
            throw new MojoExecutionException( "Cannot read OS architecture from rpm command.", e );
        }

        getLog().info( "RPMS Directory: \'" + rpmsDir.getAbsolutePath() + "\'" );

        rpmsDir.mkdirs();

        File sources = new File( topDir, "SOURCES" );
        sources.mkdirs();

        File specs = new File( topDir, "SPECS" );
        specs.mkdirs();

        File srpms = new File( topDir, "SRPMS" );
        srpms.mkdirs();

        File temps;
        try
        {
            temps = new File( topDir, "tmp/" + rpmInfoFormatter.formatRpmNameWithoutVersion( project )
                + "-root/" + prefix );
        }
        catch ( RpmFormattingException e )
        {
            throw new MojoExecutionException( "Cannot read OS arch from rpm command.", e );
        }

        temps.mkdirs();
    }

    public final class RpmrcParsingConsumer implements StreamConsumer
    {
        private String headerName;
        private String headerValue;
        
        RpmrcParsingConsumer( String headerName )
        {
            this.headerName = headerName;
        }
        
        public void consumeLine( String line )
        {
            getLog().debug( line );
            
            if ( line.startsWith( headerName ) ) 
            {
                int firstColonIdx = line.indexOf( ':' );
                headerValue = line.substring( firstColonIdx + 1 ).trim();
            }
        }
        
        public List < String > getValueList()
        {
            List < String > result = new ArrayList < String > ();
            StringTokenizer tokens = new StringTokenizer( headerValue, ":" );
            
            while ( tokens.hasMoreTokens() )
            {
                result.add( tokens.nextToken() );
            }
            
            return result;
        }
        
        public String getValue()
        {
            return headerValue;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy