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

com.github.goldin.plugins.about.AboutMojo.groovy Maven / Gradle / Ivy

The newest version!
package com.github.goldin.plugins.about

import static com.github.goldin.plugins.common.GMojoUtils.*
import com.github.goldin.gcommons.beans.ExecOption
import com.github.goldin.plugins.common.BaseGroovyMojo
import org.apache.maven.artifact.Artifact
import org.apache.maven.plugin.MojoExecutionException
import org.gcontracts.annotations.Ensures
import org.gcontracts.annotations.Requires
import org.jfrog.maven.annomojo.annotations.MojoGoal
import org.jfrog.maven.annomojo.annotations.MojoParameter
import org.jfrog.maven.annomojo.annotations.MojoPhase
import org.jfrog.maven.annomojo.annotations.MojoRequiresDependencyResolution

import java.text.SimpleDateFormat


/**
 * Updates files specified with "about" build metadata
 */
@MojoGoal( 'create-about' )
@MojoPhase( 'package' )
@MojoRequiresDependencyResolution( 'test' )
@SuppressWarnings( [ 'StatelessClass', 'PublicInstanceField', 'NonFinalPublicField' ] )
class AboutMojo extends BaseGroovyMojo
{
    public static final String SEPARATOR = '|==============================================================================='

    @MojoParameter
    public boolean updateArchives = true

    @MojoParameter
    public String  prefix = 'META-INF'

    @MojoParameter ( defaultValue = 'about-${project.groupId}-${project.artifactId}-${project.version}.txt' )
    public String  fileName

    @MojoParameter
    public boolean dumpSCM    = true

    @MojoParameter
    public boolean dumpMaven  = false

    @MojoParameter
    public boolean dumpEnv    = false

    @MojoParameter
    public boolean dumpSystem = false

    @MojoParameter
    public boolean dumpPaths  = false

    @MojoParameter
    public boolean dumpDependencies = false

    @MojoParameter
    public  String addContent = ''
    private String evaluateAddContent()
    {
        addContent.trim().with { ( startsWith( '{{' ) && endsWith( '}}' )) ? eval(( String ) delegate, String ) : delegate }
    }

    @MojoParameter
    public String  endOfLine   = 'windows'

    @MojoParameter ( defaultValue = '${project.build.directory}' )
    public File    directory

    @MojoParameter
    public String  include = '*.jar'

    @MojoParameter
    public String  exclude

    @MojoParameter
    public boolean failOnError = true

    @MojoParameter
    public boolean failIfNotFound = true

    private env = System.getenv()


    @Requires({ ( s != null ) && prefix })
    @Ensures({ result != null })
    private String padLines ( String s, String prefix )
    {
        List lines = s.readLines()
        ( lines ? ( lines[ 0 ] + (( lines.size() > 1 ) ? '\n' + lines[ 1 .. -1 ].collect { '|' + ( ' ' * prefix.size()) + it }.join( '\n' ) :
                                                         '' )) :
                  '' )
    }


    private String exec ( String  command,
                          File    directory       = basedir,
                          boolean failOnError     = true,
                          boolean failIfEmpty     = true,
                          int     minimalListSize = -1 )
    {
        assert command && directory

        if ( log.isDebugEnabled()) { log.debug( "Running [$command] in [$directory.canonicalPath]" ) }

        String result =  general().executeWithResult( command, ExecOption.Runtime, failOnError, -1, directory )

        if ( log.isDebugEnabled()) { log.debug( "Running [$command] in [$directory.canonicalPath] - result is [$result]" ) }

        if ( minimalListSize > 0 )
        {
            List lines = result.readLines()
            assert lines.size() >= minimalListSize, \
                   "Received not enough data when running [$command] in [$directory.canonicalPath] - " +
                   "expected list of size [$minimalListSize] at least, received [$result]$lines of size [${ lines.size() }]"
        }

        assert ( result || ( ! failIfEmpty )), \
               "Failed to run [$command] in [$directory.canonicalPath] - result is empty [$result]"
        result
    }


    private String find ( String prefix, String command ) { find( prefix, exec( command ).readLines()) }
    private String find ( String prefix, List l ) { l.find{ it.startsWith( prefix ) }?.replace( prefix, '' )?.trim() ?: '' }
    private String sort ( Map map )
    {
        def maxKey = maxKeyLength( map ) + 3
        map.sort().collect { key, value -> " [$key]".padRight( maxKey ) + ":[$value]" }.
                   join( '\n' )
    }


    /**
     * Retrieves result of running "mvn dependency:tree" for the current project.
     *
     * @return Result of running "mvn dependency:tree" for the current project.
     */
    @Ensures({ result })
    private String dependencyTree()
    {
        if ( project.collectedProjects )
        {
            return 'Aggregate project, no dependencies shown'
        }

        String coordinates = "${project.groupId}:${project.artifactId}:${project.packaging}:${project.version}"
        String plugin      = 'maven-dependency-plugin:2.4:tree'
        String mvnHome     = env[ 'M2_HOME' ]

        assert mvnHome, "'M2_HOME' environment variable is not defined"

        File   mvn       = new File( new File( mvnHome ), 'bin/mvn' + ( isWindows ? '.bat' : '' )).canonicalFile
        String mavenRepo = System.getProperty( 'maven.repo.local' )
        String command   = "$mvn -e -B -f ${ project.file.canonicalPath } org.apache.maven.plugins:$plugin" +
                           ( mavenRepo ? " -Dmaven.repo.local=$mavenRepo" : '' )
        long   t       = System.currentTimeMillis()

        assert mvn.file, "[$mvn] - not found"

        log.info( "Running [$command]" )

        def mdt = exec( command )

        log.info( "Running [$command] - done, [${ System.currentTimeMillis() - t }] ms" )

        assert [ plugin, coordinates ].every { mdt.contains( it ) }, \
               "Failed to run [$plugin] - data received doesn't contain enough information: [$mdt]"

        def mdtStripped = mdt.replace( '[INFO] ', '' ).
                              replaceAll( /(?s)^.+?@.+?---/,              '' ). // Removing Maven 3 header
                              replaceAll( /(?s)^.+\[dependency:tree.+?]/, '' ). // Removing Maven 2 header
                              replaceAll( /(?m)Downloading: .+$/,         '' ). // Removing Maven 3 download progress indicator
                              replaceAll( /(?m)Downloaded: .+$/,          '' ). // Removing Maven 3 download progress indicator
                              replaceAll( /(?s)----+.+$/,                 '' ). // Removing footer
                              trim()

        assert mdtStripped.startsWith( coordinates ), \
               "Failed to run [$plugin] - cleaned up data should start with [$coordinates]: [$mdtStripped]"

        project.artifacts.each {
            Artifact a ->
            if ( ! a.groupId.startsWith( IVY_PREFIX ))
            {
                "$a.groupId:$a.artifactId".with {
                    assert mdtStripped.contains(( String ) delegate ), \
                    "Failed to run [$plugin] - cleaned up data should contain [$delegate]: [$mdtStripped]"
                }
            }
        }

        mdtStripped
    }


    String jenkinsContent()
    {
        // https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project

        """
        $SEPARATOR
        | Jenkins Info
        $SEPARATOR
        | Server         : [${ env[ 'JENKINS_URL' ] }]
        | Job            : [${ env[ 'JENKINS_URL' ] }job/${ env[ 'JOB_NAME' ] }/${ env[ 'BUILD_NUMBER' ]}/]
        | Log            : [${ env[ 'JENKINS_URL' ] }job/${ env[ 'JOB_NAME' ] }/${ env[ 'BUILD_NUMBER' ]}/console]"""
    }


    String hudsonContent()
    {
        // http://weblogs.java.net/blog/johnsmart/archive/2008/03/using_hudson_en.html

        """
        $SEPARATOR
        | Hudson Info
        $SEPARATOR
        | Server         : [${ env[ 'HUDSON_URL' ] }]
        | Job            : [${ env[ 'HUDSON_URL' ] }job/${ env[ 'JOB_NAME' ] }/${ env[ 'BUILD_NUMBER' ]}/]
        | Log            : [${ env[ 'HUDSON_URL' ] }job/${ env[ 'JOB_NAME' ] }/${ env[ 'BUILD_NUMBER' ]}/console]"""
    }


    String teamcityContent()
    {
        // http://confluence.jetbrains.net/display/TCD65/Predefined+Build+Parameters
        // http://confluence.jetbrains.net/display/TCD7/Predefined+Build+Parameters

        def urlMessage  = 'Define \'TEAMCITY_URL\' environment variable and make sure \'-Dteamcity.build.id\' specified when job starts'
        def buildId     = System.getProperty( 'teamcity.build.id' )
        def teamCityUrl = ( env[ 'TEAMCITY_URL' ]?.replaceAll( /(? svnInfo = exec( "svn info ${basedir.canonicalPath}", basedir, true, true, 2 ).readLines()

        /**
         * ------------------------------------------------------------------------
         * r39087 | Evgeny | 2011-08-24 09:28:06 +0300 (Wed, 24 Aug 2011) | 1 line
         *
         * About removed
         * ------------------------------------------------------------------------
         */

        List commitLines = exec( "svn log  ${basedir.canonicalPath} -l 1", basedir, true, true, 3 ).readLines().grep()

        assert [ commitLines[ 0 ], commitLines[ -1 ]].each { it.with { startsWith( '---' ) && endsWith( '---' ) }}, \
               "Unknown commit format:\n$commitLines"

        String       commit        = commitLines[ 1 ]
        List commitMessage = ( commitLines.size() > 3 ) ? commitLines[ 2 .. -2 ]*.trim() : []

        """
        $SEPARATOR
        | SVN Info
        $SEPARATOR
        | Repository     : [${ find( 'URL:',      svnInfo )}]
        | Revision       : [${ find( 'Revision:', svnInfo )}]
        | Status         : [${ padLines( svnStatus, ' Status         : [' ) }]
        | Commit         : [$commit]
        | Commit Date    : [${ split( commit, '\\|' )[ 2 ].trim() }]
        | Commit Author  : [${ split( commit, '\\|' )[ 1 ].trim() }]
        | Commit Message : [${ padLines( commitMessage.join( '\n' ), ' Commit Message : [' ) }]"""
    }


    String gitContent( String gitStatus )
    {
        /**
         * http://schacon.github.com/git/git-log.html
         * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         * dc38954
         * dc389541c4aa7f72f07f11236b1c632a919de61c
         * Fri, 28 Oct 2011 15:40:03 +0200
         * Evgeny Goldin
         * [email protected]
         * 0.2.3.5-SNAPSHOT
         * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         */
        List log = exec( 'git log -1 --format=format:%h%n%H%n%cD%n%cN%n%ce%n%B', basedir, true, true, 5 ).readLines()*.trim()

        """
        $SEPARATOR
        | Git Info
        $SEPARATOR
        | Repositories   : [${ padLines( exec( 'git remote -v' ), ' Repositories   : [' ) }]
        | Branch         : [${ find( '# On branch', 'git status' ) }]
        | Git Status     : [${ padLines( gitStatus, ' Git Status     : [' ) }]
        | Commit         : [${ log[ 0 ] }][${ log[ 1 ] }]
        | Commit Date    : [${ log[ 2 ] }]
        | Commit Author  : [${ log[ 3 ] } <${ log[ 4 ] }>]
        | Commit Message : [${ log.size() > 5 ? padLines( log[ 5 .. -1 ].join( '\n' ), ' Commit Message : [' ) : '' }]"""
    }


    String allContent()
    {
        def version = properties( 'META-INF/maven/com.github.goldin/about-maven-plugin/pom.properties', AboutMojo.classLoader ).
                      getProperty( 'version', '' )

        ( " Created with http://evgeny-goldin.com/wiki/Maven-about-plugin${ version ? ', version "' + version + '"' : '' }\n" +
          serverContent()   +
          scmContent()      +
          buildContent()    +
          optionalContent() + '\n' +
          SEPARATOR ).
        stripMargin().readLines()*.replaceAll( /\s+$/, '' ).grep(). // Deleting empty lines
        join(( 'windows' == endOfLine ) ? '\r\n' : '\n' )
    }


    @Requires({ aboutFile })
    @Ensures({ result == aboutFile })
    File writeAboutFile( File aboutFile )
    {
        file().delete( aboutFile )

        long t = System.currentTimeMillis()

        log.info( "Generating \"about\" in [$aboutFile.canonicalPath], basedir is [${ basedir.canonicalPath }]" )
        file().mkdirs( aboutFile.parentFile )
        aboutFile.write( allContent())
        log.info( "Generated  \"about\" in [$aboutFile.canonicalPath] (${ System.currentTimeMillis() - t } ms)" )

        aboutFile
    }


    @Override
    void doExecute ()
    {
        try
        {
            if ( updateArchives )
            {
                if ( ! directory.directory )
                {
                    assert  ( ! failIfNotFound ), "Directory [$directory.canonicalPath] is not available, consider using "
                    log.warn( "Directory [$directory.canonicalPath] is not available, \"about\" is not created" )
                    return
                }

                def split = { String s -> s ? split( s ) : null }
                def files = file().files( directory, split( include ), split( exclude ), false, false, failIfNotFound )

                if ( files )
                {
                    def aboutFile = new File( outputDirectory(), fileName )
                    def prefix    = (( prefix == '/' ) ? '' : prefix )

                    writeAboutFile( aboutFile )

                    for ( f in files )
                    {
                        def aboutPath = "$f.canonicalPath/$prefix${ prefix ? '/' : '' }$fileName"

                        log.info( "Adding \"about\" to [$aboutPath]" )
                        file().pack( aboutFile.parentFile, f, [ aboutFile.name ], null, false, true, true, null, null, prefix )
                        log.info( "Added  \"about\" to [$aboutPath]" )
                    }

                    file().delete( aboutFile )
                }
                else
                {
                    log.warn( "No files found in [$directory.canonicalPath] and include/exclude patterns [${ include ?: '' }]/[${ exclude ?: '' }]" )
                }
            }
            else
            {
                def aboutFile = ( File ) new File( fileName ).with{ absolute ? delegate : new File( outputDirectory(), fileName )}
                writeAboutFile( aboutFile )
            }
        }
        catch ( Throwable e )
        {
            def message = 'Failed to create "about" file'
            if ( failOnError ) { throw new MojoExecutionException( message, e ) }
            log.error( "$message:", e )
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy