Skip to content

Commit

Permalink
Generalize usage of Eclipse Applications in Tycho
Browse files Browse the repository at this point in the history
Embedded Eclipse Application have proven to be a powerful tool in Tycho
to reuse existing Eclipse codes in a dynamic way. Currently its still a
bit of boilerplate code to start the application but always have the
same semantics we want something with bundles resolved and cached and
read from a location/target.

This adds a generic EclipseApplicationManager that is able to manage
many applications and caching these in a more effective way than single
applications can do.
  • Loading branch information
laeubi committed Nov 18, 2023
1 parent 979fa17 commit 70a85b0
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.net.URI;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -41,23 +40,13 @@
import org.eclipse.pde.api.tools.internal.IApiCoreConstants;
import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
import org.eclipse.tycho.ArtifactDescriptor;
import org.eclipse.tycho.ArtifactKey;
import org.eclipse.tycho.ClasspathEntry;
import org.eclipse.tycho.DependencyResolutionException;
import org.eclipse.tycho.IllegalArtifactReferenceException;
import org.eclipse.tycho.MavenRepositoryLocation;
import org.eclipse.tycho.ReactorProject;
import org.eclipse.tycho.ResolvedArtifactKey;
import org.eclipse.tycho.TychoConstants;
import org.eclipse.tycho.classpath.ClasspathContributor;
import org.eclipse.tycho.core.TychoProject;
import org.eclipse.tycho.core.TychoProjectManager;
import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
import org.eclipse.tycho.core.osgitools.MavenBundleResolver;
import org.eclipse.tycho.core.osgitools.OsgiBundleProject;
import org.eclipse.tycho.core.utils.TychoProjectUtils;
import org.eclipse.tycho.helper.PluginRealmHelper;
import org.eclipse.tycho.model.project.EclipseProject;
import org.eclipse.tycho.osgi.framework.EclipseApplication;
import org.eclipse.tycho.osgi.framework.EclipseFramework;
Expand Down Expand Up @@ -122,12 +111,6 @@ public class ApiAnalysisMojo extends AbstractMojo {
@Component
private ApiApplicationResolver resolver;

@Component
private PluginRealmHelper pluginRealmHelper;

@Component
protected MavenBundleResolver mavenBundleResolver;

@Component
private ApiApplicationResolver applicationResolver;

Expand Down Expand Up @@ -158,7 +141,7 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}
Collection<Path> dependencyBundles;
try {
dependencyBundles = getProjectDependencies();
dependencyBundles = projectManager.getProjectDependencies(project);
} catch (Exception e) {
throw new MojoFailureException("Can't fetch dependencies!", e);
}
Expand Down Expand Up @@ -279,56 +262,6 @@ private String time(long start) {
return sec + " s";
}

private Collection<Path> getProjectDependencies() throws Exception {
Set<Path> dependencySet = new HashSet<>();
TychoProject tychoProject = projectManager.getTychoProject(project).get();
List<ArtifactDescriptor> dependencies = TychoProjectUtils
.getDependencyArtifacts(DefaultReactorProject.adapt(project)).getArtifacts();
for (ArtifactDescriptor descriptor : dependencies) {
File location = descriptor.fetchArtifact().get();
if (location.equals(project.getBasedir())) {
continue;
}
ReactorProject reactorProject = descriptor.getMavenProject();
if (reactorProject == null) {
writeLocation(location, dependencySet);
} else {
ReactorProject otherProject = reactorProject;
writeLocation(otherProject.getArtifact(descriptor.getClassifier()), dependencySet);
}
}
if (tychoProject instanceof OsgiBundleProject bundleProject) {
pluginRealmHelper.visitPluginExtensions(project, session, ClasspathContributor.class, cpc -> {
List<ClasspathEntry> list = cpc.getAdditionalClasspathEntries(project, Artifact.SCOPE_COMPILE);
if (list != null && !list.isEmpty()) {
for (ClasspathEntry entry : list) {
for (File locations : entry.getLocations()) {
writeLocation(locations, dependencySet);
}
}
}
});
// This is a hack because "org.eclipse.osgi.services" exports the annotation
// package and might then be resolved by Tycho as a dependency, but then PDE
// can't find the annotations here, so we always add this as a dependency
// manually here, once "org.eclipse.osgi.services" is gone we can remove this
// again!
Optional<ResolvedArtifactKey> bundle = mavenBundleResolver.resolveMavenBundle(project, session, "org.osgi",
"org.osgi.service.component.annotations", "1.3.0");
bundle.ifPresent(key -> {
writeLocation(key.getLocation(), dependencySet);
});
}
return dependencySet;
}

private void writeLocation(File location, Collection<Path> consumer) {
if (location == null) {
return;
}
consumer.add(location.getAbsoluteFile().toPath());
}

private static final class ApiAppKey {

private URI key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.codehaus.plexus.component.annotations.Component;
Expand All @@ -31,8 +32,11 @@
import org.eclipse.tycho.core.resolver.P2ResolutionResult;
import org.eclipse.tycho.core.resolver.P2ResolutionResult.Entry;
import org.eclipse.tycho.core.resolver.P2Resolver;
import org.eclipse.tycho.osgi.framework.Bundles;
import org.eclipse.tycho.osgi.framework.EclipseApplication;
import org.eclipse.tycho.osgi.framework.EclipseApplicationFactory;
import org.eclipse.tycho.osgi.framework.EclipseApplicationManager;
import org.eclipse.tycho.osgi.framework.Features;
import org.osgi.framework.BundleException;
import org.osgi.service.log.LogEntry;

Expand All @@ -43,14 +47,6 @@
@Component(role = ApiApplicationResolver.class)
public class ApiApplicationResolver {

private static final String FRAGMENT_COMPATIBILITY = "org.eclipse.osgi.compatibility.state";

private static final String BUNDLE_API_TOOLS = "org.eclipse.pde.api.tools";

private static final String BUNDLE_LAUNCHING_MACOS = "org.eclipse.jdt.launching.macosx";

private static final String FILTER_MACOS = "(osgi.os=macosx)";

private final Map<URI, EclipseApplication> cache = new ConcurrentHashMap<>();

@Requirement
Expand All @@ -59,6 +55,9 @@ public class ApiApplicationResolver {
@Requirement
private EclipseApplicationFactory applicationFactory;

@Requirement
private EclipseApplicationManager applicationManager;

public Collection<Path> getApiBaselineBundles(Collection<MavenRepositoryLocation> baselineRepoLocations,
ArtifactKey artifactKey) throws IllegalArtifactReferenceException {
P2Resolver resolver = applicationFactory.createResolver();
Expand All @@ -77,16 +76,11 @@ public Collection<Path> getApiBaselineBundles(Collection<MavenRepositoryLocation
}

public EclipseApplication getApiApplication(MavenRepositoryLocation apiToolsRepo) {
return cache.computeIfAbsent(apiToolsRepo.getURL().normalize(), x -> {
logger.info("Resolve API tools runtime from " + apiToolsRepo + "...");
EclipseApplication application = applicationFactory.createEclipseApplication(apiToolsRepo,
"ApiToolsApplication");
application.addBundle(BUNDLE_API_TOOLS);
application.addBundle(FRAGMENT_COMPATIBILITY);
application.addConditionalBundle(BUNDLE_LAUNCHING_MACOS, FILTER_MACOS);
application.setLoggingFilter(ApiApplicationResolver::isOnlyDebug);
return application;
});

EclipseApplication application = applicationManager.getApplication(apiToolsRepo, new Bundles(Set.of(Bundles.BUNDLE_API_TOOLS)),
new Features(Set.of()), "Api Tools");
application.setLoggingFilter(ApiApplicationResolver::isOnlyDebug);
return application;
}

private static boolean isOnlyDebug(LogEntry entry) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Inject;
Expand All @@ -31,18 +36,28 @@
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.Logger;
import org.eclipse.tycho.ArtifactDescriptor;
import org.eclipse.tycho.ArtifactKey;
import org.eclipse.tycho.ClasspathEntry;
import org.eclipse.tycho.DefaultArtifactKey;
import org.eclipse.tycho.ExecutionEnvironmentConfiguration;
import org.eclipse.tycho.ReactorProject;
import org.eclipse.tycho.ResolvedArtifactKey;
import org.eclipse.tycho.TargetPlatform;
import org.eclipse.tycho.TargetPlatformService;
import org.eclipse.tycho.TychoConstants;
import org.eclipse.tycho.classpath.ClasspathContributor;
import org.eclipse.tycho.core.ee.ExecutionEnvironmentConfigurationImpl;
import org.eclipse.tycho.core.osgitools.AbstractTychoProject;
import org.eclipse.tycho.core.osgitools.BundleReader;
import org.eclipse.tycho.core.osgitools.DefaultReactorProject;
import org.eclipse.tycho.core.osgitools.MavenBundleResolver;
import org.eclipse.tycho.core.osgitools.OsgiBundleProject;
import org.eclipse.tycho.core.osgitools.OsgiManifest;
import org.eclipse.tycho.core.osgitools.OsgiManifestParserException;
import org.eclipse.tycho.core.resolver.DefaultTargetPlatformConfigurationReader;
import org.eclipse.tycho.core.utils.TychoProjectUtils;
import org.eclipse.tycho.helper.PluginRealmHelper;
import org.eclipse.tycho.model.project.EclipseProject;
import org.eclipse.tycho.targetplatform.TargetDefinition;

Expand Down Expand Up @@ -72,6 +87,15 @@ public class TychoProjectManager {
@Requirement
ToolchainManager toolchainManager;

@Requirement
PluginRealmHelper pluginRealmHelper;

@Requirement
MavenBundleResolver mavenBundleResolver;

@Requirement
TargetPlatformService targetPlatformService;

private final Map<File, Optional<EclipseProject>> eclipseProjectCache = new ConcurrentHashMap<>();

private final MavenSession mavenSession;
Expand Down Expand Up @@ -203,4 +227,67 @@ public Optional<Processor> getBndTychoProject(MavenProject project) {
return Optional.empty();
}

/**
* Determine the list of dependencies for a given project as a collection of path items
*
* @param project
* the project to use to determine the dependencies
* @return a Collection of pathes describing the project dependencies
* @throws Exception
*/
public Collection<Path> getProjectDependencies(MavenProject project) throws Exception {
Set<Path> dependencySet = new HashSet<>();
TychoProject tychoProject = getTychoProject(project).get();
List<ArtifactDescriptor> dependencies = TychoProjectUtils
.getDependencyArtifacts(DefaultReactorProject.adapt(project)).getArtifacts();
for (ArtifactDescriptor descriptor : dependencies) {
File location = descriptor.fetchArtifact().get();
if (location.equals(project.getBasedir())) {
continue;
}
ReactorProject reactorProject = descriptor.getMavenProject();
if (reactorProject == null) {
writeLocation(location, dependencySet);
} else {
writeLocation(reactorProject.getArtifact(descriptor.getClassifier()), dependencySet);
}
}
if (tychoProject instanceof OsgiBundleProject bundleProject) {
MavenSession session = getMavenSession();
pluginRealmHelper.visitPluginExtensions(project, session, ClasspathContributor.class, cpc -> {
List<ClasspathEntry> list = cpc.getAdditionalClasspathEntries(project, Artifact.SCOPE_COMPILE);
if (list != null && !list.isEmpty()) {
for (ClasspathEntry entry : list) {
for (File locations : entry.getLocations()) {
writeLocation(locations, dependencySet);
}
}
}
});
// This is a hack because "org.eclipse.osgi.services" exports the annotation
// package and might then be resolved by Tycho as a dependency, but then PDE
// can't find the annotations here, so we always add this as a dependency
// manually here, once "org.eclipse.osgi.services" is gone we can remove this
// again!
Optional<ResolvedArtifactKey> bundle = mavenBundleResolver.resolveMavenBundle(project, session, "org.osgi",
"org.osgi.service.component.annotations", "1.3.0");
bundle.ifPresent(key -> {
writeLocation(key.getLocation(), dependencySet);
});
}
return dependencySet;
}

private void writeLocation(File location, Collection<Path> consumer) {
if (location == null) {
return;
}
consumer.add(location.getAbsoluteFile().toPath());
}

public Optional<TargetPlatform> getTargetPlatform(MavenProject project) {
return targetPlatformService.getTargetPlatform(DefaultReactorProject.adapt(project));

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*******************************************************************************
* Copyright (c) 2023 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.osgi.framework;

import java.util.Set;

public record Bundles(Set<String> bundles) {

public static final String BUNDLE_API_TOOLS = "org.eclipse.pde.api.tools";
public static final String BUNDLE_ECLIPSE_HELP_BASE = "org.eclipse.help.base";
public static final String BUNDLE_PDE_CORE = "org.eclipse.pde.core";
static final String BUNDLE_LAUNCHING_MACOS = "org.eclipse.jdt.launching.macosx";
static final String BUNDLE_APP = "org.eclipse.equinox.app";
static final String BUNDLE_SCR = "org.apache.felix.scr";
static final String BUNDLE_CORE = "org.eclipse.core.runtime";
static final String BUNDLE_LAUNCHER = "org.eclipse.equinox.launcher";

public static Bundles of(String... bundles) {
return new Bundles(Set.of(bundles));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ public class EclipseApplication {

public static final String ARG_APPLICATION = "-application";

private static final Set<String> ALWAYS_START_BUNDLES = Set.of(EclipseApplicationFactory.BUNDLE_CORE,
EclipseApplicationFactory.BUNDLE_SCR, EclipseApplicationFactory.BUNDLE_APP);
private static final Set<String> ALWAYS_START_BUNDLES = Set.of(Bundles.BUNDLE_CORE,
Bundles.BUNDLE_SCR, Bundles.BUNDLE_APP);
private P2Resolver resolver;
private TargetPlatform targetPlatform;
private Logger logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,6 @@
@SessionScoped
public class EclipseApplicationFactory {

static final String BUNDLE_APP = "org.eclipse.equinox.app";

static final String BUNDLE_SCR = "org.apache.felix.scr";

static final String BUNDLE_CORE = "org.eclipse.core.runtime";

private static final String BUNDLE_LAUNCHER = "org.eclipse.equinox.launcher";

@Requirement
private ToolchainManager toolchainManager;

Expand All @@ -67,15 +59,17 @@ public EclipseApplicationFactory(MavenSession mavenSession) {
}

public EclipseApplication createEclipseApplication(MavenRepositoryLocation repositoryLocation, String name) {
return createEclipseApplication(createTargetPlatform(List.of(repositoryLocation)), name);
}

public EclipseApplication createEclipseApplication(TargetPlatform targetPlatform, String name) {
P2Resolver resolver = createResolver();
List<MavenRepositoryLocation> locations = List.of(repositoryLocation);
TargetPlatform targetPlatform = createTargetPlatform(locations);
EclipseApplication application = new EclipseApplication(name, resolver, targetPlatform, logger);
//add the bare minimum required ...
application.addBundle(BUNDLE_CORE);
application.addBundle(BUNDLE_SCR);
application.addBundle(BUNDLE_APP);
application.addBundle(BUNDLE_LAUNCHER);
application.addBundle(Bundles.BUNDLE_CORE);
application.addBundle(Bundles.BUNDLE_SCR);
application.addBundle(Bundles.BUNDLE_APP);
application.addBundle(Bundles.BUNDLE_LAUNCHER);
return application;
}

Expand Down
Loading

0 comments on commit 70a85b0

Please sign in to comment.