Skip to content

Commit

Permalink
Issue eclipse-tycho#611 Support setting CI-Friendly-Versions in
Browse files Browse the repository at this point in the history
tycho-build-extension

Add a first implementation and integration test
  • Loading branch information
laeubi committed May 2, 2022
1 parent 16cb813 commit 9c2e3de
Show file tree
Hide file tree
Showing 24 changed files with 609 additions and 26 deletions.
55 changes: 55 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,61 @@ This page describes the noteworthy improvements provided by each release of Ecli

## 3.0.0 (under development)

### Enhanced Support for Maven CI Friendly Versions

Starting with Maven 3.8.5 Tycho now supports an enhanced form of the [Maven CI Friendly Versions](https://maven.apache.org/maven-ci-friendly.html) beside the standard properties names one could also use:

- releaseVersion
- major
- minor
- micro
- qualifier

These uses the usual semantics that you can use them in a version string e.g. `<version>${releaseVersion}${qualifier}</version>` and pass them on the commandline.

Beside this, Tycho supports some useful default calculation for `qualifier` if you gives a format on the commandline with `-Dtycho.buildqualifier.format=yyyyMMddHHmm`
(or [any other format supported}(https://www.eclipse.org/tycho/sitedocs/tycho-packaging-plugin/build-qualifier-mojo.html#format)) Tycho automatically will set the build qualifier also availablee in your maven model!

That way you can configure your pom in the follwoing way:
```
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>...</groupId>
<artifactId>...</artifactId>
<packaging>pom</packaging>
<version>1.0.0${qualifier}</version>
<properties>
<!-- Defines the default Qualifier if no format is given-->
<qualifier>-SNAPSHOT</qualifier>
...
</properties>
...
</project>
```

What will result in the usual `1.0.0-SNAPSHOT` for a regular `mvn clean install`, if you want to do a release, you can now simply call `mvn -Dtycho.buildqualifier.format=yyyyMMddHHmm clean deploy`
and your artifact will get the `1.0.0-<formated qualifier>` version when published! This also is supported if you use pomless build.

To use this new feature you need to enable the tycho-build extension with the `.mvn/extensions.xml` file in the root of your project directory:
```
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<extension>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-build</artifactId>
<version>${tycho-version}</version>
</extension>
<!-- possibly other extension here -->
</extensions>
```
please note that we here use another new feature from Maven 3.8.5, where one could use properties from the file `.mvn/maven.config` in your `.mvn/extensions.xml` file, so if you put in this:
```
-Dtycho-version=3.0.0-SNAPSHOT
# probably add more here ..
```

You can now control your Tycho version for `.mvn/extensions.xml` and your `pom.xml` in one place and still override it on the commandline with `-Dtycho-version=...`

### Support for PDE Declarative Component Annotation progressing

One can enable either global or per project the generation of component xmls in PDE. Until now it was required for Tycho to still import the annotation package even though `classpath=true` was set, beside that one needs to check in the generated xmls.
Expand Down
10 changes: 10 additions & 0 deletions tycho-build/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
<artifactId>org.eclipse.tycho.core.shared</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.tycho.extras</groupId>
<artifactId>tycho-buildtimestamp-jgit</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-embedder-api</artifactId>
<version>${project.version}</version>
</dependency>
<!--
<dependency>
<groupId>org.eclipse.tycho</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*******************************************************************************
* Copyright (c) 2012, 2016 Sonatype Inc. and others.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sonatype Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.build;

import java.util.Date;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;

/**
* Build timestamp provider that returns the same timestamp for all projects, the
* ${maven.build.timestamp}.
*/
@Component(role = BuildTimestampProvider.class, hint = DefaultBuildTimestampProvider.ROLE_HINT)
public class DefaultBuildTimestampProvider implements BuildTimestampProvider {

static final String ROLE_HINT = "default";

@Override
public Date getTimestamp(MavenSession session, MavenProject project, MojoExecution execution) {
return session.getStartTime();
}

@Override
public void setQuiet(boolean quiet) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*******************************************************************************
* Copyright (c) 2022 Christoph Läubrich and others.
* 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.build;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Priority;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.interpolation.DefaultModelVersionProcessor;
import org.apache.maven.model.interpolation.ModelVersionProcessor;
import org.apache.maven.model.io.DefaultModelReader;
import org.apache.maven.plugin.LegacySupport;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

@Priority(100)
@Named
public class TychoCiFriendlyVersions extends DefaultModelVersionProcessor implements ModelVersionProcessor {

static final String BUILD_QUALIFIER = "qualifier";
static final String MICRO_VERSION = "micro";
static final String MINOR_VERSION = "minor";
static final String MAJOR_VERSION = "major";
static final String RELEASE_VERSION = "releaseVersion";
private PlexusContainer container;
private Logger logger;
private Map<File, MavenProject> rawProjectCache = new ConcurrentHashMap<>();

@Inject
public TychoCiFriendlyVersions(PlexusContainer plexusContainer, Logger logger) {
this.container = plexusContainer;
this.logger = logger;

}

@Override
public boolean isValidProperty(String property) {
return super.isValidProperty(property) || MAJOR_VERSION.equals(property) || MINOR_VERSION.equals(property)
|| MICRO_VERSION.equals(property) || BUILD_QUALIFIER.equals(property)
|| RELEASE_VERSION.equals(property);
}

@Override
public void overwriteModelProperties(Properties modelProperties, ModelBuildingRequest request) {
super.overwriteModelProperties(modelProperties, request);
if (request.getSystemProperties().containsKey(MAJOR_VERSION)) {
modelProperties.put(MAJOR_VERSION, request.getSystemProperties().get(MAJOR_VERSION));
}
if (request.getSystemProperties().containsKey(MINOR_VERSION)) {
modelProperties.put(MINOR_VERSION, request.getSystemProperties().get(MINOR_VERSION));
}
if (request.getSystemProperties().containsKey(MICRO_VERSION)) {
modelProperties.put(MICRO_VERSION, request.getSystemProperties().get(MICRO_VERSION));
}
if (request.getSystemProperties().containsKey(BUILD_QUALIFIER)) {
modelProperties.put(BUILD_QUALIFIER, request.getSystemProperties().get(BUILD_QUALIFIER));
} else {
String formatString = request.getSystemProperties().getProperty("tycho.buildqualifier.format");
if (formatString != null) {
Date startTime = request.getBuildStartTime();
File pomFile = request.getPomFile();
if (startTime != null && pomFile != null) {
String provider = request.getSystemProperties().getProperty("tycho.buildqualifier.provider",
"default");
try {
BuildTimestampProvider timestampProvider = container.lookup(BuildTimestampProvider.class,
provider);
SimpleDateFormat format = new SimpleDateFormat(formatString);
format.setTimeZone(TimeZone.getTimeZone("UTC"));
MavenProject mavenProject = getMavenProject(pomFile);
timestampProvider.setQuiet(true);
try {
Date timestamp = timestampProvider.getTimestamp(getMavenSession(request), mavenProject,
getExecution(mavenProject));
String qualifier = format.format(timestamp);
modelProperties.put(BUILD_QUALIFIER, "." + qualifier);
} finally {
timestampProvider.setQuiet(false);
}
} catch (ComponentLookupException | MojoExecutionException e) {
logger.warn("Can't use '" + provider
+ "' as a timestamp provider for tycho-ci-friendly-versions (" + e + ")");
}

}
}
}

}

private MojoExecution getExecution(MavenProject mavenProject) {

Plugin projectPlugin = getPlugin("org.eclipse.tycho:tycho-packaging-plugin", mavenProject);
if (projectPlugin == null) {
// create a dummy
projectPlugin = new Plugin();
projectPlugin.setGroupId("org.eclipse.tycho");
projectPlugin.setArtifactId("tycho-packaging-plugin");
projectPlugin.setConfiguration(
"<configuration><jgit.dirtyWorkingTree>ignore</jgit.dirtyWorkingTree></configuration>");
}
Object configuration = projectPlugin.getConfiguration();
if (configuration != null) {
String xml = configuration.toString();
try {
projectPlugin.setConfiguration(Xpp3DomBuilder.build(new StringReader(xml)));
} catch (XmlPullParserException | IOException e) {
projectPlugin.setConfiguration(null);
}
}
return new MojoExecution(projectPlugin, "", "");
}

private Plugin getPlugin(String id, MavenProject mavenProject) {
if (mavenProject == null) {
return null;
}
Plugin plugin = mavenProject.getPlugin(id);
if (plugin == null) {
return getPlugin(id, mavenProject.getParent());
}
return plugin;
}

private MavenProject getMavenProject(File pom) {

MavenProject project = rawProjectCache.computeIfAbsent(pom, file -> {
// at this phase there are no projects, thats all we can offer for now...
MavenProject mavenProject = new MavenProject();
DefaultModelReader modelReader = new DefaultModelReader();
try {
mavenProject.setModel(modelReader.read(pom, Map.of()));
} catch (IOException e) {
// nothing to do here then...
}
mavenProject.setFile(pom);
return mavenProject;
});
Model model = project.getModel();
if (model != null) {
Parent parent = model.getParent();
if (parent != null) {
File parentPom = new File(pom.getParentFile(), parent.getRelativePath());
if (parentPom.isFile()) {
project.setParent(getMavenProject(parentPom));
}
}
}
return project;

}

private MavenSession getMavenSession(ModelBuildingRequest request) {
try {
LegacySupport legacySupport = container.lookup(LegacySupport.class);
MavenSession session = legacySupport.getSession();
if (session != null) {
return session;
}
} catch (ComponentLookupException e) {
// fall through
}
// create a dummy session... actually time providers are not really interested
// in *all* but very limited details
DefaultMavenExecutionRequest executionRequest = new DefaultMavenExecutionRequest();
executionRequest.setBaseDirectory(request.getPomFile().getParentFile());
executionRequest.setStartTime(request.getBuildStartTime());
return new MavenSession(container, executionRequest, new DefaultMavenExecutionResult(),
Collections.emptyList());
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Christoph Läubrich and others.
* Copyright (c) 2021, 2022 Christoph Läubrich and others.
* 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
Expand Down Expand Up @@ -55,7 +55,6 @@
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.equinox.internal.p2.director.DirectorActivator;
import org.eclipse.equinox.internal.p2.publisher.eclipse.FeatureParser;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
Expand All @@ -72,8 +71,10 @@
import org.eclipse.tycho.p2.util.resolution.ProjectorResolutionStrategy;
import org.eclipse.tycho.p2.util.resolution.ResolutionDataImpl;
import org.eclipse.tycho.p2.util.resolution.ResolverException;
import org.eclipse.tycho.pomless.AbstractTychoMapping;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.sonatype.maven.polyglot.mapping.Mapping;

@Component(role = GraphBuilder.class, hint = GraphBuilder.HINT)
public class TychoGraphBuilder extends DefaultGraphBuilder {
Expand All @@ -90,8 +91,22 @@ public class TychoGraphBuilder extends DefaultGraphBuilder {
@Requirement(hint = "plexus")
private BundleContext bundleContext;

@Requirement(role = Mapping.class)
private Map<String, Mapping> polyglotMappings;

@Override
public Result<ProjectDependencyGraph> build(MavenSession session) {
// Tell the polyglot mappings that we are in extension mode
for (Mapping mapping : polyglotMappings.values()) {
if (mapping instanceof AbstractTychoMapping) {
AbstractTychoMapping tychoMapping = (AbstractTychoMapping) mapping;
tychoMapping.setExtensionMode(true);
tychoMapping.setMultiModuleProjectDirectory(session.getRequest().getMultiModuleProjectDirectory());
if (session.getRequest().getSystemProperties().getProperty("tycho.buildqualifier.format") != null) {
tychoMapping.setSnapshotFormat("${" + TychoCiFriendlyVersions.BUILD_QUALIFIER + "}");
}
}
}
MavenExecutionRequest request = session.getRequest();
ProjectDependencyGraph dependencyGraph = session.getProjectDependencyGraph();
Result<ProjectDependencyGraph> graphResult = super.build(session);
Expand Down Expand Up @@ -201,9 +216,10 @@ public Result<ProjectDependencyGraph> build(MavenSession session) {
.distinct()//
.peek(project -> loggerAdapter.debug(" + add upstream project '" + project.getName()
+ "' of project '" + projectRequest.mavenProject.getName() + "'..."))//
// make behaviors are both false here as projectDependenciesMap includes transitive already
.forEach(
project -> queue.add(new ProjectRequest(project, false, false, projectRequest)));
// make behaviors are both false here as projectDependenciesMap includes
// transitive already
.forEach(project -> queue
.add(new ProjectRequest(project, false, false, projectRequest)));
}
if (projectRequest.requestDownstream) {
projectDependenciesMap.entrySet().stream()//
Expand All @@ -217,7 +233,8 @@ public Result<ProjectDependencyGraph> build(MavenSession session) {
.distinct()//
.peek(project -> loggerAdapter.debug(" + add downstream project '" + project.getName()
+ "' of project '" + projectRequest.mavenProject.getName() + "'..."))//
// request dependencies of dependants, otherwise, -amd would not be able to produce a satisfiable build graph
// request dependencies of dependants, otherwise, -amd would not be able to
// produce a satisfiable build graph
.forEach(
project -> queue.add(new ProjectRequest(project, false, true, projectRequest)));
}
Expand Down
Loading

0 comments on commit 9c2e3de

Please sign in to comment.