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.Properties; 029import java.util.Set; 030 031import org.apache.commons.codec.digest.DigestUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.commons.release.plugin.SharedFunctions; 034import org.apache.maven.artifact.Artifact; 035import org.apache.maven.plugin.AbstractMojo; 036import org.apache.maven.plugin.MojoExecutionException; 037import org.apache.maven.plugins.annotations.LifecyclePhase; 038import org.apache.maven.plugins.annotations.Mojo; 039import org.apache.maven.plugins.annotations.Parameter; 040import org.apache.maven.project.MavenProject; 041 042/** 043 * The purpose of this Maven mojo is to detach the artifacts generated by the maven-assembly-plugin, 044 * which for the Apache Commons Project do not get uploaded to Nexus, and putting those artifacts 045 * in the dev distribution location for Apache projects. 046 * 047 * @author chtompki 048 * @since 1.0 049 */ 050@Mojo(name = "detach-distributions", 051 defaultPhase = LifecyclePhase.VERIFY, 052 threadSafe = true, 053 aggregator = true) 054public class CommonsDistributionDetachmentMojo extends AbstractMojo { 055 056 /** 057 * A list of "artifact types" in the Maven vernacular, to 058 * be detached from the deployment. For the time being we want 059 * all artifacts generated by the maven-assembly-plugin to be detached 060 * from the deployment, namely *-src.zip, *-src.tar.gz, *-bin.zip, 061 * *-bin.tar.gz, and the corresponding .asc pgp signatures. 062 */ 063 private static final Set<String> ARTIFACT_TYPES_TO_DETACH; 064 static { 065 Set<String> hashSet = new HashSet<>(); 066 hashSet.add("zip"); 067 hashSet.add("tar.gz"); 068 hashSet.add("zip.asc"); 069 hashSet.add("tar.gz.asc"); 070 ARTIFACT_TYPES_TO_DETACH = Collections.unmodifiableSet(hashSet); 071 } 072 073 /** 074 * This list is supposed to hold the Maven references to the aforementioned artifacts so that we 075 * can upload them to svn after they've been detached from the Maven deployment. 076 */ 077 private final List<Artifact> detachedArtifacts = new ArrayList<>(); 078 079 /** 080 * A {@link Properties} of {@link Artifact} → {@link String} containing the sha1 signatures 081 * for the individual artifacts, where the {@link Artifact} is represented as: 082 * <code>groupId:artifactId:version:type=sha1</code>. 083 */ 084 private final Properties artifactSha1s = new Properties(); 085 086 /** 087 * A {@link Properties} of {@link Artifact} → {@link String} containing the sha256 signatures 088 * for the individual artifacts, where the {@link Artifact} is represented as: 089 * <code>groupId:artifactId:version:type=sha1</code>. 090 */ 091 private final Properties artifactSha256s = new Properties(); 092 093 /** 094 * The maven project context injection so that we can get a hold of the variables at hand. 095 */ 096 @Parameter(defaultValue = "${project}", required = true) 097 private MavenProject project; 098 099 /** 100 * The working directory in <code>target</code> that we use as a sandbox for the plugin. 101 */ 102 @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin", 103 property = "commons.outputDirectory") 104 private File workingDirectory; 105 106 /** 107 * The subversion staging url to which we upload all of our staged artifacts. 108 */ 109 @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl") 110 private String distSvnStagingUrl; 111 112 /** 113 * A parameter to generally avoid running unless it is specifically turned on by the consuming module. 114 */ 115 @Parameter(defaultValue = "false", property = "commons.release.isDistModule") 116 private Boolean isDistModule; 117 118 @Override 119 public void execute() throws MojoExecutionException { 120 if (!isDistModule) { 121 getLog().info("This module is marked as a non distribution " 122 + "or assembly module, and the plugin will not run."); 123 return; 124 } 125 if (StringUtils.isEmpty(distSvnStagingUrl)) { 126 getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run."); 127 return; 128 } 129 getLog().info("Detaching Assemblies"); 130 for (Object attachedArtifact : project.getAttachedArtifacts()) { 131 putAttachedArtifactInSha1Map((Artifact) attachedArtifact); 132 putAttachedArtifactInSha256Map((Artifact) attachedArtifact); 133 if (ARTIFACT_TYPES_TO_DETACH.contains(((Artifact) attachedArtifact).getType())) { 134 detachedArtifacts.add((Artifact) attachedArtifact); 135 } 136 } 137 if (detachedArtifacts.isEmpty()) { 138 getLog().info("Current project contains no distributions. Not executing."); 139 return; 140 } 141 for (Artifact artifactToRemove : detachedArtifacts) { 142 project.getAttachedArtifacts().remove(artifactToRemove); 143 } 144 if (!workingDirectory.exists()) { 145 SharedFunctions.initDirectory(getLog(), workingDirectory); 146 } 147 writeAllArtifactsInSha1PropertiesFile(); 148 writeAllArtifactsInSha256PropertiesFile(); 149 copyRemovedArtifactsToWorkingDirectory(); 150 getLog().info(""); 151 hashArtifacts(); 152 } 153 154 /** 155 * Takes an attached artifact and puts the signature in the map. 156 * @param artifact is a Maven {@link Artifact} taken from the project at start time of mojo. 157 * @throws MojoExecutionException if an {@link IOException} occurs when getting the sha1 of the 158 * artifact. 159 */ 160 private void putAttachedArtifactInSha1Map(Artifact artifact) throws MojoExecutionException { 161 try { 162 String artifactKey = getArtifactKey(artifact); 163 try (FileInputStream fis = new FileInputStream(artifact.getFile())) { 164 artifactSha1s.put(artifactKey, DigestUtils.sha1Hex(fis)); 165 } 166 } catch (IOException e) { 167 throw new MojoExecutionException( 168 "Could not find artifact signature for: " 169 + artifact.getArtifactId() 170 + "-" 171 + artifact.getClassifier() 172 + "-" 173 + artifact.getVersion() 174 + " type: " 175 + artifact.getType(), 176 e); 177 } 178 } 179 180 /** 181 * Takes an attached artifact and puts the signature in the map. 182 * @param artifact is a Maven {@link Artifact} taken from the project at start time of mojo. 183 * @throws MojoExecutionException if an {@link IOException} occurs when getting the sha1 of the 184 * artifact. 185 */ 186 private void putAttachedArtifactInSha256Map(Artifact artifact) throws MojoExecutionException { 187 try { 188 String artifactKey = getArtifactKey(artifact); 189 try (FileInputStream fis = new FileInputStream(artifact.getFile())) { 190 artifactSha256s.put(artifactKey, DigestUtils.sha256Hex(fis)); 191 } 192 } catch (IOException e) { 193 throw new MojoExecutionException( 194 "Could not find artifact signature for: " 195 + artifact.getArtifactId() 196 + "-" 197 + artifact.getClassifier() 198 + "-" 199 + artifact.getVersion() 200 + " type: " 201 + artifact.getType(), 202 e); 203 } 204 } 205 206 /** 207 * Writes to ./target/commons-release-plugin/sha1.properties the artifact sha1's. 208 * 209 * @throws MojoExecutionException if we can't write the file due to an {@link IOException}. 210 */ 211 private void writeAllArtifactsInSha1PropertiesFile() throws MojoExecutionException { 212 File propertiesFile = new File(workingDirectory, "sha1.properties"); 213 getLog().info("Writting " + propertiesFile); 214 try (FileOutputStream fileWriter = new FileOutputStream(propertiesFile)) { 215 artifactSha1s.store(fileWriter, "Release SHA-1s"); 216 } catch (IOException e) { 217 throw new MojoExecutionException("Failure to write SHA-1's", e); 218 } 219 } 220 221 /** 222 * Writes to ./target/commons-release-plugin/sha256.properties the artifact sha256's. 223 * 224 * @throws MojoExecutionException if we can't write the file due to an {@link IOException}. 225 */ 226 private void writeAllArtifactsInSha256PropertiesFile() throws MojoExecutionException { 227 File propertiesFile = new File(workingDirectory, "sha256.properties"); 228 getLog().info("Writting " + propertiesFile); 229 try (FileOutputStream fileWriter = new FileOutputStream(propertiesFile)) { 230 artifactSha256s.store(fileWriter, "Release SHA-256s"); 231 } catch (IOException e) { 232 throw new MojoExecutionException("Failure to write SHA-256's", e); 233 } 234 } 235 236 /** 237 * A helper method to copy the newly detached artifacts to <code>target/commons-release-plugin</code> 238 * so that the {@link CommonsDistributionStagingMojo} can find the artifacts later. 239 * 240 * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it 241 * properly wrapped so that Maven can handle it. 242 */ 243 private void copyRemovedArtifactsToWorkingDirectory() throws MojoExecutionException { 244 StringBuffer copiedArtifactAbsolutePath; 245 final String wdAbsolutePath = workingDirectory.getAbsolutePath(); 246 getLog().info( 247 "Copying " + detachedArtifacts.size() + " detached artifacts to working directory " + wdAbsolutePath); 248 for (Artifact artifact: detachedArtifacts) { 249 File artifactFile = artifact.getFile(); 250 copiedArtifactAbsolutePath = new StringBuffer(wdAbsolutePath); 251 copiedArtifactAbsolutePath.append("/"); 252 copiedArtifactAbsolutePath.append(artifactFile.getName()); 253 File copiedArtifact = new File(copiedArtifactAbsolutePath.toString()); 254 getLog().info("Copying: " + artifactFile.getName()); 255 SharedFunctions.copyFile(getLog(), artifactFile, copiedArtifact); 256 } 257 } 258 259 /** 260 * A helper method that creates md5, sha1, and sha256 signature files for our detached artifacts in the 261 * <code>target/commons-release-plugin</code> directory for the purpose of being uploaded by 262 * the {@link CommonsDistributionStagingMojo}. 263 * 264 * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it 265 * properly wrapped so that Maven can handle it. 266 */ 267 private void hashArtifacts() throws MojoExecutionException { 268 for (Artifact artifact : detachedArtifacts) { 269 if (!artifact.getFile().getName().contains("asc")) { 270 String artifactKey = getArtifactKey(artifact); 271 try { 272 String digest; 273 // SHA-1 274 digest = artifactSha1s.getProperty(artifactKey.toString()); 275 getLog().info(artifact.getFile().getName() + " sha1: " + digest); 276 try (PrintWriter printWriter = new PrintWriter( 277 getSha1FilePath(workingDirectory, artifact.getFile()))) { 278 printWriter.println(digest); 279 } 280 // SHA-256 281 digest = artifactSha256s.getProperty(artifactKey.toString()); 282 getLog().info(artifact.getFile().getName() + " sha256: " + digest); 283 try (PrintWriter printWriter = new PrintWriter( 284 getSha256FilePath(workingDirectory, artifact.getFile()))) { 285 printWriter.println(digest); 286 } 287 } catch (IOException e) { 288 throw new MojoExecutionException("Could not sign file: " + artifact.getFile().getName(), e); 289 } 290 } 291 } 292 } 293 294 /** 295 * A helper method to create a file path for the <code>md5</code> signature file from a given file. 296 * 297 * @param directory is the {@link File} for the directory in which to make the <code>.md5</code> file. 298 * @param file the {@link File} whose name we should use to create the <code>.md5</code> file. 299 * @return a {@link String} that is the absolute path to the <code>.md5</code> file. 300 */ 301 private String getMd5FilePath(File directory, File file) { 302 StringBuffer buffer = new StringBuffer(directory.getAbsolutePath()); 303 buffer.append("/"); 304 buffer.append(file.getName()); 305 buffer.append(".md5"); 306 return buffer.toString(); 307 } 308 309 /** 310 * A helper method to create a file path for the <code>sha1</code> signature file from a given file. 311 * 312 * @param directory is the {@link File} for the directory in which to make the <code>.sha1</code> file. 313 * @param file the {@link File} whose name we should use to create the <code>.sha1</code> file. 314 * @return a {@link String} that is the absolute path to the <code>.sha1</code> file. 315 */ 316 private String getSha1FilePath(File directory, File file) { 317 StringBuffer buffer = new StringBuffer(directory.getAbsolutePath()); 318 buffer.append("/"); 319 buffer.append(file.getName()); 320 buffer.append(".sha1"); 321 return buffer.toString(); 322 } 323 324 /** 325 * A helper method to create a file path for the <code>sha1</code> signature file from a given file. 326 * 327 * @param directory is the {@link File} for the directory in which to make the <code>.sha1</code> file. 328 * @param file the {@link File} whose name we should use to create the <code>.sha1</code> file. 329 * @return a {@link String} that is the absolute path to the <code>.sha1</code> file. 330 */ 331 private String getSha256FilePath(File directory, File file) { 332 StringBuffer buffer = new StringBuffer(directory.getAbsolutePath()); 333 buffer.append("/"); 334 buffer.append(file.getName()); 335 buffer.append(".sha256"); 336 return buffer.toString(); 337 } 338 339 /** 340 * Generates the unique artifact key for storage in our sha1 map and sha256 map. For example, 341 * commons-test-1.4-src.tar.gz should have it's name as the key. 342 * 343 * @param artifact the {@link Artifact} that we wish to generate a key for. 344 * @return the generated key 345 */ 346 private String getArtifactKey(Artifact artifact) { 347 StringBuffer artifactKey = new StringBuffer(); 348 artifactKey.append(artifact.getArtifactId()).append('-') 349 .append(artifact.getVersion()).append('-'); 350 if (artifact.hasClassifier()) { 351 artifactKey.append(artifact.getClassifier()).append('-'); 352 } 353 artifactKey.append(artifact.getType()); 354 return artifactKey.toString(); 355 } 356}