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

org.codehaus.mojo.build.BuildMojo Maven / Gradle / Ivy

Go to download

This plugin is designed to give you a build number. So when you might make 100 builds of version 1.0-SNAPSHOT, you can differentiate between them all.

There is a newer version: 3.2.1
Show newest version
package org.codehaus.mojo.build;

/**
 * The MIT License
 * 
 * Copyright (c) 2005 Learning Commons, University of Calgary
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFile;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmResult;
import org.apache.maven.scm.command.status.StatusScmResult;
import org.apache.maven.scm.command.update.UpdateScmResult;
import org.apache.maven.scm.log.ScmLogDispatcher;
import org.apache.maven.scm.log.ScmLogger;
import org.apache.maven.scm.manager.ScmManager;
import org.apache.maven.scm.provider.ScmProvider;
import org.apache.maven.scm.provider.ScmProviderRepository;
import org.apache.maven.scm.provider.svn.command.update.SvnUpdateScmResult;
import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
import org.apache.maven.scm.repository.ScmRepository;
import org.codehaus.plexus.util.StringUtils;

/**
 * This mojo is designed to give you a build number. So when you might make 100 builds of version
 * 1.0-SNAPSHOT, you can differentiate between them all. The build number is based on the revision
 * number retrieved from scm. It only works with subversion, currently. This mojo can also check to
 * make sure that you have checked everything into scm, before issuing the build number. That
 * behaviour can be suppressed, and then the latest local build number is used. Build numbers are
 * not reflected in your artifact's filename (automatically), but can be added to the metadata. You
 * can access the build number in your pom with ${buildNumber}. You can also access ${timestamp}.
 * 
 * @author Julian Wood
 * @version $Id: BuildMojo.java 8560 2009-01-07 08:30:46Z dantran $
 * @goal create
 * @requiresProject
 * @description create a timestamp and a build number from scm or an integer sequence
 */
public class BuildMojo
    extends AbstractMojo
{

    /**
     * @parameter expression="${project.scm.developerConnection}"
     * @readonly
     */
    private String urlScm;

    /**
     * The username that is used when connecting to the SCM system.
     * 
     * @parameter expression="${username}"
     * @since 1.0-beta-1
     */
    private String username;

    /**
     * The password that is used when connecting to the SCM system.
     * 
     * @parameter expression="${password}"
     * @since 1.0-beta-1
     */
    private String password;

    /**
     * The tag base directory in subversion, you must define it if you don't use the standard svn
     * layout (branches/tags/trunk).
     * 
     * @parameter expression="${tagBase}"
     * @since 1.0-beta-1
     */
    private String tagBase;

    /**
     * Local directory to be used to issue SCM actions
     * 
     * @parameter expression="${maven.buildNumber.scmDirectory}" default-value="${basedir}
     * @since 1.0-beta-
     */
    private File scmDirectory;



    /**
     * You can rename the buildNumber property name to another property name if desired.
     * 
     * @parameter expression="${maven.buildNumber.buildNumberPropertyName}"
     *            default-value="buildNumber"
     * @since 1.0-beta-1
     */
    private String buildNumberPropertyName;

    /**
     * You can rename the timestamp property name to another property name if desired.
     * 
     * @parameter expression="${maven.buildNumber.timestampPropertyName}" default-value="timestamp"
     * @since 1.0-beta-1
     */
    private String timestampPropertyName;

    /**
     * If this is made true, we check for modified files, and if there are any, we fail the build.
     * Note that this used to be inverted (skipCheck), but needed to be changed to allow releases to
     * work. This corresponds to 'svn status'.
     * 
     * @parameter expression="${maven.buildNumber.doCheck}" default-value="false"
     * @since 1.0-beta-1
     */
    private boolean doCheck;

    /**
     * If this is made true, then the revision will be updated to the latest in the repo, otherwise
     * it will remain what it is locally. Note that this used to be inverted (skipUpdate), but
     * needed to be changed to allow releases to work. This corresponds to 'svn update'.
     * 
     * Note that these expressions (doCheck, doUpdate, etc) are the first thing evaluated. If there
     * is no matching expression, we get the default-value. If there is (ie
     * -Dmaven.buildNumber.doCheck=false), we get that value. The configuration, however, gets the
     * last say, through use of the getters/setters below. So if true, then
     * normally that's the final value of the param in question. However, this mojo reverses that
     * behaviour, such that the command line parameters get the last say.
     * 
     * @parameter expression="${maven.buildNumber.doUpdate}" default-value="false"
     * @since 1.0-beta-1
     */
    private boolean doUpdate;

    /**
     * Specify a message as specified by java.text.MessageFormat. This triggers "items"
     * configuration to be read
     * 
     * @parameter
     * @since 1.0-beta-1
     */
    private String format;

    /**
     * Properties file to be created when "format" is not null and item has "buildNumber". See Usage
     * for details
     * 
     * @parameter default-value="${basedir}/buildNumber.properties";
     * @since 1.0-beta-2
     */
    private File buildNumberPropertiesFileLocation;

    /**
     * Specify the corresponding items for the format message, as specified by
     * java.text.MessageFormat. Special item values are "timestamp" and "buildNumber/d*".
     * 
     * @parameter
     * @since 1.0-beta-1
     */
    private List items;

    /**
     * The locale used for date and time formatting. The locale name should be in the format defined
     * in {@link Locale#toString()}. The default locale is the platform default returned by
     * {@link Locale#getDefault()}.
     * 
     * @parameter expression="${maven.buildNumber.locale}"
     * @since 1.0-beta-2
     */
    private String locale;

    /**
     * whether to retrieve the revision for the last commit, or the last revision of the repository.
     * 
     * @parameter expression="${maven.buildNumber.useLastCommittedRevision}" default-value="false"
     * @since 1.0-beta-2
     */
    private boolean useLastCommittedRevision;

    /**
     * Apply this java.text.MessageFormat to the timestamp only (as opposed to the
     * format parameter).
     * 
     * @parameter
     * @since 1.0-beta-2
     */
    private String timestampFormat;

    /**
     * Setting this value allows the build to continue even in the event of an SCM failure.  The value set will be
     * used as the revision string in the event of a failure to retrieve the revision it from the SCM.
     * 
     * @parameter
     * @since 1.0-beta-2
     */
    private String revisionOnScmFailure;
    
    /**
     * @component
     */
    private ScmManager scmManager;

    /**
     * The maven project.
     * 
     * @parameter expression="${project}"
     * @readonly
     */
    private MavenProject project;

    private ScmLogDispatcher logger;

    private String revision;


    public void execute()
        throws MojoExecutionException, MojoFailureException
    {
        Date now = Calendar.getInstance().getTime();
        if ( format != null )
        {
            // needs to be an array
            // look for special values
            Object[] itemAry = new Object[items.size()];
            for ( int i = 0; i < items.size(); i++ )
            {
                Object item = items.get( i );
                if ( item instanceof String )
                {
                    String s = (String) item;
                    if ( s.equals( "timestamp" ) )
                    {
                        itemAry[i] = now;
                    }
                    else if ( s.startsWith( "buildNumber" ) )
                    {
                        // check for properties file
                        File propertiesFile = this.buildNumberPropertiesFileLocation;

                        // create if not exists
                        if ( !propertiesFile.exists() )
                        {
                            try
                            {
                                propertiesFile.createNewFile();
                            }
                            catch ( IOException e )
                            {
                                throw new MojoExecutionException( "Couldn't create properties file: " + propertiesFile,
                                                                  e );
                            }
                        }

                        Properties properties = new Properties();
                        String buildNumberString = null;
                        try
                        {
                            // get the number for the buildNumber specified
                            properties.load( new FileInputStream( propertiesFile ) );
                            buildNumberString = properties.getProperty( s );
                            if ( buildNumberString == null )
                            {
                                buildNumberString = "0";
                            }
                            int buildNumber = Integer.valueOf( buildNumberString ).intValue();

                            // store the increment
                            properties.setProperty( s, String.valueOf( ++buildNumber ) );
                            properties.store( new FileOutputStream( propertiesFile ),
                                              "maven.buildNumber.plugin properties file" );

                            // use in the message (format)
                            itemAry[i] = new Integer( buildNumber );
                        }
                        catch ( NumberFormatException e )
                        {
                            throw new MojoExecutionException(
                                                              "Couldn't parse buildNumber in properties file to an Integer: "
                                                                  + buildNumberString );
                        }
                        catch ( IOException e )
                        {
                            throw new MojoExecutionException( "Couldn't load properties file: " + propertiesFile, e );
                        }
                    }
                    else
                    {
                        itemAry[i] = item;
                    }
                }
                else
                {
                    itemAry[i] = item;
                }
            }

            revision = format( itemAry );
        }
        else
        {
            if ( doCheck )
            {
                // we fail if there are local mods
                checkForLocalModifications();
            }
            else
            {
                getLog().info( "Checking for local modifications: skipped." );
            }

            if ( doUpdate )
            {
                // we update your local repo
                // even after you commit, your revision stays the same until you update, thus this
                // action
                List changedFiles = update();
                for ( Iterator i = changedFiles.iterator(); i.hasNext(); )
                {
                    ScmFile file = (ScmFile) i.next();
                    getLog().info( "Updated: " + file );
                }
                if ( changedFiles.size() == 0 )
                {
                    getLog().info( "No files needed updating." );
                }
            }
            else
            {
                getLog().info( "Updating project files from SCM: skipped." );
            }

            revision = getRevision();
        }

        if ( project != null )
        {
            String timestamp = String.valueOf( now.getTime() );
            if ( timestampFormat != null )
            {
                timestamp = MessageFormat.format( timestampFormat, new Object[] { now } );
            }

            getLog().info(
                           MessageFormat.format( "Storing buildNumber: {0} at timestamp: {1}", new Object[] {
                               revision,
                               timestamp } ) );
            project.getProperties().put( buildNumberPropertyName, revision );
            project.getProperties().put( timestampPropertyName, timestamp );
        }
    }

    /**
     * Formats the given argument using the configured format template and locale.
     * 
     * @param arguments arguments to be formatted @ @return formatted result
     */
    private String format( Object[] arguments )
    {
        Locale l = Locale.getDefault();
        if ( locale != null )
        {
            String[] parts = locale.split( "_", 3 );
            if ( parts.length <= 1 )
            {
                l = new Locale( locale );
            }
            else if ( parts.length == 2 )
            {
                l = new Locale( parts[0], parts[1] );
            }
            else
            {
                l = new Locale( parts[0], parts[1], parts[2] );
            }
        }

        return new MessageFormat( format, l ).format( arguments );
    }

    private void checkForLocalModifications()
        throws MojoExecutionException
    {
        getLog().info( "Verifying there are no local modifications ..." );

        List changedFiles;

        try
        {
            changedFiles = getStatus();
        }
        catch ( ScmException e )
        {
            throw new MojoExecutionException( "An error has occurred while checking scm status.", e );
        }

        if ( !changedFiles.isEmpty() )
        {
            StringBuffer message = new StringBuffer();

            for ( Iterator i = changedFiles.iterator(); i.hasNext(); )
            {
                ScmFile file = (ScmFile) i.next();

                message.append( file.toString() );

                message.append( "\n" );
            }

            throw new MojoExecutionException(
                                              "Cannot create the build number because you have local modifications : \n"
                                                  + message );
        }

    }

    public List update()
        throws MojoExecutionException
    {
        try
        {
            ScmRepository repository = getScmRepository();

            ScmProvider scmProvider = scmManager.getProviderByRepository( repository );

            UpdateScmResult result = scmProvider.update( repository, new ScmFileSet( new File( scmDirectory
                .getAbsolutePath() ) ), "" );

            checkResult( result );

            if ( result instanceof SvnUpdateScmResult )
            {
                String revision = ( (SvnUpdateScmResult) result ).getRevision();
                getLog().info( "Got a revision during update: " + revision );
                this.revision = revision;
            }

            return result.getUpdatedFiles();
        }
        catch ( ScmException e )
        {
            throw new MojoExecutionException( "Couldn't update project.", e );
        }

    }

    public List getStatus()
        throws ScmException
    {

        ScmRepository repository = getScmRepository();

        ScmProvider scmProvider = scmManager.getProviderByRepository( repository );

        StatusScmResult result = scmProvider.status( repository, new ScmFileSet( new File( scmDirectory
            .getAbsolutePath() ) ) );

        checkResult( result );

        return result.getChangedFiles();

    }

    /**
     * Get the revision info from the repository. For svn, it is svn info
     * 
     * @return
     * @throws MojoExecutionException
     */
    public String getRevision()
        throws MojoExecutionException
    {

        if ( format != null )
        {
            return revision;
        }

        try
        {
            ScmRepository repository = getScmRepository();

            InfoScmResult result = info( repository, new ScmFileSet( new File( scmDirectory.getAbsolutePath() ) ) );

            checkResult( result );

            return result.getRevision( useLastCommittedRevision );
        }
        catch ( ScmException e )
        {
            if ( !StringUtils.isEmpty( revisionOnScmFailure ) ) {
                getLog().warn("Cannot get the revision information from the scm repository, proceeding with " +
                        "revision of " + revisionOnScmFailure + " : \n" + e.getLocalizedMessage());
                
                setDoCheck(false);
                setDoUpdate(false);
                
                return revisionOnScmFailure;
            }
            
            throw new MojoExecutionException( "Cannot get the revision information from the scm repository : \n"
                + e.getLocalizedMessage(), e );

        }

    }

    /**
     * Get info from svn.
     * 
     * @param repository
     * @param fileSet
     * @return
     * @throws ScmException
     * @todo this should be rolled into org.apache.maven.scm.provider.ScmProvider and
     *       org.apache.maven.scm.provider.svn.SvnScmProvider
     */
    public InfoScmResult info( ScmRepository repository, ScmFileSet fileSet )
        throws ScmException
    {
        SvnInfoCommand command = new SvnInfoCommand();

        command.setLogger( getLogger() );

        return (InfoScmResult) command.execute( repository.getProviderRepository(), fileSet, null );
    }

    /**
     * @return
     * @todo normally this would be handled in AbstractScmProvider
     */
    private ScmLogger getLogger()
    {
        if ( logger == null )
        {
            logger = new ScmLogDispatcher();
        }
        return logger;
    }

    private ScmRepository getScmRepository()
        throws ScmException
    {
        ScmRepository repository;

        try
        {
            repository = scmManager.makeScmRepository( urlScm );

            ScmProviderRepository scmRepo = repository.getProviderRepository();

            if ( !StringUtils.isEmpty( username ) )
            {
                scmRepo.setUser( username );
            }
            if ( !StringUtils.isEmpty( password ) )
            {
                scmRepo.setPassword( password );
            }

            if ( repository.getProvider().equals( "svn" ) )
            {
                SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();

                if ( tagBase != null && tagBase.length() > 0 )
                {
                    svnRepo.setTagBase( tagBase );
                }
            }
        }
        catch ( Exception e )
        {
            throw new ScmException( "Can't load the scm provider.", e );
        }

        return repository;
    }

    private void checkResult( ScmResult result )
        throws ScmException
    {
        if ( !result.isSuccess() )
        {
            // TODO: improve error handling
            System.err.println( "Provider message:" );

            System.err.println( result.getProviderMessage() );

            System.err.println( "Command output:" );

            System.err.println( result.getCommandOutput() );

            throw new ScmException( "Error!" );
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////
    // setters to help with test
    public void setScmManager( ScmManager scmManager )
    {
        this.scmManager = scmManager;
    }

    public void setUrlScm( String urlScm )
    {
        this.urlScm = urlScm;
    }

    public void setUsername( String username )
    {
        this.username = username;
    }

    public void setPassword( String password )
    {
        this.password = password;
    }

    public void setTagBase( String tagBase )
    {
        this.tagBase = tagBase;
    }

    public void setDoCheck( boolean doCheck )
    {
        String doCheckSystemProperty = System.getProperty( "maven.buildNumber.doCheck" );
        if ( doCheckSystemProperty != null )
        {
            // well, this gets the final say
            this.doCheck = Boolean.valueOf( doCheckSystemProperty ).booleanValue();
        }
        else
        {
            this.doCheck = doCheck;
        }
    }

    public void setDoUpdate( boolean doUpdate )
    {
        String doUpdateSystemProperty = System.getProperty( "maven.buildNumber.doUpdate" );
        if ( doUpdateSystemProperty != null )
        {
            // well, this gets the final say
            this.doUpdate = Boolean.valueOf( doUpdateSystemProperty ).booleanValue();
        }
        else
        {
            this.doUpdate = doUpdate;
        }
    }

    void setFormat( String format )
    {
        this.format = format;
    }

    void setLocale( String locale )
    {
        this.locale = locale;
    }

    void setItems( List items )
    {
        this.items = items;
    }

    public void setBuildNumberPropertiesFileLocation( File buildNumberPropertiesFileLocation )
    {
        this.buildNumberPropertiesFileLocation = buildNumberPropertiesFileLocation;
    }

    public void setScmDirectory( File scmDirectory )
    {
        this.scmDirectory = scmDirectory;
    }

    public void setRevisionOnScmFailure( String revisionOnScmFailure )
    {
        this.revisionOnScmFailure = revisionOnScmFailure;
    }
        
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy