Skip to content

Commit

Permalink
Add support for building sub-bundle with tycho-bnd-extension
Browse files Browse the repository at this point in the history
Currently the tycho-build bnd extension do not correctly handle
subbundles in the following way:

1. they are not build
2. they are not reflected in the maven metadata

this also revealed some confusion with the generated polyglot files.

Now there is a new initialize mojo that cleanup and creates a consumer
pom, sub bundles are build and represented as attached artifacts to have
multiple ones and are correctly reflected now.
  • Loading branch information
laeubi committed Jan 31, 2025
1 parent 4f4009b commit f48b6c3
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ bin/
*.iml
.idea/
.tycho-consumer-pom.xml
.polyglot.*
## Eclipse metadata files
org.eclipse.core.resources.prefs
org.eclipse.m2e.core.prefs
Expand All @@ -21,3 +22,4 @@ pom-model-classic.xml
project-dependencies.xml
project-units.xml
requirements.txt
.DS_Store
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;

import org.apache.maven.AbstractMavenLifecycleParticipant;
import org.apache.maven.MavenExecutionException;
Expand All @@ -36,6 +37,7 @@
import org.eclipse.tycho.core.bnd.BndPluginManager;

import aQute.bnd.build.Project;
import aQute.bnd.build.SubProject;
import aQute.bnd.build.Workspace;
import aQute.bnd.osgi.Constants;

Expand Down Expand Up @@ -69,46 +71,66 @@ public class BndMavenLifecycleParticipant extends AbstractMavenLifecycleParticip
@Override
public void afterProjectsRead(MavenSession session) throws MavenExecutionException {
Map<MavenProject, Project> bndProjects = getProjects(session);
Map<String, MavenProject> manifestFirstProjects = getManifestFirstProjects(session, bndProjects.keySet());
Map<String, MavenProject> bndWorkspaceProjects = bndProjects.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue().getName(), Entry<MavenProject, Project>::getKey, (a, b) -> {
logger.warn(
"Your reactor build contains duplicate BND projects from different workspace, build order might be insufficient!");
logger.warn("\tProject 1 (selected): " + a.getBasedir());
logger.warn("\tProject 2 (ignored): " + b.getBasedir());
return a;
}));
Map<String, BndMavenProject> manifestFirstProjects = getManifestFirstProjects(session, bndProjects.keySet());
Map<String, BndMavenProject> bndWorkspaceProjects = new HashMap<>();
for (Entry<MavenProject, Project> entry : bndProjects.entrySet()) {
MavenProject mavenProject = entry.getKey();
Project project = entry.getValue();
logger.debug("==" + mavenProject.getId() + "==");
List<SubProject> subProjects = project.getSubProjects();
logger.debug("Main: " + project.getName());
if (subProjects.isEmpty()) {
bndWorkspaceProjects.put(project.getName(), new BndMavenProject(mavenProject, project, null));
} else {
for (SubProject subProject : subProjects) {
logger.debug("Sub: " + subProject.getName());
bndWorkspaceProjects.put(project.getName() + "." + subProject.getName(),
new BndMavenProject(mavenProject, project, subProject.getName()));
}
}
}
Map<MavenProject, Set<String>> dependencyMap = new HashMap<>();
for (Entry<MavenProject, Project> entry : bndProjects.entrySet()) {
MavenProject mavenProject = entry.getKey();
Set<String> added = getProjectSet(mavenProject, dependencyMap);
Project bndProject = entry.getValue();
try {

for (Entry<String, String> mapping : BND_TO_MAVEN_MAPPING) {
Set<String> requirements = bndProject.getMergedParameters(mapping.getKey()).keySet();
String mavenScope = mapping.getValue();
for (String required : requirements) {
MavenProject requiredMavenProject = bndWorkspaceProjects.get(required);
if (requiredMavenProject == null) {
requiredMavenProject = manifestFirstProjects.get(required);
BndMavenProject bndMavenProject = bndWorkspaceProjects.get(required);
if (bndMavenProject == null) {
bndMavenProject = manifestFirstProjects.get(required);
}
if (requiredMavenProject == null || requiredMavenProject == mavenProject) {
if (bndMavenProject == null || bndMavenProject.mavenProject() == mavenProject) {
continue;
}
MavenProject requiredMavenProject = bndMavenProject.mavenProject();
logger.debug(mavenProject.getId() + " depends on reactor project "
+ requiredMavenProject.getId() + " ...");
Dependency dependency = new Dependency();
dependency.setGroupId(requiredMavenProject.getGroupId());
dependency.setArtifactId(requiredMavenProject.getArtifactId());
dependency.setVersion(requiredMavenProject.getVersion());
dependency.setScope(mavenScope);
mavenProject.getDependencies().add(dependency);
if (bndMavenProject.classifier() != null) {
Dependency clone = dependency.clone();
clone.setClassifier(bndMavenProject.classifier());
clone.setType("jar");
addDependency(mavenProject, clone, added);
}
dependency.setType(requiredMavenProject.getPackaging());
addDependency(mavenProject, dependency, added);
}
}
} catch (Exception e) {
logError("Can't get dependents of project " + mavenProject.getId(), e);
}
}
for (MavenProject mavenProject : manifestFirstProjects.values()) {
for (BndMavenProject bndMavenProject : manifestFirstProjects.values()) {
MavenProject mavenProject = bndMavenProject.mavenProject();
Set<String> added = getProjectSet(mavenProject, dependencyMap);
try {
File file = new File(mavenProject.getBasedir(), "build.properties");
if (file.isFile()) {
Expand All @@ -121,17 +143,17 @@ public void afterProjectsRead(MavenSession session) throws MavenExecutionExcepti
continue;
}
for (String bundle : property.split(",")) {
MavenProject requiredMavenProject = bndWorkspaceProjects.get(bundle.trim());
if (requiredMavenProject == null || requiredMavenProject == mavenProject) {
BndMavenProject requiredMavenProject = bndWorkspaceProjects.get(bundle.trim());
if (requiredMavenProject == null || requiredMavenProject.mavenProject() == mavenProject) {
continue;
}
logger.debug(mavenProject.getId() + " depends on reactor project "
+ requiredMavenProject.getId() + " ...");
+ requiredMavenProject.mavenProject().getId() + " ...");
Dependency dependency = new Dependency();
dependency.setGroupId(requiredMavenProject.getGroupId());
dependency.setArtifactId(requiredMavenProject.getArtifactId());
dependency.setVersion(requiredMavenProject.getVersion());
mavenProject.getDependencies().add(dependency);
dependency.setGroupId(requiredMavenProject.mavenProject().getGroupId());
dependency.setArtifactId(requiredMavenProject.mavenProject().getArtifactId());
dependency.setVersion(requiredMavenProject.mavenProject().getVersion());
addDependency(mavenProject, dependency, added);
}
}
} catch (Exception e) {
Expand All @@ -141,8 +163,8 @@ public void afterProjectsRead(MavenSession session) throws MavenExecutionExcepti
}
}

private Map<String, MavenProject> getManifestFirstProjects(MavenSession session, Set<MavenProject> existing) {
Map<String, MavenProject> result = new HashMap<String, MavenProject>();
private Map<String, BndMavenProject> getManifestFirstProjects(MavenSession session, Set<MavenProject> existing) {
Map<String, BndMavenProject> result = new HashMap<>();
for (MavenProject mavenProject : session.getProjects()) {
if (existing.contains(mavenProject)) {
continue;
Expand All @@ -155,7 +177,7 @@ private Map<String, MavenProject> getManifestFirstProjects(MavenSession session,
String value = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
if (value != null) {
String bsn = value.split(";", 2)[0];
result.put(bsn, mavenProject);
result.put(bsn, new BndMavenProject(mavenProject, null, null));
}
} catch (Exception e) {
logError("Can't read project " + mavenProject.getId(), e);
Expand Down Expand Up @@ -208,4 +230,21 @@ private void logError(String msg, Exception e) {
}
}

private static Set<String> getProjectSet(MavenProject mavenProject, Map<MavenProject, Set<String>> dependencyMap) {
Set<String> set = dependencyMap.computeIfAbsent(mavenProject, nil -> new HashSet<>());
mavenProject.getDependencies().stream().map(d -> getKey(d)).forEach(set::add);
return set;
}

private static void addDependency(MavenProject mavenProject, Dependency dependency, Set<String> added) {
if (added.add(getKey(dependency))) {
mavenProject.getDependencies().add(dependency);
}
}

private static String getKey(Dependency dependency) {
return dependency.getManagementKey() + ":" + dependency.getVersion() + ":"
+ Objects.requireNonNullElse(dependency.getClassifier(), "");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*******************************************************************************
* Copyright (c) 2025 Christoph Läubrich 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:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.bnd.maven;

import org.apache.maven.project.MavenProject;

import aQute.bnd.build.Project;

record BndMavenProject(MavenProject mavenProject, Project project, String classifier) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public final void execute() throws MojoExecutionException, MojoFailureException

}

private void checkResult(Report report, boolean errorOk) throws MojoFailureException {
protected void checkResult(Report report, boolean errorOk) throws MojoFailureException {
List<String> warnings = report.getWarnings();
for (String warning : warnings) {
getLog().warn(warning);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,50 @@
package org.eclipse.tycho.bnd.mojos;

import java.io.File;
import java.util.List;

import org.apache.maven.artifact.Artifact;
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.ResolutionScope;
import org.apache.maven.project.MavenProjectHelper;

import aQute.bnd.build.Project;
import aQute.bnd.build.ProjectBuilder;
import aQute.bnd.build.SubProject;
import aQute.bnd.osgi.Jar;

@Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true)
public class BndBuildMojo extends AbstractBndProjectMojo {

@Component
MavenProjectHelper helper;

@Override
protected void execute(Project project) throws Exception {
File[] files = project.build();
if (files != null && files.length > 0) {
Artifact artifact = mavenProject.getArtifact();
artifact.setFile(files[0]);
List<SubProject> subProjects = project.getSubProjects();
if (subProjects.isEmpty()) {
getLog().info(String.format("Building bundle '%s'", project.getName()));
File[] files = project.build();
if (files != null && files.length > 0) {
Artifact artifact = mavenProject.getArtifact();
artifact.setFile(files[0]);
}
} else {
for (SubProject subProject : subProjects) {
ProjectBuilder builder = subProject.getProjectBuilder();
getLog().info(String.format("Building sub bundle '%s'", subProject.getName()));
Jar jar = builder.build();
checkResult(builder, project.getWorkspace().isFailOk());
String jarName = jar.getName();
String name = subProject.getName();
File file = new File(mavenProject.getBuild().getDirectory(), String.format("%s.jar", jarName));
jar.write(file);
helper.attachArtifact(mavenProject, "jar", name, file);
}
}

}

}
Loading

0 comments on commit f48b6c3

Please sign in to comment.