001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.release.plugin.mojos; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.PrintWriter; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Locale; 029import java.util.Set; 030 031import org.apache.commons.codec.digest.DigestUtils; 032import org.apache.commons.collections4.properties.SortedProperties; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.release.plugin.SharedFunctions; 035import org.apache.maven.artifact.Artifact; 036import org.apache.maven.plugin.AbstractMojo; 037import org.apache.maven.plugin.MojoExecutionException; 038import org.apache.maven.plugins.annotations.LifecyclePhase; 039import org.apache.maven.plugins.annotations.Mojo; 040import org.apache.maven.plugins.annotations.Parameter; 041import org.apache.maven.project.MavenProject; 042 043/** 044 * The purpose of this Maven mojo is to detach the artifacts generated by the maven-assembly-plugin, 045 * which for the Apache Commons Project do not get uploaded to Nexus, and putting those artifacts 046 * in the dev distribution location for Apache projects. 047 * 048 * @author chtompki 049 * @since 1.0 050 */ 051@Mojo(name = "detach-distributions", 052 defaultPhase = LifecyclePhase.VERIFY, 053 threadSafe = true, 054 aggregator = true) 055public class CommonsDistributionDetachmentMojo extends AbstractMojo { 056 057 /** 058 * A list of "artifact types" in the Maven vernacular, to 059 * be detached from the deployment. For the time being we want 060 * all artifacts generated by the maven-assembly-plugin to be detached 061 * from the deployment, namely *-src.zip, *-src.tar.gz, *-bin.zip, 062 * *-bin.tar.gz, and the corresponding .asc pgp signatures. 063 */ 064 private static final Set<String> ARTIFACT_TYPES_TO_DETACH; 065 static { 066 final Set<String> hashSet = new HashSet<>(); 067 hashSet.add("zip"); 068 hashSet.add("tar.gz"); 069 hashSet.add("zip.asc"); 070 hashSet.add("tar.gz.asc"); 071 ARTIFACT_TYPES_TO_DETACH = Collections.unmodifiableSet(hashSet); 072 } 073 074 /** 075 * This list is supposed to hold the Maven references to the aforementioned artifacts so that we 076 * can upload them to svn after they've been detached from the Maven deployment. 077 */ 078 private final List<Artifact> detachedArtifacts = new ArrayList<>(); 079 080 /** 081 * A {@link SortedProperties} of {@link Artifact} → {@link String} containing the sha512 signatures 082 * for the individual artifacts, where the {@link Artifact} is represented as: 083 * <code>groupId:artifactId:version:type=sha512</code>. 084 */ 085 private final SortedProperties artifactSha512s = new SortedProperties(); 086 087 /** 088 * The maven project context injection so that we can get a hold of the variables at hand. 089 */ 090 @Parameter(defaultValue = "${project}", required = true) 091 private MavenProject project; 092 093 /** 094 * The working directory in <code>target</code> that we use as a sandbox for the plugin. 095 */ 096 @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin", 097 property = "commons.outputDirectory") 098 private File workingDirectory; 099 100 /** 101 * The subversion staging url to which we upload all of our staged artifacts. 102 */ 103 @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl") 104 private String distSvnStagingUrl; 105 106 /** 107 * A parameter to generally avoid running unless it is specifically turned on by the consuming module. 108 */ 109 @Parameter(defaultValue = "false", property = "commons.release.isDistModule") 110 private Boolean isDistModule; 111 112 @Override 113 public void execute() throws MojoExecutionException { 114 if (!isDistModule) { 115 getLog().info( 116 "This module is marked as a non distribution or assembly module, and the plugin will not run."); 117 return; 118 } 119 if (StringUtils.isEmpty(distSvnStagingUrl)) { 120 getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run."); 121 return; 122 } 123 getLog().info("Detaching Assemblies"); 124 for (final Object attachedArtifact : project.getAttachedArtifacts()) { 125 putAttachedArtifactInSha512Map((Artifact) attachedArtifact); 126 if (ARTIFACT_TYPES_TO_DETACH.contains(((Artifact) attachedArtifact).getType())) { 127 detachedArtifacts.add((Artifact) attachedArtifact); 128 } 129 } 130 if (detachedArtifacts.isEmpty()) { 131 getLog().info("Current project contains no distributions. Not executing."); 132 return; 133 } 134 for (final Artifact artifactToRemove : detachedArtifacts) { 135 project.getAttachedArtifacts().remove(artifactToRemove); 136 } 137 if (!workingDirectory.exists()) { 138 SharedFunctions.initDirectory(getLog(), workingDirectory); 139 } 140 writeAllArtifactsInSha512PropertiesFile(); 141 copyRemovedArtifactsToWorkingDirectory(); 142 getLog().info(""); 143 hashArtifacts(); 144 } 145 146 /** 147 * Takes an attached artifact and puts the signature in the map. 148 * @param artifact is a Maven {@link Artifact} taken from the project at start time of mojo. 149 * @throws MojoExecutionException if an {@link IOException} occurs when getting the sha512 of the 150 * artifact. 151 */ 152 private void putAttachedArtifactInSha512Map(final Artifact artifact) throws MojoExecutionException { 153 try { 154 final String artifactKey = getArtifactKey(artifact); 155 try (FileInputStream fis = new FileInputStream(artifact.getFile())) { 156 artifactSha512s.put(artifactKey, DigestUtils.sha512Hex(fis)); 157 } 158 } catch (final IOException e) { 159 throw new MojoExecutionException( 160 "Could not find artifact signature for: " 161 + artifact.getArtifactId() 162 + "-" 163 + artifact.getClassifier() 164 + "-" 165 + artifact.getVersion() 166 + " type: " 167 + artifact.getType(), 168 e); 169 } 170 } 171 172 /** 173 * Writes to ./target/commons-release-plugin/sha512.properties the artifact sha512's. 174 * 175 * @throws MojoExecutionException if we can't write the file due to an {@link IOException}. 176 */ 177 private void writeAllArtifactsInSha512PropertiesFile() throws MojoExecutionException { 178 final File propertiesFile = new File(workingDirectory, "sha512.properties"); 179 getLog().info("Writting " + propertiesFile); 180 try (FileOutputStream fileWriter = new FileOutputStream(propertiesFile)) { 181 artifactSha512s.store(fileWriter, "Release SHA-512s"); 182 } catch (final IOException e) { 183 throw new MojoExecutionException("Failure to write SHA-512's", e); 184 } 185 } 186 187 /** 188 * A helper method to copy the newly detached artifacts to <code>target/commons-release-plugin</code> 189 * so that the {@link CommonsDistributionStagingMojo} can find the artifacts later. 190 * 191 * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it 192 * properly wrapped so that Maven can handle it. 193 */ 194 private void copyRemovedArtifactsToWorkingDirectory() throws MojoExecutionException { 195 final String wdAbsolutePath = workingDirectory.getAbsolutePath(); 196 getLog().info( 197 "Copying " + detachedArtifacts.size() + " detached artifacts to working directory " + wdAbsolutePath); 198 for (final Artifact artifact: detachedArtifacts) { 199 final File artifactFile = artifact.getFile(); 200 final StringBuilder copiedArtifactAbsolutePath = new StringBuilder(wdAbsolutePath); 201 copiedArtifactAbsolutePath.append("/"); 202 copiedArtifactAbsolutePath.append(artifactFile.getName()); 203 final File copiedArtifact = new File(copiedArtifactAbsolutePath.toString()); 204 getLog().info("Copying: " + artifactFile.getName()); 205 SharedFunctions.copyFile(getLog(), artifactFile, copiedArtifact); 206 } 207 } 208 209 /** 210 * A helper method that creates sha512 signature files for our detached artifacts in the 211 * <code>target/commons-release-plugin</code> directory for the purpose of being uploaded by 212 * the {@link CommonsDistributionStagingMojo}. 213 * 214 * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it 215 * properly wrapped so that Maven can handle it. 216 */ 217 private void hashArtifacts() throws MojoExecutionException { 218 for (final Artifact artifact : detachedArtifacts) { 219 if (!artifact.getFile().getName().toLowerCase(Locale.ROOT).contains("asc")) { 220 final String artifactKey = getArtifactKey(artifact); 221 try { 222 String digest; 223 // SHA-512 224 digest = artifactSha512s.getProperty(artifactKey.toString()); 225 getLog().info(artifact.getFile().getName() + " sha512: " + digest); 226 try (PrintWriter printWriter = new PrintWriter( 227 getSha512FilePath(workingDirectory, artifact.getFile()))) { 228 printWriter.println(digest); 229 } 230 } catch (final IOException e) { 231 throw new MojoExecutionException("Could not sign file: " + artifact.getFile().getName(), e); 232 } 233 } 234 } 235 } 236 237 /** 238 * A helper method to create a file path for the <code>sha512</code> signature file from a given file. 239 * 240 * @param directory is the {@link File} for the directory in which to make the <code>.sha512</code> file. 241 * @param file the {@link File} whose name we should use to create the <code>.sha512</code> file. 242 * @return a {@link String} that is the absolute path to the <code>.sha512</code> file. 243 */ 244 private String getSha512FilePath(final File directory, final File file) { 245 final StringBuilder buffer = new StringBuilder(directory.getAbsolutePath()); 246 buffer.append("/"); 247 buffer.append(file.getName()); 248 buffer.append(".sha512"); 249 return buffer.toString(); 250 } 251 252 /** 253 * Generates the unique artifact key for storage in our sha512 map. For example, 254 * commons-test-1.4-src.tar.gz should have it's name as the key. 255 * 256 * @param artifact the {@link Artifact} that we wish to generate a key for. 257 * @return the generated key 258 */ 259 private String getArtifactKey(final Artifact artifact) { 260 return artifact.getFile().getName(); 261 } 262}