/* * Copyright 2012-2013 the original author or authors. * * Licensed 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. */ package at.nonblocking.maven.nonsnapshot; import at.nonblocking.maven.nonsnapshot.exception.NonSnapshotDependencyResolverException; import at.nonblocking.maven.nonsnapshot.exception.NonSnapshotPluginException; import at.nonblocking.maven.nonsnapshot.model.MavenArtifact; import at.nonblocking.maven.nonsnapshot.model.MavenModule; import at.nonblocking.maven.nonsnapshot.model.MavenModuleDependency; import at.nonblocking.maven.nonsnapshot.model.UpdatedUpstreamMavenArtifact; import org.apache.maven.model.Model; import org.apache.maven.plugins.annotations.Mojo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Main Goal of this Plugin. * <br><br> * Checks the version number and manipulates it if necessary (if changes were found). * <br> * Updates the version of upstream modules. * <br> * Commits the POM files if deferPomCommit is false. * * @author Juergen Kofler */ @Mojo(name = "updateVersions", aggregator = true) public class NonSnapshotUpdateVersionsMojo extends NonSnapshotBaseMojo { private static Logger LOG = LoggerFactory.getLogger(NonSnapshotUpdateVersionsMojo.class); private static String LINE_SEPARATOR = System.getProperty("line.separator"); @Override protected void internalExecute() { List<Model> mavenModels = getModuleTraverser().findAllModules(getMavenProject(), getMavenProject().getActiveProfiles()); List<MavenModule> mavenModules = buildModules(mavenModels); getDependencyTreeProcessor().buildDependencyTree(mavenModules); markDirtyWhenRevisionChangedOrInvalidQualifier(mavenModules); if (getUpstreamDependencies() != null) { updateUpstreamArtifacts(mavenModules); } //Recursively mark artifacts dirty boolean changes = getDependencyTreeProcessor().markAllArtifactsDirtyWithDirtyDependencies(mavenModules); while (changes) { changes = getDependencyTreeProcessor().markAllArtifactsDirtyWithDirtyDependencies(mavenModules); } setNextRevisionOnDirtyArtifacts(mavenModules); dumpArtifactTreeToLog(mavenModules); writeAndCommitArtifacts(mavenModules); } private List<MavenModule> buildModules(List<Model> mavenModels) { List<MavenModule> mavenModules = new ArrayList<>(); for (Model model : mavenModels) { MavenModule module = getMavenPomHandler().readArtifact(model); mavenModules.add(module); } return mavenModules; } protected void writeAndCommitArtifacts(List<MavenModule> mavenModules) { List<File> pomsToCommit = new ArrayList<>(); for (MavenModule mavenModule : mavenModules) { if (mavenModule.isDirty() && mavenModule.getNewVersion() != null) { getMavenPomHandler().updateArtifact(mavenModule); LOG.debug("Add module to dirty registry list: {}", mavenModule.getPomFile().getAbsolutePath()); pomsToCommit.add(mavenModule.getPomFile()); } } if (isGenerateChangedProjectsPropertyFile()) { generateChangedProjectsPropertyFile(pomsToCommit); } if (pomsToCommit.size() > 0) { writeDirtyModulesRegistry(pomsToCommit); if (isGenerateIncrementalBuildScripts()) { generateIncrementalBuildScripts(pomsToCommit); } if (!isDeferPomCommit()) { LOG.info("Committing {} POM files", pomsToCommit.size()); getScmHandler().commitFiles(pomsToCommit, ScmHandler.NONSNAPSHOT_COMMIT_MESSAGE_PREFIX + " Version of " + pomsToCommit.size() + " artifacts updated"); } else { LOG.info("Deferring the POM commit. Execute nonsnapshot:commit to actually commit the changes."); } } else { LOG.info("Modules are up-to-date. No versions updated."); } } private void markDirtyWhenRevisionChangedOrInvalidQualifier(List<MavenModule> mavenModules) { for (MavenModule mavenModule : mavenModules) { if (mavenModule.getVersion() == null) { LOG.info("No version found for artifact {}:{}. Assigning a new version.", mavenModule.getGroupId(), mavenModule.getArtifactId()); mavenModule.setDirty(true); } else if (mavenModule.getVersion().startsWith("${")) { LOG.info("Version property found for artifact {}:{}. Assigning a new version.", mavenModule.getGroupId(), mavenModule.getArtifactId()); mavenModule.setDirty(true); } else { String[] versionParts = mavenModule.getVersion().split("-"); String qualifierString = null; if (versionParts.length > 1) { qualifierString = versionParts[versionParts.length - 1]; } if (qualifierString == null) { LOG.info("Invalid qualifier string found for artifact {}:{}: {}. Assigning a new version.", new Object[]{mavenModule.getGroupId(), mavenModule.getArtifactId(), mavenModule.getVersion()}); mavenModule.setDirty(true); } else if (qualifierString.equals("SNAPSHOT")) { LOG.info("Snapshot version found for artifact {}:{}. Assigning a new version.", mavenModule.getGroupId(), mavenModule.getArtifactId()); mavenModule.setDirty(true); } else { if (getScmType() == SCM_TYPE.SVN && isUseSvnRevisionQualifier()) { try { long currentRev = getScmHandler().getCurrentRevisionId(mavenModule.getPomFile().getParentFile()); long revFromQualifier = Long.parseLong(qualifierString); if (revFromQualifier != currentRev && getScmHandler().checkChangesSinceRevision(mavenModule.getPomFile().getParentFile(), revFromQualifier, currentRev)) { LOG.info("Module {}:{}: There were commits after the revision number in the version qualifier. Assigning a new version.", mavenModule.getGroupId(), mavenModule.getArtifactId()); mavenModule.setDirty(true); } } catch (NumberFormatException e) { LOG.warn("Invalid SVN revision: {}", qualifierString); mavenModule.setDirty(true); } } else { //Default: compare timestamps try { DateFormat dateFormat = new SimpleDateFormat(getTimestampQualifierPattern()); Date dateFromQualifier = dateFormat.parse(qualifierString); Date lastCommitDate = dateFormat.parse(dateFormat.format(getScmHandler().getLastCommitDate(mavenModule.getPomFile().getParentFile()))); if (!dateFromQualifier.equals(lastCommitDate) && getScmHandler().checkChangesSinceDate(mavenModule.getPomFile().getParentFile(), dateFromQualifier, lastCommitDate)) { LOG.info("Module {}:{}: There were commits after the timestamp in the version qualifier. Assigning a new version.", mavenModule.getGroupId(), mavenModule.getArtifactId()); mavenModule.setDirty(true); } } catch (ParseException e) { LOG.debug("Module {}:{}: Invalid timestamp qualifier: {}", new Object[]{mavenModule.getGroupId(), mavenModule.getArtifactId(), qualifierString}); mavenModule.setDirty(true); } } } } } } private void updateUpstreamArtifacts(List<MavenModule> mavenModules) { for (MavenModule mavenModule : mavenModules) { //Parent if (mavenModule.getParent() != null) { UpdatedUpstreamMavenArtifact updatedUpstreamMavenArtifactParent = updateUpstreamArtifact(mavenModule.getParent()); if (updatedUpstreamMavenArtifactParent != null) { mavenModule.setParent(updatedUpstreamMavenArtifactParent); } } //Dependencies for (MavenModuleDependency moduleDependency : mavenModule.getDependencies()) { UpdatedUpstreamMavenArtifact updatedUpstreamMavenArtifactDep = updateUpstreamArtifact(moduleDependency.getArtifact()); if (updatedUpstreamMavenArtifactDep != null) { moduleDependency.setArtifact(updatedUpstreamMavenArtifactDep); } } } } private UpdatedUpstreamMavenArtifact updateUpstreamArtifact(MavenArtifact upstreamArtifact) { if (!(upstreamArtifact instanceof MavenModule)) { ProcessedUpstreamDependency upstreamDependency = getUpstreamDependencyHandler().findMatch(upstreamArtifact, getProcessedUpstreamDependencies()); if (upstreamDependency != null) { LOG.debug("Upstream dependency found: {}:{}", upstreamArtifact.getGroupId(), upstreamArtifact.getArtifactId()); try { String latestVersion = getUpstreamDependencyHandler().resolveLatestVersion(upstreamArtifact, upstreamDependency, getRepositorySystem(), getRepositorySystemSession(), getRemoteRepositories()); if (latestVersion != null) { LOG.info("Found newer version for upstream dependency {}:{}: {}", new Object[]{upstreamArtifact.getGroupId(), upstreamArtifact.getArtifactId(), latestVersion}); return new UpdatedUpstreamMavenArtifact(upstreamArtifact.getGroupId(), upstreamArtifact.getArtifactId(), upstreamArtifact.getVersion(), latestVersion); } } catch (NonSnapshotDependencyResolverException e) { if (isDontFailOnUpstreamVersionResolution()) { LOG.warn("Upstream dependency resolution failed (cannot update {}:{}). Error: {}", new Object[]{upstreamArtifact.getGroupId(), upstreamArtifact.getArtifactId(), e.getMessage()}); } else { throw e; } } } } return null; } private void setNextRevisionOnDirtyArtifacts(List<MavenModule> mavenModules) { for (MavenModule mavenModule : mavenModules) { File modulesPath = mavenModule.getPomFile().getParentFile(); if (mavenModule.isDirty()) { if (!getScmHandler().isWorkingCopy(modulesPath)) { throw new NonSnapshotPluginException("Module path is no working directory: " + modulesPath); } if (isUseSvnRevisionQualifier()) { mavenModule.setNewVersion(getBaseVersion() + "-" + getScmHandler().getCurrentRevisionId(modulesPath)); } else { mavenModule.setNewVersion(getBaseVersion() + "-" + new SimpleDateFormat(getTimestampQualifierPattern()).format(getScmHandler().getLastCommitDate(modulesPath))); } } } } private void writeDirtyModulesRegistry(List<File> pomFileList) { File dirtyModulesRegistryFile = getDirtyModulesRegistryFile(); LOG.info("Writing dirty modules registry to: {}", dirtyModulesRegistryFile.getAbsolutePath()); try (PrintWriter writer = new PrintWriter(new FileOutputStream(dirtyModulesRegistryFile, false))) { for (File pomFile : pomFileList) { String relativeModuleDir = PathUtil.relativePath(getMavenProject().getBasedir(), pomFile.getParentFile()); if (relativeModuleDir.isEmpty()) { relativeModuleDir = "."; } writer.write(relativeModuleDir + LINE_SEPARATOR); } } catch (IOException e) { throw new NonSnapshotPluginException("Failed to write text file with POMs to commit!", e); } } private void generateIncrementalBuildScripts(List<File> pomFileList) { String projectPaths = createProjectPathsString(pomFileList); if (isWindows()) { File batFile = new File(getMavenProject().getBasedir(), "nonsnapshotBuildIncremental.bat"); LOG.info("Writing windows batch script for incremental build to: {}", batFile.getAbsolutePath()); try (PrintWriter writer = new PrintWriter(batFile)) { writer.write("@ECHO OFF\n"); writer.write("REM Incremental build script generated by nonsnapshot-maven-plugin\n"); writer.write("REM To install all modified modules call:\n"); writer.write("REM nonsnapshotBuildIncremental.bat install\n\n"); writer.write("SET MVN_EXEC=mvn.bat\n"); writer.write("IF DEFINED M2_HOME (set MVN_EXEC=%M2_HOME%\\bin\\mvn.bat)\n"); writer.write("ECHO Using maven executable: %MVN_EXEC%\n"); writer.write("%MVN_EXEC% --projects " + projectPaths + " %*"); writer.close(); } catch (IOException e) { LOG.error("Failed to write windows batch script for incremental build!", e); } } else { File shellFile = new File(getMavenProject().getBasedir(), "nonsnapshotBuildIncremental.sh"); LOG.info("Writing unix shell script for incremental build to: {}", shellFile.getAbsolutePath()); try (PrintWriter writer = new PrintWriter(shellFile)) { writer.write("#!/bin/sh\n"); writer.write("# Incremental build script generated by nonsnapshot-maven-plugin\n"); writer.write("# To install all modified modules call:\n"); writer.write("# ./nonsnapshotBuildIncremental.sh install\n\n"); writer.write("MVN_EXEC=mvn\n"); writer.write("if [ ! -z \"$M2_HOME\" ]; then\n"); writer.write(" MVN_EXEC=$M2_HOME/bin/mvn\n"); writer.write("fi\n"); writer.write("echo \"Using maven executable: $MVN_EXEC\"\n"); writer.write("$MVN_EXEC --projects " + projectPaths + " $@"); writer.close(); Runtime.getRuntime().exec("chmod u+x " + shellFile.getAbsolutePath()); } catch (IOException e) { LOG.error("Failed to write windows batch script for incremental build!", e); } } } private void generateChangedProjectsPropertyFile(List<File> pomFileList) { String projectPaths = createProjectPathsString(pomFileList); if (projectPaths.isEmpty()) { projectPaths = "."; //An empty property wont work on Jenkins } File propertyFile = new File(getMavenProject().getBasedir(), "nonsnapshotChangedProjects.properties"); LOG.info("Writing changed projects to property file: {}", propertyFile.getAbsolutePath()); try (PrintWriter writer = new PrintWriter(propertyFile)) { writer.write("#Property with changed projects generated by nonsnapshot-maven-plugin\n"); writer.write("#Can be used together with the Jenkins EnvInject plugin to build changed projects only:\n"); writer.write("#mvn --projects ${nonsnapshot.changed.projects} install\n"); writer.write("nonsnapshot.changed.projects=" + projectPaths + "\n"); writer.close(); } catch (IOException e) { LOG.error("Failed to write changed projects property file!", e); } } private String createProjectPathsString(List<File> pomFileList) { StringBuilder projectPaths = new StringBuilder(); try { for (File pomFile : pomFileList) { String relativeModuleDir = PathUtil.relativePath(getMavenProject().getBasedir(), pomFile.getParentFile()); if (relativeModuleDir.isEmpty()) { relativeModuleDir = "."; } if (projectPaths.length() != 0) { relativeModuleDir = "," + relativeModuleDir; } projectPaths.append(relativeModuleDir); } return projectPaths.toString(); } catch (IOException e) { throw new NonSnapshotPluginException("Failed determine changed project paths!", e); } } private boolean isWindows() { return System.getProperty("os.name").toLowerCase().contains("win"); } private void dumpArtifactTreeToLog(List<MavenModule> modules) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); getDependencyTreeProcessor().printMavenModulesTree(modules, new PrintStream(baos)); LOG.info("\n" + baos.toString()); } }