diff --git a/tycho-api/src/main/java/org/eclipse/tycho/TychoConstants.java b/tycho-api/src/main/java/org/eclipse/tycho/TychoConstants.java index 2a1a5c793f..9b96ec4cc2 100644 --- a/tycho-api/src/main/java/org/eclipse/tycho/TychoConstants.java +++ b/tycho-api/src/main/java/org/eclipse/tycho/TychoConstants.java @@ -60,6 +60,10 @@ public interface TychoConstants { public static final String BUILD_TIMESTAMP = CTX_BASENAME + "/buildTimestamp"; + String HEADER_TYCHO_BUILD_TIMESTAMP = "Tycho-Build-Timestamp"; + + String HEADER_BND_LAST_MODIFIED = "Bnd-LastModified"; + public String JAR_EXTENSION = "jar"; String PROP_GROUP_ID = "maven-groupId"; diff --git a/tycho-artifactcomparator/pom.xml b/tycho-artifactcomparator/pom.xml index d699293a0d..5d29af9cf2 100644 --- a/tycho-artifactcomparator/pom.xml +++ b/tycho-artifactcomparator/pom.xml @@ -82,6 +82,11 @@ jfiveparse 1.0.2 + + org.eclipse.tycho + tycho-api + ${project.version} + diff --git a/tycho-artifactcomparator/src/main/java/org/eclipse/tycho/zipcomparator/internal/ManifestComparator.java b/tycho-artifactcomparator/src/main/java/org/eclipse/tycho/zipcomparator/internal/ManifestComparator.java index ac8a0ceb56..72756fce8c 100644 --- a/tycho-artifactcomparator/src/main/java/org/eclipse/tycho/zipcomparator/internal/ManifestComparator.java +++ b/tycho-artifactcomparator/src/main/java/org/eclipse/tycho/zipcomparator/internal/ManifestComparator.java @@ -25,6 +25,7 @@ import java.util.jar.Manifest; import org.codehaus.plexus.component.annotations.Component; +import org.eclipse.tycho.TychoConstants; import org.eclipse.tycho.artifactcomparator.ArtifactComparator.ComparisonData; import org.eclipse.tycho.artifactcomparator.ArtifactDelta; import org.eclipse.tycho.artifactcomparator.ComparatorInputStream; @@ -51,13 +52,14 @@ private static enum Change { new Name("Build-Jdk"), // new Name("Built-By"), // new Name("Build-Jdk-Spec"), + //added by Tycho itself + new Name(TychoConstants.HEADER_TYCHO_BUILD_TIMESTAMP), // // lets be friendly to bnd/maven-bundle-plugin - new Name("Bnd-LastModified"), // + new Name(TychoConstants.HEADER_BND_LAST_MODIFIED), // new Name("Bundle-Developers"), // new Name("Tool"), // this is common attribute not supported by Tycho yet new Name("Eclipse-SourceReferences")); - // TODO make it possible to disable default ignores and add custom ignore @Override public ArtifactDelta getDelta(ComparatorInputStream baseline, ComparatorInputStream reactor, ComparisonData data) diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierAggregatorMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierAggregatorMojo.java index 66cf3be4cc..7a7adde3f7 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierAggregatorMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierAggregatorMojo.java @@ -12,17 +12,25 @@ *******************************************************************************/ package org.eclipse.tycho.buildversion; +import java.io.File; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.OptionalLong; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import org.eclipse.tycho.ArtifactDescriptor; import org.eclipse.tycho.ReactorProject; import org.eclipse.tycho.TargetPlatformService; +import org.eclipse.tycho.TychoConstants; import org.eclipse.tycho.core.ArtifactDependencyVisitor; import org.eclipse.tycho.core.FeatureDescription; import org.eclipse.tycho.core.PluginDescription; @@ -60,6 +68,15 @@ public class BuildQualifierAggregatorMojo extends BuildQualifierMojo { @Component private TargetPlatformService platformService; + /** + * Controls if when a aggregation happens the mojo should use the embedded + * timestamps in the jar manifest or finally fall back to the last modified + * timestamps of the jar entries itself if it can't parse the qualifier from the + * file name. + */ + @Parameter(defaultValue = "true") + private boolean useArtifactTimestamps = true; + @Override protected Date getBuildTimestamp() throws MojoExecutionException { Date timestamp = super.getBuildTimestamp(); @@ -103,49 +120,77 @@ public void visitPlugin(PluginDescription plugin) { } private void visitArtifact(ArtifactDescriptor artifact) { - ReactorProject otherProject = artifact.getMavenProject(); - String otherVersion = (otherProject != null) ? otherProject.getExpandedVersion() - : artifact.getKey().getVersion(); - Version v = Version.parseVersion(otherVersion); - String otherQualifier = v.getQualifier(); - if (otherQualifier != null) { - Date timestamp = parseQualifier(otherQualifier); - if (timestamp != null) { - if (latestTimestamp[0].compareTo(timestamp) < 0) { - if (getLog().isDebugEnabled()) { - getLog().debug("Found '" + format.format(timestamp) + "' from qualifier '" - + otherQualifier + "' for artifact " + artifact); - } - latestTimestamp[0] = timestamp; - } - } else { - getLog().debug("Could not parse qualifier timestamp " + otherQualifier); - } - } - } - - private Date parseQualifier(String qualifier) { - Date timestamp = parseQualifier(qualifier, format); - if (timestamp != null) { - return timestamp; - } - return discoverTimestamp(qualifier); + Date timestamp = getTimestamp(artifact); + if (timestamp != null && latestTimestamp[0].compareTo(timestamp) < 0) { + latestTimestamp[0] = timestamp; + } } - private Date parseQualifier(String qualifier, SimpleDateFormat format) { - ParsePosition pos = new ParsePosition(0); - Date timestamp = format.parse(qualifier, pos); - if (timestamp != null && pos.getIndex() == qualifier.length()) { - return timestamp; - } - return null; - } - - private Date discoverTimestamp(String qualifier) { - return timestampFinder.findInString(qualifier); - } }); return latestTimestamp[0]; } + + private Date getTimestamp(ArtifactDescriptor artifact) { + ReactorProject otherProject = artifact.getMavenProject(); + if (otherProject != null) { + Object contextValue = otherProject.getContextValue(TychoConstants.BUILD_TIMESTAMP); + if (contextValue instanceof Date date) { + return date; + } + } + String otherVersion = (otherProject != null) ? otherProject.getExpandedVersion() + : artifact.getKey().getVersion(); + Version v = Version.parseVersion(otherVersion); + String otherQualifier = v.getQualifier(); + if (otherQualifier != null) { + Date parseQualifier = parseQualifier(otherQualifier); + if (parseQualifier != null) { + return parseQualifier; + } + } + if (useArtifactTimestamps) { + try { + File file = artifact.fetchArtifact().get(); + try (JarFile jarFile = new JarFile(file)) { + Manifest manifest = jarFile.getManifest(); + if (manifest != null) { + Attributes attributes = manifest.getMainAttributes(); + String tychoTs = attributes.getValue(TychoConstants.HEADER_TYCHO_BUILD_TIMESTAMP); + if (tychoTs != null) { + return new Date(Long.parseLong(tychoTs)); + } + String bndTs = attributes.getValue(TychoConstants.HEADER_BND_LAST_MODIFIED); + if (bndTs != null) { + return new Date(Long.parseLong(bndTs)); + } + } + OptionalLong max = jarFile.stream().mapToLong(JarEntry::getTime).filter(l -> l > 0).max(); + if (max.isPresent()) { + return new Date(max.getAsLong()); + } + } + } catch (Exception e) { + // can't use it then... + } + } + return null; + } + + private Date parseQualifier(String qualifier) { + Date timestamp = parseQualifier(qualifier, format); + if (timestamp != null) { + return timestamp; + } + return timestampFinder.findInString(qualifier); + } + + private Date parseQualifier(String qualifier, SimpleDateFormat format) { + ParsePosition pos = new ParsePosition(0); + Date timestamp = format.parse(qualifier, pos); + if (timestamp != null && pos.getIndex() == qualifier.length()) { + return timestamp; + } + return null; + } } diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierMojo.java index 5c76d1df3f..e84a1edf3f 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/buildversion/BuildQualifierMojo.java @@ -151,7 +151,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { project.getProperties().put(UNQUALIFIED_VERSION, projectVersion.unqualifiedVersion); project.getProperties().put(QUALIFIED_VERSION, projectVersion.getOSGiVersion()); getLog().info("The project's OSGi version is " + projectVersion.getOSGiVersion()); - DefaultReactorProject.adapt(project).setContextValue(TychoConstants.BUILD_TIMESTAMP, projectVersion); + DefaultReactorProject.adapt(project).setContextValue(TychoConstants.BUILD_TIMESTAMP, timestamp); } private TychoProjectVersion calculateQualifiedVersion(Date timestamp) diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/AbstractTychoPackagingMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/AbstractTychoPackagingMojo.java index 4f8e3d840c..c460dcaa6f 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/AbstractTychoPackagingMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/AbstractTychoPackagingMojo.java @@ -15,11 +15,15 @@ import java.io.File; import java.util.Arrays; +import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.maven.archiver.ManifestConfiguration; +import org.apache.maven.archiver.MavenArchiver; +import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -29,10 +33,13 @@ import org.apache.maven.project.MavenProjectHelper; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.archiver.FileSet; +import org.codehaus.plexus.archiver.jar.Manifest; +import org.codehaus.plexus.archiver.jar.ManifestException; import org.codehaus.plexus.archiver.util.DefaultFileSet; import org.codehaus.plexus.util.AbstractScanner; import org.eclipse.tycho.BuildProperties; import org.eclipse.tycho.DependencyArtifacts; +import org.eclipse.tycho.TychoConstants; import org.eclipse.tycho.TychoProperties; import org.eclipse.tycho.core.TychoProject; import org.eclipse.tycho.core.osgitools.DefaultReactorProject; @@ -47,115 +54,145 @@ public abstract class AbstractTychoPackagingMojo extends AbstractMojo { @Parameter(property = "project.build.directory", required = true) protected File buildDirectory; - @Parameter(property = "session", readonly = true) - protected MavenSession session; + @Parameter(property = "session", readonly = true) + protected MavenSession session; - @Parameter(property = "project", readonly = true) - protected MavenProject project; + @Parameter(property = "project", readonly = true) + protected MavenProject project; - @Parameter(defaultValue = "true") - protected boolean useDefaultExcludes; + @Parameter(defaultValue = "true") + protected boolean useDefaultExcludes; - /** - * Build qualifier. Recommended way to set this parameter is using build-qualifier goal. - */ + /** + * Build qualifier. Recommended way to set this parameter is using + * build-qualifier goal. + */ @Parameter(property = TychoProperties.BUILD_QUALIFIER) - protected String qualifier; - - /** - * If set to true (the default), missing build.properties bin.includes will cause - * build failure. If set to false, missing build.properties bin.includes will be - * reported as warnings but the build will not fail. - */ - @Parameter(defaultValue = "true") - protected boolean strictBinIncludes; - - /** - * Additional files to be included in the final .jar. - *

- * A typical usage might be when bin.includes in build.properties - * is not flexible enough, e.g., for generated files, as when conflicting additional files - * win over bin.includes. - *

- * Example: - *

-     * <additionalFileSets>
-     *  <fileSet>
-     *   <directory>${project.build.directory}/mytool-gen/</directory>
-     *   <includes>
-     *    <include>**/*</include>
-     *   </includes>
-     *  </fileSet>
-     * </additionalFileSets>
-     * 
- * Note: currently, additional file sets are not used for the package-iu goal. - */ - @Parameter - protected DefaultFileSet[] additionalFileSets; - - @Component - protected PlexusContainer plexus; - - @Component - protected MavenProjectHelper projectHelper; - - @Component(role = TychoProject.class) - private Map projectTypes; - - @Component - private IncludeValidationHelper includeValidationHelper; - - /** - * @return a {@link FileSet} with the given includes and excludes and the configured default - * excludes. An empty list of includes leads to an empty file set. - */ - protected FileSet getFileSet(File basedir, List includes, List excludes) { - DefaultFileSet fileSet = new DefaultFileSet(); - fileSet.setDirectory(basedir); - - if (includes.isEmpty()) { - // FileSet interprets empty list as "everything", so we need to express "nothing" in a different way - fileSet.setIncludes(new String[] { "" }); - } else { - fileSet.setIncludes(includes.toArray(new String[includes.size()])); - } - - Set allExcludes = new LinkedHashSet<>(); - if (excludes != null) { - allExcludes.addAll(excludes); - } - if (useDefaultExcludes) { - allExcludes.addAll(Arrays.asList(AbstractScanner.DEFAULTEXCLUDES)); - // keep ignoring the following files after - // https://github.com/codehaus-plexus/plexus-utils/pull/174 - allExcludes.add("**/.gitignore"); - allExcludes.add("**/.gitattributes"); - } + protected String qualifier; - fileSet.setExcludes(allExcludes.toArray(new String[allExcludes.size()])); + /** + * If set to true (the default), missing build.properties + * bin.includes will cause build failure. If set to false, missing + * build.properties bin.includes will be reported as warnings but the build will + * not fail. + */ + @Parameter(defaultValue = "true") + protected boolean strictBinIncludes; - return fileSet; - } + /** + * Additional files to be included in the final .jar. + *

+ * A typical usage might be when bin.includes in + * build.properties is not flexible enough, e.g., for generated + * files, as when conflicting additional files win over + * bin.includes. + *

+ * Example: + * + *

+	 * <additionalFileSets>
+	 *  <fileSet>
+	 *   <directory>${project.build.directory}/mytool-gen/</directory>
+	 *   <includes>
+	 *    <include>**/*</include>
+	 *   </includes>
+	 *  </fileSet>
+	 * </additionalFileSets>
+	 * 
+ * + * Note: currently, additional file sets are not used for the + * package-iu goal. + */ + @Parameter + protected DefaultFileSet[] additionalFileSets; + + @Component + protected PlexusContainer plexus; - protected TychoProject getTychoProjectFacet() { - return getTychoProjectFacet(project.getPackaging()); - } + @Component + protected MavenProjectHelper projectHelper; - protected TychoProject getTychoProjectFacet(String packaging) { - TychoProject facet = projectTypes.get(packaging); - if (facet == null) { - throw new IllegalStateException("Unknown or unsupported packaging type " + packaging); - } - return facet; - } + @Component(role = TychoProject.class) + private Map projectTypes; - protected DependencyArtifacts getDependencyArtifacts() { - return getTychoProjectFacet().getDependencyArtifacts(DefaultReactorProject.adapt(project)); - } + @Component + private IncludeValidationHelper includeValidationHelper; - protected void checkBinIncludesExist(BuildProperties buildProperties, String... ignoredIncludes) - throws MojoExecutionException { - includeValidationHelper.checkBinIncludesExist(project, buildProperties, strictBinIncludes, ignoredIncludes); - } + /** + * @return a {@link FileSet} with the given includes and excludes and the + * configured default excludes. An empty list of includes leads to an + * empty file set. + */ + protected FileSet getFileSet(File basedir, List includes, List excludes) { + DefaultFileSet fileSet = new DefaultFileSet(); + fileSet.setDirectory(basedir); + + if (includes.isEmpty()) { + // FileSet interprets empty list as "everything", so we need to express + // "nothing" in a different way + fileSet.setIncludes(new String[] { "" }); + } else { + fileSet.setIncludes(includes.toArray(new String[includes.size()])); + } + + Set allExcludes = new LinkedHashSet<>(); + if (excludes != null) { + allExcludes.addAll(excludes); + } + if (useDefaultExcludes) { + allExcludes.addAll(Arrays.asList(AbstractScanner.DEFAULTEXCLUDES)); + // keep ignoring the following files after + // https://github.com/codehaus-plexus/plexus-utils/pull/174 + allExcludes.add("**/.gitignore"); + allExcludes.add("**/.gitattributes"); + } + + fileSet.setExcludes(allExcludes.toArray(new String[allExcludes.size()])); + + return fileSet; + } + + protected TychoProject getTychoProjectFacet() { + return getTychoProjectFacet(project.getPackaging()); + } + + protected TychoProject getTychoProjectFacet(String packaging) { + TychoProject facet = projectTypes.get(packaging); + if (facet == null) { + throw new IllegalStateException("Unknown or unsupported packaging type " + packaging); + } + return facet; + } + + protected DependencyArtifacts getDependencyArtifacts() { + return getTychoProjectFacet().getDependencyArtifacts(DefaultReactorProject.adapt(project)); + } + + protected void checkBinIncludesExist(BuildProperties buildProperties, String... ignoredIncludes) + throws MojoExecutionException { + includeValidationHelper.checkBinIncludesExist(project, buildProperties, strictBinIncludes, ignoredIncludes); + } + + protected static MavenArchiver createMavenArchiver(boolean includeBuildTimestamp) { + if (includeBuildTimestamp) { + MavenArchiver archiver = new MavenArchiver() { + @Override + protected Manifest getManifest(MavenSession session, MavenProject project, ManifestConfiguration config, + Map entries) throws ManifestException, DependencyResolutionRequiredException { + Manifest manifest = super.getManifest(session, project, config, entries); + Object contextValue = DefaultReactorProject.adapt(project) + .getContextValue(TychoConstants.BUILD_TIMESTAMP); + if (contextValue instanceof Date timestamp) { + Manifest.Attribute attr = new Manifest.Attribute(TychoConstants.HEADER_TYCHO_BUILD_TIMESTAMP, + Long.toString(timestamp.getTime())); + manifest.addConfiguredAttribute(attr); + } + return manifest; + } + }; + return archiver; + } + return new MavenArchiver(); + } } diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java index 8af5d29cc3..5605ddc90d 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java @@ -50,66 +50,75 @@ @Mojo(name = "package-feature", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true) public class PackageFeatureMojo extends AbstractTychoPackagingMojo { - private static final Object LOCK = new Object(); - - private static final String FEATURE_PROPERTIES = "feature.properties"; - - /** - * The maven archiver to use. One - * of the archiver properties is the addMavenDescriptor flag, which indicates - * whether the generated archive will contain the pom.xml and pom.properties file. If no archive - * configuration is specified, the default value is false. If the maven descriptor - * should be added to the artifact, use the following configuration: - * - *
-     * <plugin>
-     *   <groupId>org.eclipse.tycho</groupId>
-     *   <artifactId>tycho-packaging-plugin</artifactId>
-     *   <version>${tycho-version}</version>
-     *   <configuration>
-     *     <archive>
-     *       <addMavenDescriptor>true</addMavenDescriptor>
-     *     </archive>
-     *   </configuration>
-     * </plugin>
-     * 
- */ - @Parameter - private MavenArchiveConfiguration archive; - - /** - * The output directory of the jar file - * - * By default this is the Maven target/ directory. - */ - @Parameter(property = "project.build.directory") - private File outputDirectory; - - @Parameter(property = "project.basedir") - private File basedir; - - /** - * The path to the feature.xml file. - *

- * Defaults to the feature.xml under the project's base directory. - */ - @Parameter(defaultValue = "${project.basedir}/" + FEATURE_XML) - private File featureFile; - - /** - * Name of the generated JAR. - */ - @Parameter(property = "project.build.finalName", alias = "jarName", required = true) - private String finalName; - - @Parameter(defaultValue = "${project.build.directory}/site") - private File target; - - @Component - private FeatureXmlTransformer featureXmlTransformer; - - @Component - private LicenseFeatureHelper licenseFeatureHelper; + private static final Object LOCK = new Object(); + + private static final String FEATURE_PROPERTIES = "feature.properties"; + + /** + * The maven + * archiver to use. One of the archiver properties is the + * addMavenDescriptor flag, which indicates whether the generated + * archive will contain the pom.xml and pom.properties file. If no archive + * configuration is specified, the default value is false. If the + * maven descriptor should be added to the artifact, use the following + * configuration: + * + *

+	 * <plugin>
+	 *   <groupId>org.eclipse.tycho</groupId>
+	 *   <artifactId>tycho-packaging-plugin</artifactId>
+	 *   <version>${tycho-version}</version>
+	 *   <configuration>
+	 *     <archive>
+	 *       <addMavenDescriptor>true</addMavenDescriptor>
+	 *     </archive>
+	 *   </configuration>
+	 * </plugin>
+	 * 
+ */ + @Parameter + private MavenArchiveConfiguration archive; + + /** + * The output directory of the jar file + * + * By default this is the Maven target/ directory. + */ + @Parameter(property = "project.build.directory") + private File outputDirectory; + + @Parameter(property = "project.basedir") + private File basedir; + + /** + * The path to the feature.xml file. + *

+ * Defaults to the feature.xml under the project's base directory. + */ + @Parameter(defaultValue = "${project.basedir}/" + FEATURE_XML) + private File featureFile; + + /** + * Name of the generated JAR. + */ + @Parameter(property = "project.build.finalName", alias = "jarName", required = true) + private String finalName; + + @Parameter(defaultValue = "${project.build.directory}/site") + private File target; + + /** + * Include the build timestamp in the manifest to track changes in the + * build-qualifier-aggregator mojo + */ + @Parameter(defaultValue = "true") + private boolean includeBuildTimestamp; + + @Component + private FeatureXmlTransformer featureXmlTransformer; + + @Component + private LicenseFeatureHelper licenseFeatureHelper; @Component private TargetPlatformService platformService; @@ -117,174 +126,175 @@ public class PackageFeatureMojo extends AbstractTychoPackagingMojo { @Component private BuildPropertiesParser buildPropertiesParser; - @Override - public void execute() throws MojoExecutionException, MojoFailureException { - synchronized (LOCK) { - outputDirectory.mkdirs(); + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + synchronized (LOCK) { + outputDirectory.mkdirs(); - if (!featureFile.isFile()) { - throw new MojoExecutionException("The featureFile parameter must represent a valid file"); - } + if (!featureFile.isFile()) { + throw new MojoExecutionException("The featureFile parameter must represent a valid file"); + } - Feature feature; + Feature feature; - try { - feature = Feature.read(featureFile); - } catch (final IOException e) { - throw new MojoExecutionException("Error reading " + featureFile, e); - } + try { + feature = Feature.read(featureFile); + } catch (final IOException e) { + throw new MojoExecutionException("Error reading " + featureFile, e); + } - File licenseFeature = licenseFeatureHelper.getLicenseFeature(feature, project); + File licenseFeature = licenseFeatureHelper.getLicenseFeature(feature, project); - updateLicenseProperties(feature, licenseFeature); + updateLicenseProperties(feature, licenseFeature); - File featureXml = new File(outputDirectory, FEATURE_XML); - try { - expandVersionQualifiers(feature); - Feature.write(feature, featureXml); - } catch (IOException e) { - throw new MojoExecutionException("Error updating feature.xml", e); - } + File featureXml = new File(outputDirectory, FEATURE_XML); + try { + expandVersionQualifiers(feature); + Feature.write(feature, featureXml); + } catch (IOException e) { + throw new MojoExecutionException("Error updating feature.xml", e); + } BuildProperties buildProperties = buildPropertiesParser.parse(DefaultReactorProject.adapt(project)); - checkBinIncludesExist(buildProperties); - - File featureProperties = getFeatureProperties(licenseFeature, buildProperties); - - File outputJar = new File(outputDirectory, finalName + ".jar"); - outputJar.getParentFile().mkdirs(); - - MavenArchiver archiver = new MavenArchiver(); - JarArchiver jarArchiver = getJarArchiver(); - archiver.setArchiver(jarArchiver); - archiver.setOutputFile(outputJar); - jarArchiver.setDestFile(outputJar); - - try { - // Additional file sets win over bin.includes ones, so we add them first - if (additionalFileSets != null) { - for (final var fileSet : additionalFileSets) { - final var directory = fileSet.getDirectory(); - - // noinspection ConstantConditions - if (directory != null && directory.isDirectory()) { - archiver.getArchiver().addFileSet(fileSet); - } - } - } - - archiver.getArchiver().addFileSet(getManuallyIncludedFiles(buildProperties)); - if (licenseFeature != null) { - archiver.getArchiver() - .addArchivedFileSet(licenseFeatureHelper.getLicenseFeatureFileSet(licenseFeature)); - } - archiver.getArchiver().addFile(featureXml, FEATURE_XML); - if (featureProperties != null) { - archiver.getArchiver().addFile(featureProperties, FEATURE_PROPERTIES); - } - if (archive == null) { - archive = new MavenArchiveConfiguration(); - archive.setAddMavenDescriptor(false); - } + checkBinIncludesExist(buildProperties); + + File featureProperties = getFeatureProperties(licenseFeature, buildProperties); + + File outputJar = new File(outputDirectory, finalName + ".jar"); + outputJar.getParentFile().mkdirs(); + + MavenArchiver archiver = createMavenArchiver(includeBuildTimestamp); + JarArchiver jarArchiver = getJarArchiver(); + archiver.setArchiver(jarArchiver); + archiver.setOutputFile(outputJar); + jarArchiver.setDestFile(outputJar); + + try { + // Additional file sets win over bin.includes ones, so we add them first + if (additionalFileSets != null) { + for (final var fileSet : additionalFileSets) { + final var directory = fileSet.getDirectory(); + + // noinspection ConstantConditions + if (directory != null && directory.isDirectory()) { + archiver.getArchiver().addFileSet(fileSet); + } + } + } + + archiver.getArchiver().addFileSet(getManuallyIncludedFiles(buildProperties)); + if (licenseFeature != null) { + archiver.getArchiver() + .addArchivedFileSet(licenseFeatureHelper.getLicenseFeatureFileSet(licenseFeature)); + } + archiver.getArchiver().addFile(featureXml, FEATURE_XML); + if (featureProperties != null) { + archiver.getArchiver().addFile(featureProperties, FEATURE_PROPERTIES); + } + if (archive == null) { + archive = new MavenArchiveConfiguration(); + archive.setAddMavenDescriptor(false); + } MavenProject mavenProject = project; archiver.createArchive(session, mavenProject, archive); - } catch (Exception e) { - throw new MojoExecutionException("Error creating feature package", e); - } - - project.getArtifact().setFile(outputJar); - } - } - - private void updateLicenseProperties(Feature feature, File licenseFeatureFile) { - // remove license feature id and version from feature.xml - feature.setLicenseFeature(null); - feature.setLicenseFeatureVersion(null); - // copy the license text and URL from the license feature - if (licenseFeatureFile != null) { - Feature licenseFeature = Feature.loadFeature(licenseFeatureFile); - if (licenseFeature.getLicenseURL() != null) { - feature.setLicenseURL(licenseFeature.getLicenseURL()); - } - if (licenseFeature.getLicense() != null) { - feature.setLicense(licenseFeature.getLicense()); - } - } - } - - private File getFeatureProperties(File licenseFeature, BuildProperties buildProperties) - throws MojoExecutionException { - try { - File localFeatureProperties = new File(basedir, FEATURE_PROPERTIES); - File targetFeatureProperties = new File(outputDirectory, FEATURE_PROPERTIES); - if (targetFeatureProperties.exists() && !targetFeatureProperties.delete()) { - throw new MojoExecutionException("Could not delete file " + targetFeatureProperties.getAbsolutePath()); - } - // copy the feature.properties from the current feature to the target directory - if (buildProperties.getBinIncludes().contains(FEATURE_PROPERTIES) && localFeatureProperties.canRead()) { - Files.copy(localFeatureProperties.toPath(), targetFeatureProperties.toPath()); - } - // if there is a license feature, append to the existing feature.properties or create - // a new one containing the license features's feature.properties content - if (licenseFeature != null) { - appendToOrAddFeatureProperties(targetFeatureProperties, licenseFeature); - } - if (targetFeatureProperties.exists()) { - return targetFeatureProperties; - } - return null; - } catch (IOException e) { - throw new MojoExecutionException("Could not create feature.properties file for project " + project, e); - } - } - - private void appendToOrAddFeatureProperties(File targetFeatureProperties, File licenseFeature) throws IOException { - try (ZipFile zip = new ZipFile(licenseFeature)) { - ZipEntry entry = zip.getEntry(FEATURE_PROPERTIES); - if (entry != null) { - try (InputStream inputStream = zip.getInputStream(entry); - FileWriter writer = new FileWriter(targetFeatureProperties.getAbsolutePath(), true)) { - // if we append, first add a new line to be sure that we start - // in a new line of the existing file - if (targetFeatureProperties.exists()) { - IOUtil.copy("\n", writer); - } - IOUtil.copy(inputStream, writer); - } - } - } - } - - /** - * @return A {@link FileSet} including files as configured by the bin.includes and - * bin.excludes properties without the files that are always included - * automatically. - */ - private FileSet getManuallyIncludedFiles(BuildProperties buildProperties) { - List binExcludes = new ArrayList<>(buildProperties.getBinExcludes()); - binExcludes.add(FEATURE_XML); // we'll include updated feature.xml - binExcludes.add(FEATURE_PROPERTIES); // we'll include updated feature.properties - return getFileSet(basedir, buildProperties.getBinIncludes(), binExcludes); - } - - private void expandVersionQualifiers(Feature feature) throws MojoFailureException { - ReactorProject reactorProject = DefaultReactorProject.adapt(project); - feature.setVersion(reactorProject.getExpandedVersion()); + } catch (Exception e) { + throw new MojoExecutionException("Error creating feature package", e); + } + + project.getArtifact().setFile(outputJar); + } + } + + private void updateLicenseProperties(Feature feature, File licenseFeatureFile) { + // remove license feature id and version from feature.xml + feature.setLicenseFeature(null); + feature.setLicenseFeatureVersion(null); + // copy the license text and URL from the license feature + if (licenseFeatureFile != null) { + Feature licenseFeature = Feature.loadFeature(licenseFeatureFile); + if (licenseFeature.getLicenseURL() != null) { + feature.setLicenseURL(licenseFeature.getLicenseURL()); + } + if (licenseFeature.getLicense() != null) { + feature.setLicense(licenseFeature.getLicense()); + } + } + } + + private File getFeatureProperties(File licenseFeature, BuildProperties buildProperties) + throws MojoExecutionException { + try { + File localFeatureProperties = new File(basedir, FEATURE_PROPERTIES); + File targetFeatureProperties = new File(outputDirectory, FEATURE_PROPERTIES); + if (targetFeatureProperties.exists() && !targetFeatureProperties.delete()) { + throw new MojoExecutionException("Could not delete file " + targetFeatureProperties.getAbsolutePath()); + } + // copy the feature.properties from the current feature to the target directory + if (buildProperties.getBinIncludes().contains(FEATURE_PROPERTIES) && localFeatureProperties.canRead()) { + Files.copy(localFeatureProperties.toPath(), targetFeatureProperties.toPath()); + } + // if there is a license feature, append to the existing feature.properties or + // create + // a new one containing the license features's feature.properties content + if (licenseFeature != null) { + appendToOrAddFeatureProperties(targetFeatureProperties, licenseFeature); + } + if (targetFeatureProperties.exists()) { + return targetFeatureProperties; + } + return null; + } catch (IOException e) { + throw new MojoExecutionException("Could not create feature.properties file for project " + project, e); + } + } + + private void appendToOrAddFeatureProperties(File targetFeatureProperties, File licenseFeature) throws IOException { + try (ZipFile zip = new ZipFile(licenseFeature)) { + ZipEntry entry = zip.getEntry(FEATURE_PROPERTIES); + if (entry != null) { + try (InputStream inputStream = zip.getInputStream(entry); + FileWriter writer = new FileWriter(targetFeatureProperties.getAbsolutePath(), true)) { + // if we append, first add a new line to be sure that we start + // in a new line of the existing file + if (targetFeatureProperties.exists()) { + IOUtil.copy("\n", writer); + } + IOUtil.copy(inputStream, writer); + } + } + } + } + + /** + * @return A {@link FileSet} including files as configured by the + * bin.includes and bin.excludes properties without + * the files that are always included automatically. + */ + private FileSet getManuallyIncludedFiles(BuildProperties buildProperties) { + List binExcludes = new ArrayList<>(buildProperties.getBinExcludes()); + binExcludes.add(FEATURE_XML); // we'll include updated feature.xml + binExcludes.add(FEATURE_PROPERTIES); // we'll include updated feature.properties + return getFileSet(basedir, buildProperties.getBinIncludes(), binExcludes); + } + + private void expandVersionQualifiers(Feature feature) throws MojoFailureException { + ReactorProject reactorProject = DefaultReactorProject.adapt(project); + feature.setVersion(reactorProject.getExpandedVersion()); TargetPlatform targetPlatform = platformService.getTargetPlatform(reactorProject).orElse(null); if (targetPlatform == null) { getLog().warn( - "Skipping version reference expansion in eclipse-feature project using the deprecated -Dtycho.targetPlatform configuration"); + "Skipping version reference expansion in eclipse-feature project using the deprecated -Dtycho.targetPlatform configuration"); return; } featureXmlTransformer.expandReferences(feature, targetPlatform); - } + } - private JarArchiver getJarArchiver() throws MojoExecutionException { - try { + private JarArchiver getJarArchiver() throws MojoExecutionException { + try { return plexus.lookup(JarArchiver.class, "jar"); - } catch (ComponentLookupException e) { - throw new MojoExecutionException("Unable to get JarArchiver", e); - } - } + } catch (ComponentLookupException e) { + throw new MojoExecutionException("Unable to get JarArchiver", e); + } + } } diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java index 59b3abbc49..a3c7f0118c 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java @@ -155,6 +155,13 @@ public class PackagePluginMojo extends AbstractTychoPackagingMojo { @Parameter(defaultValue = "true") private boolean deriveHeaderFromSource; + /** + * Include the build timestamp in the manifest to track changes in the + * build-qualifier-aggregator mojo + */ + @Parameter(defaultValue = "true") + private boolean includeBuildTimestamp; + @Component private SourceReferenceComputer soureReferenceComputer; @@ -224,7 +231,7 @@ private File makeJar(BuildOutputJar jar) throws MojoExecutionException { private File createPluginJar() throws MojoExecutionException { try { - MavenArchiver archiver = new MavenArchiver(); + MavenArchiver archiver = createMavenArchiver(includeBuildTimestamp); archiver.setArchiver(jarArchiver); File pluginFile = new File(buildDirectory, finalName + ".jar");