| /* |
| * 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. |
| */ |
| package org.apache.commons.release.plugin.mojos; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| |
| import org.apache.commons.codec.digest.DigestUtils; |
| import org.apache.commons.collections4.properties.SortedProperties; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.commons.lang3.reflect.MethodUtils; |
| import org.apache.commons.release.plugin.SharedFunctions; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.plugin.AbstractMojo; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| |
| /** |
| * The purpose of this Maven mojo is to detach the artifacts generated by the maven-assembly-plugin, |
| * which for the Apache Commons Project do not get uploaded to Nexus, and putting those artifacts |
| * in the dev distribution location for Apache projects. |
| * |
| * @author chtompki |
| * @since 1.0 |
| */ |
| @Mojo(name = "detach-distributions", |
| defaultPhase = LifecyclePhase.VERIFY, |
| threadSafe = true, |
| aggregator = true) |
| public class CommonsDistributionDetachmentMojo extends AbstractMojo { |
| |
| /** |
| * A list of "artifact types" in the Maven vernacular, to |
| * be detached from the deployment. For the time being we want |
| * all artifacts generated by the maven-assembly-plugin to be detached |
| * from the deployment, namely *-src.zip, *-src.tar.gz, *-bin.zip, |
| * *-bin.tar.gz, and the corresponding .asc pgp signatures. |
| */ |
| private static final Set<String> ARTIFACT_TYPES_TO_DETACH; |
| static { |
| final Set<String> hashSet = new HashSet<>(); |
| hashSet.add("zip"); |
| hashSet.add("tar.gz"); |
| hashSet.add("zip.asc"); |
| hashSet.add("tar.gz.asc"); |
| ARTIFACT_TYPES_TO_DETACH = Collections.unmodifiableSet(hashSet); |
| } |
| |
| /** |
| * This list is supposed to hold the Maven references to the aforementioned artifacts so that we |
| * can upload them to svn after they've been detached from the Maven deployment. |
| */ |
| private final List<Artifact> detachedArtifacts = new ArrayList<>(); |
| |
| /** |
| * A {@link SortedProperties} of {@link Artifact} → {@link String} containing the sha512 signatures |
| * for the individual artifacts, where the {@link Artifact} is represented as: |
| * <code>groupId:artifactId:version:type=sha512</code>. |
| */ |
| private final SortedProperties artifactSha512s = new SortedProperties(); |
| |
| /** |
| * The maven project context injection so that we can get a hold of the variables at hand. |
| */ |
| @Parameter(defaultValue = "${project}", required = true) |
| private MavenProject project; |
| |
| /** |
| * The working directory in <code>target</code> that we use as a sandbox for the plugin. |
| */ |
| @Parameter(defaultValue = "${project.build.directory}/commons-release-plugin", |
| property = "commons.outputDirectory") |
| private File workingDirectory; |
| |
| /** |
| * The subversion staging url to which we upload all of our staged artifacts. |
| */ |
| @Parameter(defaultValue = "", property = "commons.distSvnStagingUrl") |
| private String distSvnStagingUrl; |
| |
| /** |
| * A parameter to generally avoid running unless it is specifically turned on by the consuming module. |
| */ |
| @Parameter(defaultValue = "false", property = "commons.release.isDistModule") |
| private Boolean isDistModule; |
| |
| @Override |
| public void execute() throws MojoExecutionException { |
| if (!isDistModule) { |
| getLog().info("This module is marked as a non distribution or assembly module, and the plugin will not run."); |
| return; |
| } |
| if (StringUtils.isEmpty(distSvnStagingUrl)) { |
| getLog().warn("commons.distSvnStagingUrl is not set, the commons-release-plugin will not run."); |
| return; |
| } |
| getLog().info("Detaching Assemblies"); |
| for (final Artifact attachedArtifact : project.getAttachedArtifacts()) { |
| putAttachedArtifactInSha512Map(attachedArtifact); |
| if (ARTIFACT_TYPES_TO_DETACH.contains(attachedArtifact.getType())) { |
| detachedArtifacts.add(attachedArtifact); |
| } |
| } |
| if (detachedArtifacts.isEmpty()) { |
| getLog().info("Current project contains no distributions. Not executing."); |
| return; |
| } |
| // |
| // PROBLEM CODE for Maven >= 3.8.3 |
| // https://issues.apache.org/jira/browse/MNG-7316 |
| try { |
| // (1) Try the normal way |
| // Maven 3.8.3 throws an exception here because MavenProject.getAttachedArtifacts() |
| // returns an IMMUTABLE collection. |
| project.getAttachedArtifacts().removeAll(detachedArtifacts); |
| } catch (final UnsupportedOperationException e) { |
| // (2) HACK workaround for https://issues.apache.org/jira/browse/MNG-7316 |
| final ArrayList<Artifact> arrayList = new ArrayList<>(project.getAttachedArtifacts()); |
| arrayList.removeAll(detachedArtifacts); |
| try { |
| // MavenProject#setAttachedArtifacts(List) is protected |
| MethodUtils.invokeMethod(project, true, "setAttachedArtifacts", arrayList); |
| } catch (final ReflectiveOperationException roe) { |
| throw new MojoExecutionException(roe); |
| } |
| } |
| if (!workingDirectory.exists()) { |
| SharedFunctions.initDirectory(getLog(), workingDirectory); |
| } |
| writeAllArtifactsInSha512PropertiesFile(); |
| copyRemovedArtifactsToWorkingDirectory(); |
| getLog().info(""); |
| hashArtifacts(); |
| } |
| |
| /** |
| * Takes an attached artifact and puts the signature in the map. |
| * @param artifact is a Maven {@link Artifact} taken from the project at start time of mojo. |
| * @throws MojoExecutionException if an {@link IOException} occurs when getting the sha512 of the |
| * artifact. |
| */ |
| private void putAttachedArtifactInSha512Map(final Artifact artifact) throws MojoExecutionException { |
| try { |
| final String artifactKey = getArtifactKey(artifact); |
| if (!artifactKey.endsWith(".asc")) { // .asc files don't need hashes |
| try (InputStream fis = Files.newInputStream(artifact.getFile().toPath())) { |
| artifactSha512s.put(artifactKey, DigestUtils.sha512Hex(fis)); |
| } |
| } |
| } catch (final IOException e) { |
| throw new MojoExecutionException( |
| "Could not find artifact signature for: " |
| + artifact.getArtifactId() |
| + "-" |
| + artifact.getClassifier() |
| + "-" |
| + artifact.getVersion() |
| + " type: " |
| + artifact.getType(), |
| e); |
| } |
| } |
| |
| /** |
| * Writes to ./target/commons-release-plugin/sha512.properties the artifact sha512's. |
| * |
| * @throws MojoExecutionException if we can't write the file due to an {@link IOException}. |
| */ |
| private void writeAllArtifactsInSha512PropertiesFile() throws MojoExecutionException { |
| final File propertiesFile = new File(workingDirectory, "sha512.properties"); |
| getLog().info("Writing " + propertiesFile); |
| try (OutputStream fileWriter = Files.newOutputStream(propertiesFile.toPath())) { |
| artifactSha512s.store(fileWriter, "Release SHA-512s"); |
| } catch (final IOException e) { |
| throw new MojoExecutionException("Failure to write SHA-512's", e); |
| } |
| } |
| |
| /** |
| * A helper method to copy the newly detached artifacts to <code>target/commons-release-plugin</code> |
| * so that the {@link CommonsDistributionStagingMojo} can find the artifacts later. |
| * |
| * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it |
| * properly wrapped so that Maven can handle it. |
| */ |
| private void copyRemovedArtifactsToWorkingDirectory() throws MojoExecutionException { |
| final String wdAbsolutePath = workingDirectory.getAbsolutePath(); |
| getLog().info( |
| "Copying " + detachedArtifacts.size() + " detached artifacts to working directory " + wdAbsolutePath); |
| for (final Artifact artifact: detachedArtifacts) { |
| final File artifactFile = artifact.getFile(); |
| final StringBuilder copiedArtifactAbsolutePath = new StringBuilder(wdAbsolutePath); |
| copiedArtifactAbsolutePath.append("/"); |
| copiedArtifactAbsolutePath.append(artifactFile.getName()); |
| final File copiedArtifact = new File(copiedArtifactAbsolutePath.toString()); |
| getLog().info("Copying: " + artifactFile.getName()); |
| SharedFunctions.copyFile(getLog(), artifactFile, copiedArtifact); |
| } |
| } |
| |
| /** |
| * A helper method that creates sha512 signature files for our detached artifacts in the |
| * <code>target/commons-release-plugin</code> directory for the purpose of being uploaded by |
| * the {@link CommonsDistributionStagingMojo}. |
| * |
| * @throws MojoExecutionException if some form of an {@link IOException} occurs, we want it |
| * properly wrapped so that Maven can handle it. |
| */ |
| private void hashArtifacts() throws MojoExecutionException { |
| for (final Artifact artifact : detachedArtifacts) { |
| if (!artifact.getFile().getName().toLowerCase(Locale.ROOT).contains("asc")) { |
| final String artifactKey = getArtifactKey(artifact); |
| try { |
| final String digest; |
| // SHA-512 |
| digest = artifactSha512s.getProperty(artifactKey.toString()); |
| getLog().info(artifact.getFile().getName() + " sha512: " + digest); |
| try (PrintWriter printWriter = new PrintWriter( |
| getSha512FilePath(workingDirectory, artifact.getFile()))) { |
| printWriter.println(digest); |
| } |
| } catch (final IOException e) { |
| throw new MojoExecutionException("Could not sign file: " + artifact.getFile().getName(), e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * A helper method to create a file path for the <code>sha512</code> signature file from a given file. |
| * |
| * @param directory is the {@link File} for the directory in which to make the <code>.sha512</code> file. |
| * @param file the {@link File} whose name we should use to create the <code>.sha512</code> file. |
| * @return a {@link String} that is the absolute path to the <code>.sha512</code> file. |
| */ |
| private String getSha512FilePath(final File directory, final File file) { |
| final StringBuilder buffer = new StringBuilder(directory.getAbsolutePath()); |
| buffer.append("/"); |
| buffer.append(file.getName()); |
| buffer.append(".sha512"); |
| return buffer.toString(); |
| } |
| |
| /** |
| * Generates the unique artifact key for storage in our sha512 map. For example, |
| * commons-test-1.4-src.tar.gz should have it's name as the key. |
| * |
| * @param artifact the {@link Artifact} that we wish to generate a key for. |
| * @return the generated key |
| */ |
| private String getArtifactKey(final Artifact artifact) { |
| return artifact.getFile().getName(); |
| } |
| } |