| package org.apache.maven.shared.release; |
| |
| /* |
| * 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.util.Collections; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.lang3.BooleanUtils; |
| import org.apache.maven.shared.release.config.ReleaseDescriptor; |
| import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder; |
| import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor; |
| import org.apache.maven.shared.release.config.ReleaseDescriptorStore; |
| import org.apache.maven.shared.release.config.ReleaseDescriptorStoreException; |
| import org.apache.maven.shared.release.config.ReleaseUtils; |
| import org.apache.maven.shared.release.phase.ReleasePhase; |
| import org.apache.maven.shared.release.phase.ResourceGenerator; |
| import org.apache.maven.shared.release.strategy.Strategy; |
| import org.codehaus.plexus.component.annotations.Component; |
| import org.codehaus.plexus.component.annotations.Requirement; |
| import org.codehaus.plexus.logging.AbstractLogEnabled; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * Implementation of the release manager. |
| * |
| * @author <a href="mailto:brett@apache.org">Brett Porter</a> |
| */ |
| @Component( role = ReleaseManager.class ) |
| public class DefaultReleaseManager |
| extends AbstractLogEnabled |
| implements ReleaseManager |
| { |
| @Requirement |
| private Map<String, Strategy> strategies; |
| |
| /** |
| * The available phases. |
| */ |
| @Requirement |
| private Map<String, ReleasePhase> releasePhases; |
| |
| /** |
| * The configuration storage. |
| */ |
| @Requirement( hint = "properties" ) |
| private ReleaseDescriptorStore configStore; |
| |
| private static final int PHASE_SKIP = 0, PHASE_START = 1, PHASE_END = 2, GOAL_END = 12, ERROR = 99; |
| |
| @Override |
| public ReleaseResult prepareWithResult( ReleasePrepareRequest prepareRequest ) |
| { |
| ReleaseResult result = new ReleaseResult(); |
| |
| result.setStartTime( System.currentTimeMillis() ); |
| |
| try |
| { |
| prepare( prepareRequest, result ); |
| |
| result.setResultCode( ReleaseResult.SUCCESS ); |
| } |
| catch ( ReleaseExecutionException | ReleaseFailureException e ) |
| { |
| captureException( result, prepareRequest.getReleaseManagerListener(), e ); |
| } |
| finally |
| { |
| result.setEndTime( System.currentTimeMillis() ); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public void prepare( ReleasePrepareRequest prepareRequest ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| prepare( prepareRequest, new ReleaseResult() ); |
| } |
| |
| private void prepare( ReleasePrepareRequest prepareRequest, ReleaseResult result ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| |
| final ReleaseDescriptorBuilder builder = prepareRequest.getReleaseDescriptorBuilder(); |
| |
| // Create a config containing values from the session properties (ie command line properties with cli). |
| ReleaseUtils.copyPropertiesToReleaseDescriptor( prepareRequest.getUserProperties(), |
| new ReleaseDescriptorBuilder() |
| { |
| public ReleaseDescriptorBuilder addDevelopmentVersion( String key, |
| String value ) |
| { |
| builder.addDevelopmentVersion( key, value ); |
| return this; |
| } |
| |
| public ReleaseDescriptorBuilder addReleaseVersion( String key, |
| String value ) |
| { |
| builder.addReleaseVersion( key, value ); |
| return this; |
| }; |
| } ); |
| |
| BuilderReleaseDescriptor config; |
| if ( BooleanUtils.isNotFalse( prepareRequest.getResume() ) ) |
| { |
| config = loadReleaseDescriptor( builder, prepareRequest.getReleaseManagerListener() ); |
| } |
| else |
| { |
| config = ReleaseUtils.buildReleaseDescriptor( prepareRequest.getReleaseDescriptorBuilder() ); |
| } |
| |
| Strategy releaseStrategy = getStrategy( config.getReleaseStrategyId() ); |
| |
| List<String> preparePhases = getGoalPhases( releaseStrategy, "prepare" ); |
| |
| goalStart( prepareRequest.getReleaseManagerListener(), "prepare", preparePhases ); |
| |
| // Later, it would be a good idea to introduce a proper workflow tool so that the release can be made up of a |
| // more flexible set of steps. |
| |
| String completedPhase = config.getCompletedPhase(); |
| int index = preparePhases.indexOf( completedPhase ); |
| |
| for ( int idx = 0; idx <= index; idx++ ) |
| { |
| updateListener( prepareRequest.getReleaseManagerListener(), preparePhases.get( idx ), PHASE_SKIP ); |
| } |
| |
| if ( index == preparePhases.size() - 1 ) |
| { |
| logInfo( result, "Release preparation already completed. You can now continue with release:perform, " |
| + "or start again using the -Dresume=false flag" ); |
| } |
| else if ( index >= 0 ) |
| { |
| logInfo( result, "Resuming release from phase '" + preparePhases.get( index + 1 ) + "'" ); |
| } |
| |
| // start from next phase |
| for ( int i = index + 1; i < preparePhases.size(); i++ ) |
| { |
| String name = preparePhases.get( i ); |
| |
| ReleasePhase phase = releasePhases.get( name ); |
| |
| if ( phase == null ) |
| { |
| throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" ); |
| } |
| |
| updateListener( prepareRequest.getReleaseManagerListener(), name, PHASE_START ); |
| |
| ReleaseResult phaseResult = null; |
| try |
| { |
| if ( BooleanUtils.isTrue( prepareRequest.getDryRun() ) ) |
| { |
| phaseResult = phase.simulate( config, |
| prepareRequest.getReleaseEnvironment(), |
| prepareRequest.getReactorProjects() ); |
| } |
| else |
| { |
| phaseResult = phase.execute( config, |
| prepareRequest.getReleaseEnvironment(), |
| prepareRequest.getReactorProjects() ); |
| } |
| } |
| finally |
| { |
| if ( result != null && phaseResult != null ) |
| { |
| result.appendOutput( phaseResult.getOutput() ); |
| } |
| } |
| |
| config.setCompletedPhase( name ); |
| try |
| { |
| configStore.write( config ); |
| } |
| catch ( ReleaseDescriptorStoreException e ) |
| { |
| // TODO: rollback? |
| throw new ReleaseExecutionException( "Error writing release properties after completing phase", e ); |
| } |
| |
| updateListener( prepareRequest.getReleaseManagerListener(), name, PHASE_END ); |
| } |
| |
| updateListener( prepareRequest.getReleaseManagerListener(), "prepare", GOAL_END ); |
| } |
| |
| @Override |
| public void rollback( ReleaseRollbackRequest rollbackRequest ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| ReleaseDescriptor releaseDescriptor = |
| loadReleaseDescriptor( rollbackRequest.getReleaseDescriptorBuilder(), null ); |
| |
| Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() ); |
| |
| List<String> rollbackPhases = getGoalPhases( releaseStrategy, "rollback" ); |
| |
| goalStart( rollbackRequest.getReleaseManagerListener(), "rollback", rollbackPhases ); |
| |
| for ( String name : rollbackPhases ) |
| { |
| ReleasePhase phase = releasePhases.get( name ); |
| |
| if ( phase == null ) |
| { |
| throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" ); |
| } |
| |
| updateListener( rollbackRequest.getReleaseManagerListener(), name, PHASE_START ); |
| phase.execute( releaseDescriptor, |
| rollbackRequest.getReleaseEnvironment(), |
| rollbackRequest.getReactorProjects() ); |
| updateListener( rollbackRequest.getReleaseManagerListener(), name, PHASE_END ); |
| } |
| |
| //call release:clean so that resume will not be possible anymore after a rollback |
| clean( rollbackRequest ); |
| updateListener( rollbackRequest.getReleaseManagerListener(), "rollback", GOAL_END ); |
| } |
| |
| @Override |
| public ReleaseResult performWithResult( ReleasePerformRequest performRequest ) |
| { |
| ReleaseResult result = new ReleaseResult(); |
| |
| try |
| { |
| result.setStartTime( System.currentTimeMillis() ); |
| |
| perform( performRequest, result ); |
| |
| result.setResultCode( ReleaseResult.SUCCESS ); |
| } |
| catch ( ReleaseExecutionException | ReleaseFailureException e ) |
| { |
| captureException( result, performRequest.getReleaseManagerListener(), e ); |
| } |
| finally |
| { |
| result.setEndTime( System.currentTimeMillis() ); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public void perform( ReleasePerformRequest performRequest ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| perform( performRequest, new ReleaseResult() ); |
| } |
| |
| private void perform( ReleasePerformRequest performRequest, ReleaseResult result ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| ReleaseDescriptor releaseDescriptor = |
| loadReleaseDescriptor( performRequest.getReleaseDescriptorBuilder(), |
| performRequest.getReleaseManagerListener() ); |
| |
| Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() ); |
| |
| List<String> performPhases = getGoalPhases( releaseStrategy, "perform" ); |
| |
| goalStart( performRequest.getReleaseManagerListener(), "perform", performPhases ); |
| |
| for ( String name : performPhases ) |
| { |
| ReleasePhase phase = releasePhases.get( name ); |
| |
| if ( phase == null ) |
| { |
| throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" ); |
| } |
| |
| updateListener( performRequest.getReleaseManagerListener(), name, PHASE_START ); |
| |
| ReleaseResult phaseResult = null; |
| try |
| { |
| if ( BooleanUtils.isTrue( performRequest.getDryRun() ) ) |
| { |
| phaseResult = phase.simulate( releaseDescriptor, |
| performRequest.getReleaseEnvironment(), |
| performRequest.getReactorProjects() ); |
| } |
| else |
| { |
| phaseResult = phase.execute( releaseDescriptor, |
| performRequest.getReleaseEnvironment(), |
| performRequest.getReactorProjects() ); |
| } |
| } |
| finally |
| { |
| if ( result != null && phaseResult != null ) |
| { |
| result.appendOutput( phaseResult.getOutput() ); |
| } |
| } |
| |
| updateListener( performRequest.getReleaseManagerListener(), name, PHASE_END ); |
| } |
| |
| if ( BooleanUtils.isNotFalse( performRequest.getClean() ) ) |
| { |
| // call release:clean so that resume will not be possible anymore after a perform |
| clean( performRequest ); |
| } |
| |
| updateListener( performRequest.getReleaseManagerListener(), "perform", GOAL_END ); |
| } |
| |
| @Override |
| public void branch( ReleaseBranchRequest branchRequest ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| final ReleaseDescriptorBuilder builder = branchRequest.getReleaseDescriptorBuilder(); |
| |
| ReleaseUtils.copyPropertiesToReleaseDescriptor( branchRequest.getUserProperties(), |
| new ReleaseDescriptorBuilder() |
| { |
| public ReleaseDescriptorBuilder addDevelopmentVersion( String key, |
| String value ) |
| { |
| builder.addDevelopmentVersion( key, value ); |
| return this; |
| } |
| |
| public ReleaseDescriptorBuilder addReleaseVersion( String key, |
| String value ) |
| { |
| builder.addReleaseVersion( key, value ); |
| return this; |
| }; |
| } ); |
| |
| ReleaseDescriptor releaseDescriptor = |
| loadReleaseDescriptor( builder, branchRequest.getReleaseManagerListener() ); |
| |
| boolean dryRun = BooleanUtils.isTrue( branchRequest.getDryRun() ); |
| |
| Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() ); |
| |
| List<String> branchPhases = getGoalPhases( releaseStrategy, "branch" ); |
| |
| goalStart( branchRequest.getReleaseManagerListener(), "branch", branchPhases ); |
| |
| for ( String name : branchPhases ) |
| { |
| ReleasePhase phase = releasePhases.get( name ); |
| |
| if ( phase == null ) |
| { |
| throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" ); |
| } |
| |
| updateListener( branchRequest.getReleaseManagerListener(), name, PHASE_START ); |
| |
| if ( dryRun ) |
| { |
| phase.simulate( releaseDescriptor, |
| branchRequest.getReleaseEnvironment(), |
| branchRequest.getReactorProjects() ); |
| } |
| else // getDryRun is null or FALSE |
| { |
| phase.execute( releaseDescriptor, |
| branchRequest.getReleaseEnvironment(), |
| branchRequest.getReactorProjects() ); |
| } |
| updateListener( branchRequest.getReleaseManagerListener(), name, PHASE_END ); |
| } |
| |
| if ( !dryRun ) |
| { |
| clean( branchRequest ); |
| } |
| |
| updateListener( branchRequest.getReleaseManagerListener(), "branch", GOAL_END ); |
| } |
| |
| @Override |
| public void updateVersions( ReleaseUpdateVersionsRequest updateVersionsRequest ) |
| throws ReleaseExecutionException, ReleaseFailureException |
| { |
| final ReleaseDescriptorBuilder builder = updateVersionsRequest.getReleaseDescriptorBuilder(); |
| |
| // Create a config containing values from the session properties (ie command line properties with cli). |
| ReleaseUtils.copyPropertiesToReleaseDescriptor( updateVersionsRequest.getUserProperties(), |
| new ReleaseDescriptorBuilder() |
| { |
| public ReleaseDescriptorBuilder addDevelopmentVersion( String key, |
| String value ) |
| { |
| builder.addDevelopmentVersion( key, value ); |
| return this; |
| } |
| |
| public ReleaseDescriptorBuilder addReleaseVersion( String key, |
| String value ) |
| { |
| builder.addReleaseVersion( key, value ); |
| return this; |
| }; |
| } ); |
| |
| ReleaseDescriptor releaseDescriptor = |
| loadReleaseDescriptor( builder, updateVersionsRequest.getReleaseManagerListener() ); |
| |
| Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() ); |
| |
| List<String> updateVersionsPhases = getGoalPhases( releaseStrategy, "updateVersions" ); |
| |
| goalStart( updateVersionsRequest.getReleaseManagerListener(), "updateVersions", updateVersionsPhases ); |
| |
| for ( String name : updateVersionsPhases ) |
| { |
| ReleasePhase phase = releasePhases.get( name ); |
| |
| if ( phase == null ) |
| { |
| throw new ReleaseExecutionException( "Unable to find phase '" + name + "' to execute" ); |
| } |
| |
| updateListener( updateVersionsRequest.getReleaseManagerListener(), name, PHASE_START ); |
| phase.execute( releaseDescriptor, |
| updateVersionsRequest.getReleaseEnvironment(), |
| updateVersionsRequest.getReactorProjects() ); |
| updateListener( updateVersionsRequest.getReleaseManagerListener(), name, PHASE_END ); |
| } |
| |
| clean( updateVersionsRequest ); |
| |
| updateListener( updateVersionsRequest.getReleaseManagerListener(), "updateVersions", GOAL_END ); |
| } |
| |
| /** |
| * Determines the path of the working directory. By default, this is the |
| * checkout directory. For some SCMs, the project root directory is not the |
| * checkout directory itself, but a SCM-specific subdirectory. |
| * |
| * @param checkoutDirectory The checkout directory as java.io.File |
| * @param relativePathProjectDirectory The relative path of the project directory within the checkout |
| * directory or "" |
| * @return The working directory |
| */ |
| protected File determineWorkingDirectory( File checkoutDirectory, String relativePathProjectDirectory ) |
| { |
| if ( StringUtils.isNotEmpty( relativePathProjectDirectory ) ) |
| { |
| return new File( checkoutDirectory, relativePathProjectDirectory ); |
| } |
| else |
| { |
| return checkoutDirectory; |
| } |
| } |
| |
| private BuilderReleaseDescriptor loadReleaseDescriptor( ReleaseDescriptorBuilder builder, |
| ReleaseManagerListener listener ) |
| throws ReleaseExecutionException |
| { |
| try |
| { |
| updateListener( listener, "verify-release-configuration", PHASE_START ); |
| BuilderReleaseDescriptor descriptor = ReleaseUtils.buildReleaseDescriptor( configStore.read( builder ) ); |
| updateListener( listener, "verify-release-configuration", PHASE_END ); |
| return descriptor; |
| } |
| catch ( ReleaseDescriptorStoreException e ) |
| { |
| updateListener( listener, e.getMessage(), ERROR ); |
| |
| throw new ReleaseExecutionException( "Error reading stored configuration: " + e.getMessage(), e ); |
| } |
| } |
| |
| |
| protected void clean( AbstractReleaseRequest releaseRequest ) throws ReleaseFailureException |
| { |
| ReleaseCleanRequest cleanRequest = new ReleaseCleanRequest(); |
| cleanRequest.setReleaseDescriptorBuilder( releaseRequest.getReleaseDescriptorBuilder() ); |
| cleanRequest.setReleaseManagerListener( releaseRequest.getReleaseManagerListener() ); |
| cleanRequest.setReactorProjects( releaseRequest.getReactorProjects() ); |
| |
| clean( cleanRequest ); |
| } |
| |
| @Override |
| public void clean( ReleaseCleanRequest cleanRequest ) throws ReleaseFailureException |
| { |
| updateListener( cleanRequest.getReleaseManagerListener(), "cleanup", PHASE_START ); |
| |
| getLogger().info( "Cleaning up after release..." ); |
| |
| ReleaseDescriptor releaseDescriptor = |
| ReleaseUtils.buildReleaseDescriptor( cleanRequest.getReleaseDescriptorBuilder() ); |
| |
| configStore.delete( releaseDescriptor ); |
| |
| Strategy releaseStrategy = getStrategy( releaseDescriptor.getReleaseStrategyId() ); |
| |
| Set<String> phases = new LinkedHashSet<>(); |
| phases.addAll( getGoalPhases( releaseStrategy, "prepare" ) ); |
| phases.addAll( getGoalPhases( releaseStrategy, "branch" ) ); |
| |
| for ( String name : phases ) |
| { |
| ReleasePhase phase = releasePhases.get( name ); |
| |
| if ( phase instanceof ResourceGenerator ) |
| { |
| ( (ResourceGenerator) phase ).clean( cleanRequest.getReactorProjects() ); |
| } |
| } |
| |
| updateListener( cleanRequest.getReleaseManagerListener(), "cleanup", PHASE_END ); |
| } |
| |
| void setConfigStore( ReleaseDescriptorStore configStore ) |
| { |
| this.configStore = configStore; |
| } |
| |
| void goalStart( ReleaseManagerListener listener, String goal, List<String> phases ) |
| { |
| if ( listener != null ) |
| { |
| listener.goalStart( goal, phases ); |
| } |
| } |
| |
| void updateListener( ReleaseManagerListener listener, String name, int state ) |
| { |
| if ( listener != null ) |
| { |
| switch ( state ) |
| { |
| case GOAL_END: |
| listener.goalEnd(); |
| break; |
| case PHASE_SKIP: |
| listener.phaseSkip( name ); |
| break; |
| case PHASE_START: |
| listener.phaseStart( name ); |
| break; |
| case PHASE_END: |
| listener.phaseEnd(); |
| break; |
| default: |
| listener.error( name ); |
| } |
| } |
| } |
| |
| private Strategy getStrategy( String strategyId ) throws ReleaseFailureException |
| { |
| Strategy strategy = strategies.get( strategyId ); |
| if ( strategy == null ) |
| { |
| throw new ReleaseFailureException( "Unknown strategy: " + strategyId ); |
| } |
| return strategy; |
| } |
| |
| private List<String> getGoalPhases( Strategy strategy, String goal ) |
| { |
| List<String> phases; |
| |
| if ( "prepare".equals( goal ) ) |
| { |
| phases = strategy.getPreparePhases(); |
| if ( phases == null ) |
| { |
| phases = strategies.get( "default" ).getPreparePhases(); |
| } |
| } |
| else if ( "perform".equals( goal ) ) |
| { |
| phases = strategy.getPerformPhases(); |
| if ( phases == null ) |
| { |
| phases = strategies.get( "default" ).getPerformPhases(); |
| } |
| } |
| else if ( "rollback".equals( goal ) ) |
| { |
| phases = strategy.getRollbackPhases(); |
| if ( phases == null ) |
| { |
| phases = strategies.get( "default" ).getRollbackPhases(); |
| } |
| } |
| else if ( "branch".equals( goal ) ) |
| { |
| phases = strategy.getBranchPhases(); |
| if ( phases == null ) |
| { |
| phases = strategies.get( "default" ).getBranchPhases(); |
| } |
| } |
| else if ( "updateVersions".equals( goal ) ) |
| { |
| phases = strategy.getUpdateVersionsPhases(); |
| if ( phases == null ) |
| { |
| phases = strategies.get( "default" ).getUpdateVersionsPhases(); |
| } |
| } |
| else |
| { |
| phases = null; |
| } |
| |
| return Collections.unmodifiableList( phases ); |
| } |
| |
| private void logInfo( ReleaseResult result, String message ) |
| { |
| if ( result != null ) |
| { |
| result.appendInfo( message ); |
| } |
| |
| getLogger().info( message ); |
| } |
| |
| private void captureException( ReleaseResult result, ReleaseManagerListener listener, Exception e ) |
| { |
| updateListener( listener, e.getMessage(), ERROR ); |
| |
| result.appendError( e ); |
| |
| result.setResultCode( ReleaseResult.ERROR ); |
| } |
| } |