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

com.github.goldin.plugins.jenkins.JenkinsMojo.groovy Maven / Gradle / Ivy

package com.github.goldin.plugins.jenkins

import static com.github.goldin.plugins.common.GMojoUtils.*
import com.github.goldin.plugins.common.BaseGroovyMojo
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


/**
 * Plugin that creates Jenkins config files to define new build projects
 */
@MojoGoal ( 'generate' )
@MojoPhase ( 'compile' )
@SuppressWarnings( [ 'StatelessClass', 'UnnecessaryPublicModifier', 'PublicInstanceField', 'NonFinalPublicField' ] )
class JenkinsMojo extends BaseGroovyMojo
{
    @MojoParameter ( required = true )
    public String jenkinsUrl
    public String jenkinsUrl() { verify().notNullOrEmpty( this.jenkinsUrl ) }


    @MojoParameter ( required = true )
    public String generationPom
    public String generationPom() { verify().notNullOrEmpty( this.generationPom ) }


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

    /**
     * When no repository local path is specified - the remote one starting from this value is used
     * For example: for remote repository      "http://google-guice.googlecode.com/svn/trunk/"
     *              and svnRepositoryLocalBase "svn"
     *              local repo path will be    "svn/trunk/"
     */
    @MojoParameter ( required = false )
    public String svnRepositoryLocalBase = 'svn'

    @MojoParameter ( required = false )
    public String endOfLine = 'windows'

    @MojoParameter ( required = false )
    public boolean timestamp = true

    @MojoParameter ( required = false )
    public String timestampFormat = 'MMMM dd, yyyy (HH:mm:ss, \'GMT\'Z)'

    @MojoParameter
    public Job[] jobs

    @MojoParameter
    public Job   job

    private List jobs() { general().list( this.jobs, this.job ) }


    @Override
    void doExecute ()
    {
        int             jobNamePad   = 0; // Number of characters to pad the job name, when logged
        int             jobParentPad = 0; // Number of characters to pad the job parent, when logged
        def             jobParent    = { Job job -> job.parent ? "${ job.parent }" : 'No ' }
        Collection jobs         = configureJobs( jenkinsUrl(), generationPom(), svnRepositoryLocalBase )

        /**
         * Verifying job's state and calculating logging pads
         */
        for ( job in jobs )
        {
            jobNamePad   = Math.max( jobNamePad,   job.toString().size())
            jobParentPad = Math.max( jobParentPad, jobParent( job ).size())
        }

        /**
         * Generating config files
         */
        for ( job in jobs )
        {
            String configPath = ''

            if ( job.isAbstract )
            {
                configPath = "${ job } is abstract"
            }
            else
            {
                File configFile = new File( outputDirectory, "${ job.id }/config.xml" )
                configPath      = configFile.canonicalPath.replace( '\\', '/' )
                def  timestamp  = timestamp ? 'on ' + new Date().format( timestampFormat ) : null
                def  config     = makeTemplate( '/config.xml', [ job : job, timestamp : timestamp ], endOfLine, true )
                Node rootNode   = verify().notNull( new XmlParser().parseText( config ))

                file().mkdirs( configFile.parentFile )

                if ( job.process )
                {
                    def printer                = new XmlNodePrinter( configFile.newPrintWriter( 'UTF-8' ))
                    printer.preserveWhitespace = true
                    String expression          = job.process.trim().with {
                        endsWith( '.groovy' ) ? verify().file( new File(( String ) delegate )).getText( 'UTF-8' ) : delegate
                    }

                    eval( expression, null, null, 'config', config, 'node', rootNode, 'file', configFile )
                    printer.print( rootNode )
                }
                else
                {
                    configFile.write( config, 'UTF-8' )
                }

                verify().file( configFile )
            }

            assert configPath
            log.info( "${ job.toString().padRight( jobNamePad ) }  ${ jobParent( job ).padRight( jobParentPad ) }  ==>  [${ configPath }]" )
        }
    }


    /**
    * Reads all jobs specified and configures them.
    *
    * Verifies that no job is defined more than once and all jobs referenced
    * (via  and ) are defined. Also, when job extends another job
    * (with ) - it's construction is completed using a  job.
    *
    * Returns a mapping of "job ID" => job itself.
    */
    @Requires({ jenkinsUrl && generationPom && svnRepositoryLocalBase })
    private Collection configureJobs ( String jenkinsUrl, String generationPom, String svnRepositoryLocalBase )
    {
        Map allJobs = [:]

        /**
         * - Reading all jobs,
         * - Building allJobs[] map
         * - Updating job's "Jenkins URL" and "generation POM" properties
         * - For each repository - setting it's "local" part
         *   (most of the time it is omitted by user since it can be calculated from "remote" part)
         */
        for ( job in jobs())
        {
            Job prevJob = allJobs.put( job.id, job )
            assert ( ! prevJob ), "[$job] is defined more than once"

            job.jenkinsUrl    = jenkinsUrl
            job.generationPom = generationPom

            for ( repo in job.repositories())
            {
                repo.remote = verify().notNullOrEmpty( repo.remote ).replaceAll( '/$', '' ) // Trimming trailing '/'
                assert  ( ! ( verify().notNullOrEmpty( repo.remote )).endsWith( '/' ))

                if (( ! repo.local ) && ( repo.svn ))
                {
                    int index                    = repo.remote.lastIndexOf( svnRepositoryLocalBase )
                    if     ( index < 0 ) { index = repo.remote.lastIndexOf( '/' ) + 1 } // last path chunk
                    assert ( index > 0 )
                    repo.local = verify().notNullOrEmpty( repo.remote.substring( index ))
                }
            }
        }

        for( job in jobs())
        {
            /**
             * "Extending" each job with a  jobs or with default values
             */

            job.extend( job.parent ? composeJob( allJobs, job.parent ) : new Job())

            /**
             * Whether job's parent is a real or an abstract one
             */
            job.parentIsReal = ( job.parent && ( ! allJobs[ job.parent ].isAbstract ))

            if ( job.mavenGoals && ( job.jobType == Job.JobType.maven ))
            {   /**
                 * Top-level "base" job may have no maven goals set and it has nowhere to "inherit" it from
                 */
                job.updateMavenGoals()
            }

            /**
             * Verifying all jobs invoked are defined
             */

            for ( invokedJobId in job.invoke?.jobsSplit )
            {
                assert allJobs[ invokedJobId ], "[$job] invokes job [$invokedJobId] but it's not defined. " +
                                                "Defined jobs: ${ allJobs.keySet() }"
            }

           /**
            * Updating "Child Jobs" and "Invoked By" list
            */

            List childJobs = []
            List invokedBy = []

            for ( otherJob in jobs().findAll{ it.id != job.id } )
            {
                if ( otherJob.parent == job.id )                       { childJobs << otherJob }
                if ( otherJob.invoke?.jobsSplit?.any{ it == job.id } ) { invokedBy << otherJob }
            }

            job.childJobs = childJobs as Job[]
            job.invokedBy = invokedBy as Job[]
        }

        allJobs.values()*.validate()
    }


    /**
     * Composes one job from all "parent jobs" specified, separated with comma
     *
     * @param allJobs    mapping of all existing jobs: job id => job instance
     * @param parentJobs parent jobs to compose the result job from, separated with comma.
     *
     *                 Note: later jobs in the list are of higher priority, they override
     *                       values set by earlier jobs!
     *                       In "nightly, git" - "git" job is of higher priority and will override values
     *                                           set by "nightly" one
     * @return composed job
     */
    @Requires({ allJobs && parentJobs })
    @Ensures({ result })
    private Job composeJob( Map allJobs, String parentJobs )
    {
        Job resultJob = null

        if ( parentJobs.contains( ',' ))
        {   /**
             * Multiple inheritance - we need to compose an "aggregator" job from all "base" jobs
             */

            resultJob = new Job( id: "Composition of jobs [$parentJobs]" )
            for ( parentJobId in split( parentJobs ))
            {
                Job    parentJob = allJobs[ parentJobId ]
                assert parentJob, "Parent job [$parentJobId] is undefined"

                resultJob.extend( parentJob, true )
            }
        }
        else
        {   /**
             * No multiple inheritance
             */

            resultJob = allJobs[ parentJobs ]
            assert resultJob, "Parent job [$parentJobs] is undefined"
        }

        resultJob
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy