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 sha256 signatures 081 * for the individual artifacts, where the {@link Artifact} is represented as: 082 * <code>groupId:artifactId:version:type=sha512</code>. 083 */ 084 private final Properties artifactSha512s = new Properties(); 085 086 /** 087 * The maven project context injection so that we can get a hold of the variables at hand. 088 */ 089 @Parameter(defaultValue = "${project}", required = true) 090 private MavenProject project; 091 092 /** 093 * The working directory in <code>target</code> that we use as a sandbox for the plugin. 094 */ 095 @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin", 096 property = "commons.outputDirectory") 097 private File workingDirectory; 098 099 /** 100 * The subversion staging url to which we upload all of our staged artifacts. 101 */ 102 @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl") 103 private String distSvnStagingUrl; 104 105 /** 106 * A parameter to generally avoid running unless it is specifically turned on by the consuming module. 107 */ 108 @Parameter(defaultValue = "false", property = "commons.release.isDistModule") 109 private Boolean isDistModule; 110 111 @Override 112 public void execute() throws MojoExecutionException { 113 if (!isDistModule) { 114 getLog().info( 115 "This module is marked as a non distribution or assembly module, and the plugin will not run."); 116 return; 117 } 118 if (StringUtils.isEmpty(distSvnStagingUrl)) { 119 getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run."); 120 return; 121 } 122 getLog().info("Detaching Assemblies"); 123 for (Object attachedArtifact : project.getAttachedArtifacts()) { 124 putAttachedArtifactInSha512Map((Artifact) attachedArtifact); 125 if (ARTIFACT_TYPES_TO_DETACH.contains(((Artifact) attachedArtifact).getType())) { 126 detachedArtifacts.add((Artifact) attachedArtifact); 127 } 128 } 129 if (detachedArtifacts.isEmpty()) { 130 getLog().info("Current project contains no distributions. Not executing."); 131 return; 132 } 133 for (Artifact artifactToRemove : detachedArtifacts) { 134 project.getAttachedArtifacts().remove(artifactToRemove); 135 } 136 if (!workingDirectory.exists()) { 137 SharedFunctions.initDirectory(getLog(), workingDirectory); 138 } 139 writeAllArtifactsInSha512PropertiesFile(); 140 copyRemovedArtifactsToWorkingDirectory(); 141 getLog().info(""); 142 hashArtifacts(); 143 } 144 145 /** 146 * Takes an attached artifact and puts the signature in the map. 147 * @param artifact is a Maven {@link Artifact} taken from the project at start time of mojo. 148 * @throws MojoExecutionException if an {@link IOException} occurs when getting the sha512 of the 149 * artifact. 150 */ 151 private void putAttachedArtifactInSha512Map(Artifact artifact) throws MojoExecutionException { 152 try { 153 String artifactKey = getArtifactKey(artifact); 154 try (FileInputStream fis = new FileInputStream(artifact.getFile())) { 155 artifactSha512s.put(artifactKey, DigestUtils.sha512Hex(fis)); 156 } 157 } catch (IOException e) { 158 throw new MojoExecutionException( 159 "Could not find artifact signature for: " 160 + artifact.getArtifactId() 161 + "-" 162 + artifact.getClassifier() 163 + "-" 164 + artifact.getVersion() 165 + " type: " 166 + artifact.getType(), 167 e); 168 } 169 } 170 171 /** 172 * Writes to ./target/commons-release-plugin/sha512.properties the artifact sha512's. 173 * 174 * @throws MojoExecutionException if we can't write the file due to an {@link IOException}. 175 */ 176 private void writeAllArtifactsInSha512PropertiesFile() throws MojoExecutionException { 177 File propertiesFile = new File(workingDirectory, "sha512.properties"); 178 getLog().info("Writting " + propertiesFile); 179 try (FileOutputStream fileWriter = new FileOutputStream(propertiesFile)) { 180 artifactSha512s.store(fileWriter, "Release SHA-512s"); 181 } catch (IOException e) { 182 throw new MojoExecutionException("Failure to write SHA-512's", e); 183 } 184 } 185 186 /** 187 * A helper method to copy the newly detached artifacts to <code>target/commons-release-plugin</code> 188 * so that the {@link CommonsDistributionStagingMojo} can find the artifacts later. 189 * 190 * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it 191 * properly wrapped so that Maven can handle it. 192 */ 193 private void copyRemovedArtifactsToWorkingDirectory() throws MojoExecutionException { 194 final String wdAbsolutePath = workingDirectory.getAbsolutePath(); 195 getLog().info( 196 "Copying " + detachedArtifacts.size() + " detached artifacts to working directory " + wdAbsolutePath); 197 for (Artifact artifact: detachedArtifacts) { 198 File artifactFile = artifact.getFile(); 199 StringBuilder copiedArtifactAbsolutePath = new StringBuilder(wdAbsolutePath); 200 copiedArtifactAbsolutePath.append("/"); 201 copiedArtifactAbsolutePath.append(artifactFile.getName()); 202 File copiedArtifact = new File(copiedArtifactAbsolutePath.toString()); 203 getLog().info("Copying: " + artifactFile.getName()); 204 SharedFunctions.copyFile(getLog(), artifactFile, copiedArtifact); 205 } 206 } 207 208 /** 209 * A helper method that creates sha256 and sha512 signature files for our detached artifacts in the 210 * <code>target/commons-release-plugin</code> directory for the purpose of being uploaded by 211 * the {@link CommonsDistributionStagingMojo}. 212 * 213 * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it 214 * properly wrapped so that Maven can handle it. 215 */ 216 private void hashArtifacts() throws MojoExecutionException { 217 for (Artifact artifact : detachedArtifacts) { 218 if (!artifact.getFile().getName().contains("asc")) { 219 String artifactKey = getArtifactKey(artifact); 220 try { 221 String digest; 222 // SHA-512 223 digest = artifactSha512s.getProperty(artifactKey.toString()); 224 getLog().info(artifact.getFile().getName() + " sha512: " + digest); 225 try (PrintWriter printWriter = new PrintWriter( 226 getSha512FilePath(workingDirectory, artifact.getFile()))) { 227 printWriter.println(digest); 228 } 229 } catch (IOException e) { 230 throw new MojoExecutionException("Could not sign file: " + artifact.getFile().getName(), e); 231 } 232 } 233 } 234 } 235 236 /** 237 * A helper method to create a file path for the <code>sha512</code> signature file from a given file. 238 * 239 * @param directory is the {@link File} for the directory in which to make the <code>.sha512</code> file. 240 * @param file the {@link File} whose name we should use to create the <code>.sha512</code> file. 241 * @return a {@link String} that is the absolute path to the <code>.sha512</code> file. 242 */ 243 private String getSha512FilePath(File directory, File file) { 244 StringBuilder buffer = new StringBuilder(directory.getAbsolutePath()); 245 buffer.append("/"); 246 buffer.append(file.getName()); 247 buffer.append(".sha512"); 248 return buffer.toString(); 249 } 250 251 /** 252 * Generates the unique artifact key for storage in our sha256 map and sha512 map. For example, 253 * commons-test-1.4-src.tar.gz should have it's name as the key. 254 * 255 * @param artifact the {@link Artifact} that we wish to generate a key for. 256 * @return the generated key 257 */ 258 private String getArtifactKey(Artifact artifact) { 259 StringBuilder artifactKey = new StringBuilder(); 260 artifactKey.append(artifact.getArtifactId()).append('-') 261 .append(artifact.getVersion()).append('-'); 262 if (artifact.hasClassifier()) { 263 artifactKey.append(artifact.getClassifier()).append('-'); 264 } 265 artifactKey.append(artifact.getType()); 266 return artifactKey.toString(); 267 } 268}