From 550125ca26abbf4ec5b114e71de4c7749b2a3f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 19 Jan 2025 19:42:31 +0100 Subject: [PATCH] Add abstraction support for the code executed inside the OSGi Framework Currently the code for executing a build in the embedded framework is non trivial as well. This now uses an abstract class here as well and add support for the EclipseFramework to use such one level indirection as well. --- .../eclipse/tycho/osgi/framework/Bundles.java | 2 + .../osgi/framework/EclipseFramework.java | 19 ++- .../framework/EclipseModuleConnector.java | 25 +++- ...seBuild.java => AbstractEclipseBuild.java} | 134 +++++++++--------- .../AbstractEclipseBuildMojo.java | 34 ++++- .../EclipseBuildInstallableUnitProvider.java | 33 +++-- .../eclipsebuild/EclipseBuildProjectMojo.java | 5 +- .../eclipsebuild/EclipseProjectBuild.java | 30 ++++ .../tycho/eclipsebuild/SetTargetPlatform.java | 4 +- 9 files changed, 188 insertions(+), 98 deletions(-) rename tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/{EclipseBuild.java => AbstractEclipseBuild.java} (55%) create mode 100644 tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseProjectBuild.java diff --git a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/Bundles.java b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/Bundles.java index 70d44d2c95..ef5db313e8 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/Bundles.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/Bundles.java @@ -19,6 +19,8 @@ public record Bundles(Set 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"; + public static final String BUNDLE_JDT_CORE = "org.eclipse.jdt.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"; diff --git a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java index e3d85b2bc7..e546276f60 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java @@ -23,6 +23,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -125,24 +126,32 @@ private static String toBundleState(int state) { } @SuppressWarnings("unchecked") - public & Serializable, R extends Serializable> R execute(X runnable) - throws InvocationTargetException { + public & Serializable, R extends Serializable> R execute(X runnable, + String... requireBundles) throws InvocationTargetException { try { start(); byte[] runnableBytes = getBytes(runnable); BundleContext bundleContext = framework.getBundleContext(); - String newBundleId = connector.newBundle(runnable.getClass()); + Class clazz = runnable.getClass(); + String newBundleId = connector.newBundle(clazz, requireBundles); Bundle bundle = bundleContext.installBundle(newBundleId); + Class superclass = clazz.getSuperclass(); + if (superclass != null && !Object.class.equals(superclass)) { + if (!Objects.equals(EclipseModuleConnector.getLocationFromClass(clazz), + EclipseModuleConnector.getLocationFromClass(superclass))) { + bundleContext.installBundle(connector.newFragment(superclass, bundle)); + } + } try { bundle.start(); - Class foreignClass = bundle.loadClass(runnable.getClass().getName()); + Class foreignClass = bundle.loadClass(clazz.getName()); Object foreignObject = readObject(runnableBytes, foreignClass.getClassLoader()); Method method = foreignClass.getMethod("call"); byte[] resultBytes = getBytes(method.invoke(foreignObject)); if (resultBytes == null) { return null; } - return (R) readObject(resultBytes, runnable.getClass().getClassLoader()); + return (R) readObject(resultBytes, clazz.getClassLoader()); } finally { bundle.uninstall(); connector.release(newBundleId); diff --git a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseModuleConnector.java b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseModuleConnector.java index ab3afe9cbe..67cc01beba 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseModuleConnector.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseModuleConnector.java @@ -20,6 +20,7 @@ import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -28,8 +29,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; +import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleException; import org.osgi.framework.connect.ConnectContent; @@ -64,7 +67,7 @@ public Optional newBundleActivator() { return Optional.empty(); } - public String newBundle(Class clazz) { + public String newBundle(Class clazz, String[] requireBundles) { URI location = getLocationFromClass(clazz); if (location == null) { throw new RuntimeException("can't get location of class " + clazz); @@ -75,6 +78,24 @@ public String newBundle(Class clazz) { header.put(Constants.BUNDLE_SYMBOLICNAME, id); header.put(Constants.BUNDLE_VERSION, "1.0.0"); header.put(Constants.DYNAMICIMPORT_PACKAGE, "*"); + if (requireBundles != null && requireBundles.length > 0) { + header.put(Constants.REQUIRE_BUNDLE, Arrays.stream(requireBundles).collect(Collectors.joining(","))); + } + modules.put(id, new TempBundle(new File(location), header)); + return id; + } + + public String newFragment(Class clazz, Bundle bundle) { + URI location = getLocationFromClass(clazz); + if (location == null) { + throw new RuntimeException("can't get location of class " + clazz); + } + String id = "eclipse-fragment" + UUID.randomUUID().toString(); + Map header = new HashMap<>(); + header.put(Constants.BUNDLE_NAME, clazz.getName()); + header.put(Constants.BUNDLE_SYMBOLICNAME, id); + header.put(Constants.BUNDLE_VERSION, "1.0.0"); + header.put(Constants.FRAGMENT_HOST, bundle.getSymbolicName()); modules.put(id, new TempBundle(new File(location), header)); return id; } @@ -83,7 +104,7 @@ public void release(String id) { modules.remove(id); } - private static URI getLocationFromClass(Class clazz) { + static URI getLocationFromClass(Class clazz) { ProtectionDomain domain = clazz.getProtectionDomain(); if (domain == null) { return null; diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuild.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java similarity index 55% rename from tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuild.java rename to tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java index 58a210cf56..199607b938 100644 --- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuild.java +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java @@ -1,15 +1,3 @@ -/******************************************************************************* - * 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.eclipsebuild; import java.io.IOException; @@ -28,78 +16,42 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; -public class EclipseBuild implements Callable, Serializable { - +/** + * Abstract class for performing a build and producing a result + * + * @param + */ +public abstract class AbstractEclipseBuild + implements Callable, Serializable, IProgressMonitor { private boolean debug; private String baseDir; - EclipseBuild(Path projectDir, boolean debug) { + protected AbstractEclipseBuild(Path projectDir, boolean debug) { this.debug = debug; this.baseDir = pathAsString(projectDir); } @Override - public EclipseBuildResult call() throws Exception { - EclipseBuildResult result = new EclipseBuildResult(); + public final Result call() throws Exception { Platform.addLogListener((status, plugin) -> debug(status.toString())); disableAutoBuild(); deleteAllProjects(); IProject project = importProject(); - IProgressMonitor debugMonitor = new IProgressMonitor() { - - @Override - public void worked(int work) { - - } - - @Override - public void subTask(String name) { - debug("SubTask: " + name); - } - - @Override - public void setTaskName(String name) { - debug("Task: " + name); - } - - @Override - public void setCanceled(boolean value) { - - } - - @Override - public boolean isCanceled() { - return false; - } - - @Override - public void internalWorked(double work) { - - } - - @Override - public void done() { - - } - - @Override - public void beginTask(String name, int totalWork) { - setTaskName(name); - } - }; - project.build(IncrementalProjectBuilder.CLEAN_BUILD, debugMonitor); - project.build(IncrementalProjectBuilder.FULL_BUILD, debugMonitor); + project.build(IncrementalProjectBuilder.CLEAN_BUILD, this); + project.build(IncrementalProjectBuilder.FULL_BUILD, this); + Result result = createResult(project); for (IMarker marker : project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE)) { result.addMarker(marker); debug(marker.toString()); } - ResourcesPlugin.getWorkspace().save(true, new NullProgressMonitor()); + ResourcesPlugin.getWorkspace().save(true, this); return result; } + protected abstract Result createResult(IProject project) throws Exception; + static void disableAutoBuild() throws CoreException { IWorkspace workspace = ResourcesPlugin.getWorkspace(); IWorkspaceDescription desc = workspace.getDescription(); @@ -109,7 +61,7 @@ static void disableAutoBuild() throws CoreException { private void deleteAllProjects() throws CoreException { for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { - project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT | IResource.FORCE, new NullProgressMonitor()); + project.delete(IResource.NEVER_DELETE_PROJECT_CONTENT | IResource.FORCE, this); } } @@ -119,14 +71,13 @@ private IProject importProject() throws CoreException, IOException { IProjectDescription projectDescription = ResourcesPlugin.getWorkspace() .loadProjectDescription(projectDescriptionFile); projectDescription.setLocation(projectPath); -// projectDescription.setBuildSpec(new ICommand[0]); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectDescription.getName()); - project.create(projectDescription, new NullProgressMonitor()); - project.open(new NullProgressMonitor()); + project.create(projectDescription, this); + project.open(this); return project; } - private void debug(String string) { + protected void debug(String string) { if (debug) { System.out.println(string); } @@ -139,4 +90,51 @@ static String pathAsString(Path path) { return null; } + @Override + public boolean isCanceled() { + return Thread.currentThread().isInterrupted(); + } + + @Override + public void beginTask(String name, int totalWork) { + if (name != null && !name.isBlank()) { + debug("> " + name); + } + } + + @Override + public void subTask(String name) { + if (name != null && !name.isBlank()) { + debug(">> " + name); + } + } + + @Override + public void setTaskName(String name) { + if (name != null && !name.isBlank()) { + debug("> " + name); + } + } + + @Override + public void done() { + // do nothing + } + + @Override + public void setCanceled(boolean value) { + // do nothing + } + + @Override + public void internalWorked(double work) { + // do nothing + + } + + @Override + public void worked(int work) { + // do nothing + } + } diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java index b22dfe9193..c9e4717f3a 100644 --- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java @@ -12,14 +12,13 @@ *******************************************************************************/ package org.eclipse.tycho.eclipsebuild; -import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.Callable; import java.util.function.Consumer; import org.apache.maven.model.Repository; @@ -34,6 +33,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.tycho.TargetPlatform; import org.eclipse.tycho.core.TychoProjectManager; +import org.eclipse.tycho.model.project.EclipseProject; import org.eclipse.tycho.osgi.framework.Bundles; import org.eclipse.tycho.osgi.framework.EclipseApplication; import org.eclipse.tycho.osgi.framework.EclipseApplicationManager; @@ -91,7 +91,11 @@ public abstract class AbstractEclipseBuildMojo eclipseProjectValue = projectManager.getEclipseProject(project); + if (eclipseProjectValue.isEmpty()) { + return; + } + EclipseProject eclipseProject = eclipseProjectValue.get(); Collection projectDependencies; try { projectDependencies = projectManager.getProjectDependencies(project); @@ -99,7 +103,7 @@ public final void execute() throws MojoExecutionException, MojoFailureException throw new MojoFailureException("Can't resolve project dependencies", e); } EclipseApplication application; - Bundles bundles = new Bundles(getBundles()); + Bundles bundles = new Bundles(getBundles(eclipseProject)); Features features = new Features(getFeatures()); if (local) { TargetPlatform targetPlatform = projectManager.getTargetPlatform(project).orElseThrow( @@ -119,7 +123,7 @@ public final void execute() throws MojoExecutionException, MojoFailureException getLog().info("Skip set Target Platform because " + Bundles.BUNDLE_PDE_CORE + " is not part of the framework..."); } - Result result = framework.execute(createExecutable()); + Result result = framework.execute(createExecutable(), getRequireBundles()); if (printMarker) { Log log = getLog(); result.markers().filter(marker -> marker.getAttribute(IMarker.SEVERITY, -1) == IMarker.SEVERITY_INFO) @@ -143,7 +147,11 @@ public final void execute() throws MojoExecutionException, MojoFailureException protected abstract void handleResult(Result result) throws MojoFailureException; - protected abstract & Serializable, R extends Serializable> R createExecutable(); + protected abstract AbstractEclipseBuild createExecutable(); + + protected String[] getRequireBundles() { + return new String[0]; + } protected Set getFeatures() { Set set = new HashSet<>(); @@ -153,7 +161,7 @@ protected Set getFeatures() { return set; } - protected Set getBundles() { + protected Set getBundles(EclipseProject eclipseProject) { Set set = new HashSet<>(); set.add("org.eclipse.core.resources"); set.add("org.eclipse.core.runtime"); @@ -161,6 +169,18 @@ protected Set getBundles() { if (bundles != null) { set.addAll(bundles); } + for (String requiredBundle : getRequireBundles()) { + set.add(requiredBundle); + } + if (eclipseProject.hasNature("org.eclipse.pde.PluginNature")) { + set.add(Bundles.BUNDLE_PDE_CORE); + } + if (eclipseProject.hasNature("org.eclipse.jdt.core.javanature")) { + set.add(Bundles.BUNDLE_JDT_CORE); + } + // TODO if project has org.eclipse.pde.api.tools.apiAnalysisNature and we have a + // baseline parameter then + // set.add(Bundles.BUNDLE_API_TOOLS); return set; } diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildInstallableUnitProvider.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildInstallableUnitProvider.java index 5299163281..8b51f891fc 100644 --- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildInstallableUnitProvider.java +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildInstallableUnitProvider.java @@ -24,9 +24,9 @@ import org.eclipse.equinox.p2.metadata.IInstallableUnit; import org.eclipse.equinox.p2.metadata.MetadataFactory; import org.eclipse.equinox.p2.metadata.VersionRange; -import org.eclipse.tycho.p2maven.tmp.BundlesAction; import org.eclipse.tycho.helper.PluginConfigurationHelper; import org.eclipse.tycho.helper.PluginConfigurationHelper.Configuration; +import org.eclipse.tycho.p2maven.tmp.BundlesAction; import org.eclipse.tycho.resolver.InstallableUnitProvider; @Component(role = InstallableUnitProvider.class, hint = "eclipse-build") @@ -38,18 +38,29 @@ public class EclipseBuildInstallableUnitProvider implements InstallableUnitProvi @Override public Collection getInstallableUnits(MavenProject project, MavenSession session) throws CoreException { - Configuration configuration = configurationHelper.getConfiguration(EclipseBuildProjectMojo.class, project, session); - Optional local = configuration.getBoolean(EclipseBuildProjectMojo.PARAMETER_LOCAL); - if (local.isPresent() && local.get()) { - // for local target resolution the bundles become requirements... - Optional> list = configuration.getStringList("bundles"); - return InstallableUnitProvider.createIU(list.stream().flatMap(Collection::stream) - .map(bundleName -> MetadataFactory.createRequirement(BundlesAction.CAPABILITY_NS_OSGI_BUNDLE, - bundleName, - VersionRange.emptyRange, null, false, true)), - "eclipse-build-bundles"); + Configuration configuration = extracted(project, session); + if (configuration != null) { + Optional local = configuration.getBoolean(EclipseBuildProjectMojo.PARAMETER_LOCAL); + if (local.isPresent() && local.get()) { + // for local target resolution the bundles become requirements... + Optional> list = configuration.getStringList("bundles"); + return InstallableUnitProvider.createIU(list.stream().flatMap(Collection::stream) + .map(bundleName -> MetadataFactory.createRequirement(BundlesAction.CAPABILITY_NS_OSGI_BUNDLE, + bundleName, VersionRange.emptyRange, null, false, true)), + "eclipse-build-bundles"); + } } return List.of(); } + private Configuration extracted(MavenProject project, MavenSession session) { + try { + return configurationHelper.getConfiguration(EclipseBuildProjectMojo.class, project, session); + } catch (Exception e) { + // this can happen if some other mojo in a different plugin enhances the + // AbstractEclipseBuild + return null; + } + } + } diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildProjectMojo.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildProjectMojo.java index b57242572c..269c55f439 100644 --- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildProjectMojo.java +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseBuildProjectMojo.java @@ -38,10 +38,9 @@ protected String getName() { return "Eclipse Project Build"; } - @SuppressWarnings("unchecked") @Override - protected EclipseBuild createExecutable() { - return new EclipseBuild(project.getBasedir().toPath(), debug); + protected EclipseProjectBuild createExecutable() { + return new EclipseProjectBuild(project.getBasedir().toPath(), debug); } @Override diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseProjectBuild.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseProjectBuild.java new file mode 100644 index 0000000000..96871ccc33 --- /dev/null +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/EclipseProjectBuild.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * 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.eclipsebuild; + +import java.nio.file.Path; + +import org.eclipse.core.resources.IProject; + +public class EclipseProjectBuild extends AbstractEclipseBuild { + + EclipseProjectBuild(Path projectDir, boolean debug) { + super(projectDir, debug); + } + + @Override + protected EclipseBuildResult createResult(IProject project) { + return new EclipseBuildResult(); + } + +} diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java index 616326345a..c0e1473d7b 100644 --- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java @@ -38,14 +38,14 @@ public class SetTargetPlatform implements Callable, Serializable { SetTargetPlatform(Collection dependencyBundles, boolean debug) { this.debug = debug; - this.targetBundles = dependencyBundles.stream().map(EclipseBuild::pathAsString).toList(); + this.targetBundles = dependencyBundles.stream().map(EclipseProjectBuild::pathAsString).toList(); } @Override public Serializable call() throws Exception { ILogListener listener = (status, plugin) -> debug(status.toString()); Platform.addLogListener(listener); - EclipseBuild.disableAutoBuild(); + EclipseProjectBuild.disableAutoBuild(); ITargetPlatformService service = TargetPlatformService.getDefault(); ITargetDefinition target = service.newTarget(); target.setName("buildpath");