Java tutorial
/** * Copyright (c) 2014 Takari, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package io.takari.maven.plugins.jar; import static com.google.common.collect.Iterables.size; import static java.util.Arrays.asList; import static java.util.Collections.singleton; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.Manifest; import javax.inject.Inject; 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; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import io.takari.incrementalbuild.Output; import io.takari.incrementalbuild.aggregator.AggregatorBuildContext; import io.takari.incrementalbuild.aggregator.InputAggregator; import io.takari.incrementalbuild.aggregator.InputSet; import io.takari.maven.plugins.TakariLifecycleMojo; import io.takari.maven.plugins.util.PropertiesWriter; import io.tesla.proviso.archive.Archiver; import io.tesla.proviso.archive.Entry; import io.tesla.proviso.archive.source.FileEntry; @Mojo(name = "jar", defaultPhase = LifecyclePhase.PACKAGE, configurator = "takari", threadSafe = true) public class Jar extends TakariLifecycleMojo { @Parameter(defaultValue = "${project.build.outputDirectory}") protected File classesDirectory; @Parameter(defaultValue = "${project.build.finalName}") private String finalName; @Parameter(defaultValue = "${project.build.directory}") private File outputDirectory; @Parameter(defaultValue = "true", property = "mainJar") private boolean mainJar; @Parameter(defaultValue = "false", property = "sourceJar") private boolean sourceJar; @Parameter(defaultValue = "false", property = "testJar") private boolean testJar; @Parameter(defaultValue = "${project.build.testOutputDirectory}") private File testClassesDirectory; @Parameter private ArchiveConfiguration archive; @Inject private AggregatorBuildContext buildContext; private static final String MANIFEST_PATH = "META-INF/MANIFEST.MF"; @Override protected void executeMojo() throws MojoExecutionException { if (!outputDirectory.exists()) { outputDirectory.mkdir(); } if (mainJar) { File jar = new File(outputDirectory, String.format("%s.jar", finalName)); InputSet registeredOutput = buildContext.newInputSet(); try { if (classesDirectory.isDirectory()) { classesDirectory = classesDirectory.getCanonicalFile(); Iterable<File> inputs = registeredOutput.addInputs(classesDirectory, null, null); logger.debug("Analyzing main classes directory {} with {} entries", classesDirectory, size(inputs)); } else { logger.warn("Main classes directory {} does not exist", classesDirectory); } // XXX this does not detect changes in archive#manifestFile. boolean processingRequired = registeredOutput.aggregateIfNecessary(jar, new InputAggregator() { @Override public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException { logger.info("Building main JAR."); List<Iterable<Entry>> sources = new ArrayList<>(); if (archive != null && archive.getManifestFile() != null) { sources.add(jarManifestSource(archive.getManifestFile())); } sources.add(inputsSource(classesDirectory, inputs)); sources.add(singleton(pomPropertiesSource(project))); sources.add(jarManifestSource(project)); archive(output.getResource(), sources); } }); if (!processingRequired) { logger.info("Main JAR is up-to-date"); } project.getArtifact().setFile(jar); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } if (sourceJar) { final Multimap<File, File> sources = HashMultimap.create(); File sourceJar = new File(outputDirectory, String.format("%s-%s.jar", finalName, "sources")); InputSet registeredOutput = buildContext.newInputSet(); try { for (String sourceRoot : project.getCompileSourceRoots()) { File dir = new File(sourceRoot); if (dir.isDirectory()) { dir = dir.getCanonicalFile(); Iterable<File> inputs = registeredOutput.addInputs(dir, null, null); logger.debug("Analyzing sources directory {} with {} entries", dir, size(inputs)); sources.putAll(dir, inputs); } else { logger.debug("Sources directory {} does not exist", dir); } } boolean processingRequired = registeredOutput.aggregateIfNecessary(sourceJar, new InputAggregator() { @Override public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException { logger.info("Building source JAR."); archive(output.getResource(), asList(inputsSource(sources), jarManifestSource(project))); } }); if (!processingRequired) { logger.info("Source JAR is up-to-date"); } projectHelper.attachArtifact(project, "jar", "sources", sourceJar); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } } if (testJar && testClassesDirectory.isDirectory()) { File testJar = new File(outputDirectory, String.format("%s-%s.jar", finalName, "tests")); InputSet registeredOutput = buildContext.newInputSet(); try { testClassesDirectory = testClassesDirectory.getCanonicalFile(); Iterable<File> inputs = registeredOutput.addInputs(testClassesDirectory, null, null); logger.debug("Analyzing test classes directory {} with {} entries", testClassesDirectory, size(inputs)); boolean processingRequired = registeredOutput.aggregateIfNecessary(testJar, new InputAggregator() { @Override public void aggregate(Output<File> output, Iterable<File> inputs) throws IOException { logger.info("Building test JAR."); archive(output.getResource(), asList(inputsSource(testClassesDirectory, inputs), jarManifestSource(project))); } }); if (!processingRequired) { logger.info("Test JAR is up-to-date"); } } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } projectHelper.attachArtifact(project, "jar", "tests", testJar); } } private void archive(File jar, List<Iterable<Entry>> sources) throws IOException { Archiver archiver = Archiver.builder() // .useRoot(false) // .normalize(true) // .build(); archiver.archive(jar, new AggregateSource(sources)); if (logger.isDebugEnabled()) { int size = 0; for (Iterable<?> source : sources) { size += size(source); } logger.debug("Created archive {} with {} entries", jar, size); } } static String getRelativePath(File basedir, File resource) { return basedir.toPath().relativize(resource.toPath()).toString().replace('\\', '/'); // always use forward slash for path separator } private List<Entry> inputsSource(Multimap<File, File> inputs) { final List<Entry> entries = new ArrayList<>(); for (File basedir : inputs.keySet()) { entries.addAll(inputsSource(basedir, inputs.get(basedir))); } return entries; } private List<Entry> inputsSource(File basedir, Iterable<File> inputs) { final List<Entry> entries = new ArrayList<>(); for (File input : inputs) { String entryName = getRelativePath(basedir, input); entries.add(new FileEntry(entryName, input)); } return entries; } public static Iterable<Entry> jarManifestSource(File file) { return singleton((Entry) new FileEntry(MANIFEST_PATH, file)); } private Iterable<Entry> jarManifestSource(MavenProject project) throws IOException { Manifest manifest = new Manifest(); Attributes main = manifest.getMainAttributes(); main.putValue("Manifest-Version", "1.0"); main.putValue("Archiver-Version", "Provisio Archiver"); main.putValue("Created-By", "Takari Inc."); main.putValue("Built-By", System.getProperty("user.name")); main.putValue("Build-Jdk", System.getProperty("java.version")); main.putValue("Specification-Title", project.getArtifactId()); main.putValue("Specification-Version", project.getVersion()); main.putValue("Implementation-Title", project.getArtifactId()); main.putValue("Implementation-Version", project.getVersion()); main.putValue("Implementation-Vendor-Id", project.getGroupId()); File manifestFile = new File(project.getBuild().getDirectory(), "MANIFEST.MF"); if (!manifestFile.getParentFile().exists()) { manifestFile.getParentFile().mkdirs(); } ByteArrayOutputStream buf = new ByteArrayOutputStream(); manifest.write(buf); return singleton((Entry) new BytesEntry(MANIFEST_PATH, buf.toByteArray())); } protected Entry pomPropertiesSource(MavenProject project) throws IOException { String entryName = String.format("META-INF/maven/%s/%s/pom.properties", project.getGroupId(), project.getArtifactId()); ByteArrayOutputStream buf = new ByteArrayOutputStream(); Properties properties = new Properties(); properties.setProperty("groupId", project.getGroupId()); properties.setProperty("artifactId", project.getArtifactId()); properties.setProperty("version", project.getVersion()); PropertiesWriter.write(properties, null, buf); return new BytesEntry(entryName, buf.toByteArray()); } }