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 17, 2023
1 parent 979fa17 commit ee8d31a
Show file tree
Hide file tree
Showing 13 changed files with 349 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,8 @@ public class ApiApplicationResolver {
@Requirement
private EclipseApplicationFactory applicationFactory;

private EclipseApplicationManager applicationManager;

public Collection<Path> getApiBaselineBundles(Collection<MavenRepositoryLocation> baselineRepoLocations,
ArtifactKey artifactKey) throws IllegalArtifactReferenceException {
P2Resolver resolver = applicationFactory.createResolver();
Expand All @@ -77,16 +75,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
@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2019 Red Hat 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:
* - Mickael Istria (Red Hat Inc.)
*******************************************************************************/
package org.eclipse.tycho.compiler;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.pde.core.target.ITargetDefinition;
import org.eclipse.pde.core.target.ITargetLocation;
import org.eclipse.pde.core.target.TargetBundle;
import org.eclipse.pde.core.target.TargetFeature;

class BundleListTargetLocation implements ITargetLocation {

private TargetBundle[] bundles;

public BundleListTargetLocation(TargetBundle[] bundles) {
this.bundles = bundles;
}

@Override
public <T> T getAdapter(Class<T> adapter) {
return null;
}

@Override
public IStatus resolve(ITargetDefinition definition, IProgressMonitor monitor) {
return Status.OK_STATUS;
}

@Override
public boolean isResolved() {
return true;
}

@Override
public IStatus getStatus() {
return Status.OK_STATUS;
}

@Override
public String getType() {
return "BundleList"; //$NON-NLS-1$
}

@Override
public String getLocation(boolean resolve) throws CoreException {
return null;
}

@Override
public TargetBundle[] getBundles() {
return this.bundles;
}

@Override
public TargetFeature[] getFeatures() {
return null;
}

@Override
public String[] getVMArguments() {
return null;
}

@Override
public String serialize() {
return null;
}

}
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));

}

}
Loading

0 comments on commit ee8d31a

Please sign in to comment.