com.github.pms1.tppt.CreateFromDependenciesMojo Maven / Gradle / Ivy
package com.github.pms1.tppt;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ResolutionErrorHandler;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExclusionSetFilter;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.apache.maven.shared.dependency.graph.DependencyNode;
import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
import org.eclipse.osgi.util.ManifestElement;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import com.github.pms1.tppt.jaxb.Plugin;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import aQute.bnd.osgi.Builder;
import aQute.bnd.osgi.Jar;
import aQute.bnd.version.MavenVersion;
import aQute.bnd.version.Version;
/**
* A maven mojo for creating a p2 repository from maven dependencies
*
* @author pms1
**/
@Mojo(name = "create-from-dependencies", requiresDependencyResolution = ResolutionScope.COMPILE)
public class CreateFromDependenciesMojo extends AbstractMojo {
@Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true)
private File target;
@Parameter(defaultValue = "${project}", readonly = true, required = true)
private MavenProject project;
@Parameter(defaultValue = "${mojoExecution}", readonly = true)
private MojoExecution mojoExecution;
@Component
private RepositorySystem repositorySystem;
@Parameter(readonly = true, required = true, defaultValue = "${project.remoteArtifactRepositories}")
private List remoteRepositories;
@Parameter(readonly = true, required = true, defaultValue = "${localRepository}")
private ArtifactRepository localRepository;
@Parameter(readonly = true, required = true, defaultValue = "${project.basedir}/src/main/bnd")
private File sourceDir;
@Component
private EquinoxRunnerFactory runnerFactory;
@Component
private TychoArtifactUnpacker installer;
@Component
private ResolutionErrorHandler resolutionErrorHandler;
@Component(hint = "default")
private DependencyGraphBuilder dependencyGraphBuilder;
@Parameter(property = "session", readonly = true)
private MavenSession session;
@Parameter
private ArtifactFilter exclusionTransitives = new ExclusionSetFilter(Collections.emptySet());
public void setExclusionTransitives(String[] exclusionTransitives) {
this.exclusionTransitives = new ExclusionSetFilter(exclusionTransitives);
}
@Parameter
private ArtifactFilter exclusions = new ExclusionSetFilter(Collections.emptySet());
public void setExclusions(String[] exclusions) {
this.exclusions = new ExclusionSetFilter(exclusions);
}
static Plugin scanPlugin(Path p) throws IOException, BundleException, MojoExecutionException {
long uncompressedSize = 0;
Map manifest = null;
// Cannot use ZipInputStream since that leaves getCompressedSize() and
// getSize() of ZipEntry unfilled
try (ZipFile zf = new ZipFile(p.toFile())) {
for (Enumeration extends ZipEntry> e = zf.entries(); e.hasMoreElements();) {
ZipEntry entry = e.nextElement();
if (entry.getSize() == -1)
throw new Error();
uncompressedSize += entry.getSize();
boolean isManifest = entry.getName().equals("META-INF/MANIFEST.MF");
if (isManifest) {
if (manifest != null)
throw new IllegalStateException();
manifest = ManifestElement.parseBundleManifest(zf.getInputStream(entry), null);
}
}
}
if (manifest == null)
return null;
ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME,
manifest.get(Constants.BUNDLE_SYMBOLICNAME));
if (elements == null)
return null;
if (elements.length != 1)
throw new MojoExecutionException(
"Unhandled: malformed " + Constants.BUNDLE_SYMBOLICNAME + " header: " + Arrays.toString(elements));
Plugin result = new Plugin();
result.id = elements[0].getValue();
elements = ManifestElement.parseHeader(Constants.BUNDLE_VERSION, manifest.get(Constants.BUNDLE_VERSION));
if (elements.length != 1)
throw new MojoExecutionException(
"Unhandled: malformed " + Constants.BUNDLE_VERSION + " header: " + Arrays.toString(elements));
result.version = elements[0].getValue();
result.download_size = Files.size(p) / 1024;
result.install_size = uncompressedSize / 1024;
return result;
}
/**
* Headers that are in regular bundles, but must not be in source bundles.
*/
private final static List binaryHeaders;
static {
List names = new LinkedList<>();
names.add(Name.MAIN_CLASS);
Arrays.asList(Constants.IMPORT_PACKAGE, //
Constants.REQUIRE_BUNDLE, //
Constants.REQUIRE_CAPABILITY, //
Constants.PROVIDE_CAPABILITY, //
Constants.EXPORT_PACKAGE) //
.stream() //
.map(p -> new Name(p)) //
.forEach(names::add);
binaryHeaders = Collections.unmodifiableList(names);
}
// Implementation-Title: hibernate-core
// Implementation-Version: 5.2.3.Final
// Specification-Vendor: Hibernate.org
// Specification-Title: hibernate-core
// Implementation-Vendor-Id: org.hibernate
// Implementation-Vendor: Hibernate.org
// Bundle-Name: hibernate-core
// Bundle-Version: 5.2.3.Final
// Specification-Version: 5.2.3.Final
// Implementation-Url: http://hibernate.org
public void execute() throws MojoExecutionException, MojoFailureException {
final Path repoDependencies = target.toPath().resolve("repository-source");
final Path repoDependenciesPlugins = repoDependencies.resolve("plugins");
final Path repoOut = target.toPath().resolve("repository");
try {
Files.createDirectories(repoDependenciesPlugins);
List plugins = new LinkedList<>();
ProjectBuildingRequest pbRequest = new DefaultProjectBuildingRequest();
pbRequest.setLocalRepository(localRepository);
pbRequest.setProject(project);
pbRequest.setRemoteRepositories(remoteRepositories);
pbRequest.setRepositorySession(session.getRepositorySession());
pbRequest.setResolveDependencies(true);
pbRequest.setResolveVersionRanges(true);
DependencyNode n = dependencyGraphBuilder.buildDependencyGraph(pbRequest, null);
Set artifacts = new HashSet<>();
n.accept(new DependencyNodeVisitor() {
@Override
public boolean visit(DependencyNode node) {
if (node.getArtifact() != project.getArtifact() && exclusions.include(node.getArtifact())) {
artifacts.add(node.getArtifact());
}
return exclusionTransitives.include(node.getArtifact());
}
@Override
public boolean endVisit(DependencyNode node) {
return true;
}
});
for (Artifact a : artifacts) {
Plugin plugin = scanPlugin(a.getFile().toPath());
Path receipe = findReceipe(a);
if (plugin == null || receipe != null) {
plugin = createPlugin(a, receipe,
repoDependenciesPlugins.resolve(a.getFile().toPath().getFileName()));
} else {
Files.copy(a.getFile().toPath(),
repoDependenciesPlugins.resolve(a.getFile().toPath().getFileName()));
}
if (plugin == null)
throw new Error();
plugins.add(plugin);
// try to find artifacts with "sources" qualifier
Artifact sourcesArtifact = repositorySystem.createArtifactWithClassifier(a.getGroupId(),
a.getArtifactId(), a.getVersion(), "jar", "sources");
ArtifactResolutionRequest request = new ArtifactResolutionRequest();
request.setArtifact(sourcesArtifact);
request.setRemoteRepositories(remoteRepositories);
request.setLocalRepository(localRepository);
ArtifactResolutionResult resolution = repositorySystem.resolve(request);
switch (resolution.getArtifacts().size()) {
case 0:
// no sources: sad, but ok
break;
case 1:
Artifact sourceArtifact = Iterables.getOnlyElement(resolution.getArtifacts());
Path out1 = repoDependenciesPlugins.resolve(sourceArtifact.getFile().toPath().getFileName());
createSourceBundle(plugin, sourceArtifact.getFile().toPath(), out1);
plugins.add(scanPlugin(out1));
break;
default:
throw new MojoExecutionException(
"Unhandled: multiple source artifacts: " + resolution.getArtifacts());
}
}
int exitCode = createRunner().run("-application",
"org.eclipse.equinox.p2.publisher.FeaturesAndBundlesPublisher", "-source",
repoDependencies.toString(), //
"-metadataRepository", repoOut.toUri().toURL().toExternalForm(), //
"-artifactRepository", repoOut.toUri().toURL().toExternalForm(), //
"-publishArtifacts", //
"-append", "true", //
"-metadataRepositoryName", project.getName(), //
"-artifactRepositoryName", project.getName());
if (exitCode != 0)
throw new MojoExecutionException("fab failed: exitCode=" + exitCode);
Files.write(repoOut.resolve("p2.index"),
"version = 1\rmetadata.repository.factory.order = content.xml,\\!\rartifact.repository.factory.order = artifacts.xml,\\!\r"
.getBytes(StandardCharsets.US_ASCII));
} catch (MojoExecutionException e) {
throw e;
} catch (Exception e) {
throw new MojoExecutionException("mojo failed: " + e.getMessage(), e);
}
}
private void createSourceBundle(Plugin plugin, Path bundle, Path out1) throws Exception {
Manifest mf = null;
try (ZipFile zf = new ZipFile(bundle.toFile())) {
ZipEntry e = zf.getEntry("META-INF/MANIFEST.MF");
if (e != null)
mf = new Manifest(zf.getInputStream(e));
}
boolean sourceHasHeaders = false;
if (mf != null) {
String sourceBundle = mf.getMainAttributes().getValue("Eclipse-SourceBundle");
if (sourceBundle != null) {
ManifestElement[] elements = ManifestElement.parseHeader("Eclipse-SourceBundle", sourceBundle);
if (elements.length != 1)
throw new MojoExecutionException(
"Unhandled: malformed Eclipse-SourceBundle header: " + sourceBundle);
String sbundle = elements[0].getValue();
String sversion = elements[0].getAttribute("version");
if (!Objects.equals(plugin.id, sbundle))
throw new MojoExecutionException(
"Unhandled: different bundle in Eclipse-SourceBundle header: " + plugin.id + " " + sbundle);
if (!Objects.equals(plugin.version, sversion))
throw new MojoExecutionException("Unhandled: different version in Eclipse-SourceBundle header: "
+ plugin.version + " " + sversion);
sourceHasHeaders = true;
}
}
boolean requireCleanup = false;
if (mf != null) {
Manifest fmf = mf;
requireCleanup = binaryHeaders.stream().anyMatch(p -> fmf.getMainAttributes().getValue(p) != null);
}
if (sourceHasHeaders && !requireCleanup) {
Files.copy(bundle, out1);
} else {
if (mf == null) {
mf = new Manifest();
// This is a required header
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
}
for (Name h : binaryHeaders)
mf.getMainAttributes().remove(h);
mf.getMainAttributes().putValue(Constants.BUNDLE_MANIFESTVERSION, "2");
mf.getMainAttributes().putValue(Constants.BUNDLE_SYMBOLICNAME, plugin.id + ".source");
mf.getMainAttributes().putValue(Constants.BUNDLE_VERSION, plugin.version);
mf.getMainAttributes().putValue("Eclipse-SourceBundle", plugin.id + ";version=\"" + plugin.version + "\"");
try (JarFile zf = new JarFile(bundle.toFile());
OutputStream os = Files.newOutputStream(out1);
JarOutputStream jar = new JarOutputStream(os, mf)) {
Set names = new HashSet<>();
for (Enumeration extends ZipEntry> e = zf.entries(); e.hasMoreElements();) {
ZipEntry e1 = e.nextElement();
if (e1.getName().equals("META-INF/MANIFEST.MF"))
continue;
if (names.add(e1.getName())) {
jar.putNextEntry(new JarEntry(e1.getName()));
long copied = ByteStreams.copy(zf.getInputStream(e1), jar);
if (e1.getSize() != -1 && copied != e1.getSize())
throw new MojoExecutionException("Error while copying entry '" + e1 + "': size should be "
+ e1.getSize() + ", but copied " + copied);
} else {
getLog().warn(bundle + " contains multiple entries for '" + e1.getName()
+ "'. Keeping the first and ignoring subsequent entries with the same name.");
}
}
} catch (IOException e) {
throw new MojoExecutionException("Failed creating source bundle '" + out1 + "' from '" + bundle + "'",
e);
}
}
}
private Path findReceipe(Artifact a) {
Path noVersionPath = sourceDir.toPath().resolve(a.getGroupId()).resolve(a.getArtifactId());
Path versionPath = noVersionPath.resolve(a.getVersion());
for (Path path : new Path[] { versionPath, noVersionPath }) {
Path bndFile = path.resolve("bnd.bnd");
if (Files.isReadable(bndFile))
return bndFile;
}
return null;
}
private Plugin createPlugin(Artifact a, Path receipe, Path target) throws Exception {
try (Builder builder = new Builder()) {
builder.setTrace(getLog().isDebugEnabled());
Jar classesDirJar = new Jar(a.getFile());
if (receipe != null)
builder.setProperties(receipe.getParent().toFile(), builder.loadProperties(receipe.toFile()));
if (receipe != null)
getLog().info(a + ": Creating an OSGi bundle using '" + sourceDir.toPath().relativize(receipe) + "'");
else
getLog().info(a + ": Creating an OSGi bundle");
if (builder.getProperty(Constants.BUNDLE_SYMBOLICNAME) == null)
builder.setProperty(Constants.BUNDLE_SYMBOLICNAME, a.getArtifactId());
if (builder.getProperty(Constants.BUNDLE_VERSION) == null) {
Version version = MavenVersion.parseString(a.getVersion()).getOSGiVersion();
builder.setProperty(Constants.BUNDLE_VERSION, version.toString());
}
if (builder.getProperty(Constants.EXPORT_PACKAGE) == null) {
builder.setProperty(Constants.EXPORT_PACKAGE, "*");
}
builder.setJar(classesDirJar);
Jar j = builder.build();
j.write(target.toFile());
return scanPlugin(target);
}
}
private List getPluginRepositories(MavenSession session) {
List repositories = new ArrayList<>();
for (MavenProject project : session.getProjects()) {
repositories.addAll(project.getPluginArtifactRepositories());
}
return repositorySystem.getEffectiveRepositories(repositories);
}
private Artifact resolveDependency(MavenSession session, Artifact artifact) throws MavenExecutionException {
ArtifactResolutionRequest request = new ArtifactResolutionRequest();
request.setArtifact(artifact);
request.setResolveRoot(true);
request.setResolveTransitively(false);
request.setLocalRepository(session.getLocalRepository());
request.setRemoteRepositories(getPluginRepositories(session));
request.setOffline(session.isOffline());
request.setProxies(session.getSettings().getProxies());
request.setForceUpdate(session.getRequest().isUpdateSnapshots());
ArtifactResolutionResult result = repositorySystem.resolve(request);
try {
resolutionErrorHandler.throwErrors(request, result);
} catch (ArtifactResolutionException e) {
throw new MavenExecutionException("Could not resolve artifact for Tycho's OSGi runtime", e);
}
return artifact;
}
EquinoxRunner runner;
EquinoxRunner createRunner() throws IOException, MavenExecutionException {
if (runner == null) {
Artifact platform = resolveDependency(session,
repositorySystem.createArtifact("org.eclipse.tycho", "tycho-bundles-external", "1.0.0", "zip"));
Path p = installer.addRuntimeArtifact(session, platform);
runner = runnerFactory.newBuilder().withInstallation(p).build();
}
return runner;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy