From b54cb7bb812bcabfedfdba1106d8c90bc3a7a7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 16 Feb 2025 07:13:32 +0100 Subject: [PATCH 1/2] Add OSGi test classpath support Similar to what we have for OSGi annotations, PDE should have OSGi Testing Support as it is a great library for testing OSGi applications. The most hindering thing in this regard is that it is rater complex to setup until one can make the first steps. This now adds a new classpath contributor that detects if a PDE project is already using JUNIT classpath container and then adds OSGi test dependencies automatically as test dependencies if they are part of the target platform or alternatively from the running platform. See https://github.com/eclipse-pde/eclipse.pde/issues/877 --- features/org.eclipse.pde-feature/feature.xml | 7 + ui/org.eclipse.pde.core/META-INF/MANIFEST.MF | 3 +- .../pde/internal/core/ClasspathUtilCore.java | 110 +++++++++--- .../OSGiTestClasspathContributor.java | 156 ++++++++++++++++++ 4 files changed, 252 insertions(+), 24 deletions(-) create mode 100644 ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/osgitest/OSGiTestClasspathContributor.java diff --git a/features/org.eclipse.pde-feature/feature.xml b/features/org.eclipse.pde-feature/feature.xml index d52425401a..e075368b7d 100644 --- a/features/org.eclipse.pde-feature/feature.xml +++ b/features/org.eclipse.pde-feature/feature.xml @@ -28,6 +28,13 @@ + + + + + + + collectLibraryEntries(IPluginModelBa } public static Stream classpathEntriesForBundle(String id) { + return classpathEntriesForBundle(id, false, new IClasspathAttribute[0]); + } + + public static Stream classpathEntriesForBundle(String id, boolean includeRequired, + IClasspathAttribute[] extra) { // first look if we have something in the workspace... IPluginModelBase model = PluginRegistry.findModel(id); if (model != null && model.isEnabled()) { - IResource resource = model.getUnderlyingResource(); - if (resource != null && PluginProject.isJavaProject(resource.getProject())) { - IJavaProject javaProject = JavaCore.create(resource.getProject()); - return Stream.of(JavaCore.newProjectEntry(javaProject.getPath())); - } - String location = model.getInstallLocation(); - if (location == null) { - return Stream.empty(); + Stream modelBundleClasspath = classpathEntriesForModelBundle(model, extra); + if (includeRequired) { + return Stream.concat(modelBundleClasspath, + getRequiredByDescription(model.getBundleDescription(), extra)); } - boolean isJarShape = new File(location).isFile(); - IPluginLibrary[] libraries = model.getPluginBase().getLibraries(); - if (isJarShape || libraries.length == 0) { - return Stream.of(getEntryForPath(IPath.fromOSString(location))); - } - return Arrays.stream(libraries).filter(library -> !IPluginLibrary.RESOURCE.equals(library.getType())) - .map(library -> { - String name = library.getName(); - String expandedName = ClasspathUtilCore.expandLibraryName(name); - return ClasspathUtilCore.getPath(model, expandedName, isJarShape); - }).filter(Objects::nonNull).map(ClasspathUtilCore::getEntryForPath); + return modelBundleClasspath; } // if not found in the models, try to use one from the running eclipse - return Optional.ofNullable(Platform.getBundle(id)).map(bundle -> bundle.adapt(File.class)).filter(File::exists) + Bundle runtimeBundle = Platform.getBundle(id); + if (runtimeBundle == null) { + return Stream.empty(); + } + Stream bundleClasspath = classpathEntriesForRuntimeBundle(runtimeBundle, extra).stream(); + if (includeRequired) { + return Stream.concat(bundleClasspath, getRequiredByWire(runtimeBundle, extra)); + } + return bundleClasspath; + } + + private static Stream getRequiredByDescription(BundleDescription description, + IClasspathAttribute[] extra) { + BundleWiring wiring = description.getWiring(); + if (wiring == null) { + return Stream.empty(); + } + + List wires = wiring.getRequiredWires(PackageNamespace.PACKAGE_NAMESPACE); + return wires.stream().map(wire -> { + return wire.getProvider(); + }).distinct().flatMap(provider -> { + IPluginModelBase model = PluginRegistry.findModel(provider); + if (model != null && model.isEnabled()) { + return classpathEntriesForModelBundle(model, extra); + } + return Stream.empty(); + }); + } + + protected static Stream classpathEntriesForModelBundle(IPluginModelBase model, + IClasspathAttribute[] extra) { + IResource resource = model.getUnderlyingResource(); + if (resource != null && PluginProject.isJavaProject(resource.getProject())) { + IJavaProject javaProject = JavaCore.create(resource.getProject()); + return Stream.of(JavaCore.newProjectEntry(javaProject.getPath())); + } + String location = model.getInstallLocation(); + if (location == null) { + return Stream.empty(); + } + boolean isJarShape = new File(location).isFile(); + IPluginLibrary[] libraries = model.getPluginBase().getLibraries(); + if (isJarShape || libraries.length == 0) { + return Stream.of(getEntryForPath(IPath.fromOSString(location), extra)); + } + return Arrays.stream(libraries).filter(library -> !IPluginLibrary.RESOURCE.equals(library.getType())) + .map(library -> { + String name = library.getName(); + String expandedName = ClasspathUtilCore.expandLibraryName(name); + return ClasspathUtilCore.getPath(model, expandedName, isJarShape); + }).filter(Objects::nonNull).map(entry -> getEntryForPath(entry, extra)); + } + + public static Stream getRequiredByWire(Bundle bundle, IClasspathAttribute[] extra) { + BundleWiring wiring = bundle.adapt(BundleWiring.class); + if (wiring == null) { + return Stream.empty(); + } + List wires = wiring.getRequiredWires(PackageNamespace.PACKAGE_NAMESPACE); + return wires.stream().map(wire -> wire.getProviderWiring().getBundle()).distinct() + .filter(b -> b.getBundleId() != 0) + .flatMap(b -> classpathEntriesForRuntimeBundle(b, extra).stream()); + } + + private static Optional classpathEntriesForRuntimeBundle(Bundle bundle, + IClasspathAttribute[] extra) { + return Optional.ofNullable(bundle.adapt(File.class)).filter(File::exists) .map(File::toPath).map(Path::normalize).map(path -> IPath.fromOSString(path.toString())) - .map(ClasspathUtilCore::getEntryForPath).stream(); + .map(entry -> getEntryForPath(entry, extra)); } public static boolean isEntryForModel(IClasspathEntry entry, IPluginModelBase projectModel) { @@ -127,8 +191,8 @@ public static boolean isEntryForModel(IClasspathEntry entry, IPluginModelBase pr return false; } - private static IClasspathEntry getEntryForPath(IPath path) { - return JavaCore.newLibraryEntry(path, path, IPath.ROOT, new IAccessRule[0], new IClasspathAttribute[0], false); + private static IClasspathEntry getEntryForPath(IPath path, IClasspathAttribute[] extra) { + return JavaCore.newLibraryEntry(path, path, IPath.ROOT, new IAccessRule[0], extra, false); } private static void addLibraryEntry(IPluginLibrary library, Collection entries) { diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/osgitest/OSGiTestClasspathContributor.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/osgitest/OSGiTestClasspathContributor.java new file mode 100644 index 0000000000..32efbb03d4 --- /dev/null +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/osgitest/OSGiTestClasspathContributor.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * 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.pde.internal.core.osgitest; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IClasspathAttribute; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.osgi.service.resolver.BundleDelta; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.State; +import org.eclipse.osgi.service.resolver.StateDelta; +import org.eclipse.pde.core.IClasspathContributor; +import org.eclipse.pde.core.plugin.IPluginModelBase; +import org.eclipse.pde.core.plugin.PluginRegistry; +import org.eclipse.pde.internal.core.ClasspathUtilCore; +import org.eclipse.pde.internal.core.IStateDeltaListener; +import org.eclipse.pde.internal.core.PDECore; +import org.osgi.resource.Resource; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; + +/** + * If the users chose to add JUNIT container to a PDE project and OSGi test is + * in the target platform this contributor automatically adds the OSGi test + * extensions to the classpath. + */ +@Component(service = IClasspathContributor.class) +public class OSGiTestClasspathContributor implements IClasspathContributor, IStateDeltaListener { + + private static final IPath PATH = new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER"); //$NON-NLS-1$ + + private static final IPath JUNIT5_CONTAINER_PATH = PATH.append("5"); //$NON-NLS-1$ + private static final IPath JUNIT4_CONTAINER_PATH = PATH.append("4"); //$NON-NLS-1$ + + private static final int CHANGE_FLAGS = BundleDelta.ADDED | BundleDelta.REMOVED | BundleDelta.UPDATED; + + private static final IClasspathAttribute TEST_ATTRIBUTE = JavaCore.newClasspathAttribute(IClasspathAttribute.TEST, + "true"); //$NON-NLS-1$ + + private static final Collection OSGI_TEST_BUNDLES = List.of("org.osgi.test.common", //$NON-NLS-1$ + "org.osgi.test.assertj.framework", "org.osgi.test.assertj.log", "org.osgi.test.assertj.promise"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private static final Collection OSGI_TEST_JUNIT5_BUNDLES = List.of("org.osgi.test.junit5", //$NON-NLS-1$ + "org.osgi.test.junit5.cm"); //$NON-NLS-1$ + + private static final Collection OSGI_TEST_JUNIT4_BUNDLES = List.of("org.osgi.test.junit4"); //$NON-NLS-1$ + + private final ConcurrentMap> entryMap = new ConcurrentHashMap<>(); + + @Activate + void registerListener() { + // TODO we need to listen to classpath changes as well, eg. container + // added/removed/changed/... + PDECore.getDefault().getModelManager().addStateDeltaListener(this); + } + + @Deactivate + void undregisterListener() { + PDECore.getDefault().getModelManager().removeStateDeltaListener(this); + } + + /** + * @return s stream of all osgi test bundles + */ + public static Stream bundles(boolean junit5) { + return Stream.concat(OSGI_TEST_BUNDLES.stream(), + junit5 ? OSGI_TEST_JUNIT5_BUNDLES.stream() : OSGI_TEST_JUNIT4_BUNDLES.stream()); + } + + @Override + public List getInitialEntries(BundleDescription project) { + IPluginModelBase projectModel = PluginRegistry.findModel((Resource) project); + return junitBundles(projectModel) + .map(bundleId -> entryMap.computeIfAbsent(bundleId, + id -> ClasspathUtilCore + .classpathEntriesForBundle(id, true, new IClasspathAttribute[] { TEST_ATTRIBUTE }) + .toList())) + .flatMap(Collection::stream) + .filter(Predicate.not(entry -> ClasspathUtilCore.isEntryForModel(entry, projectModel))).toList(); + } + + protected Stream junitBundles(IPluginModelBase projectModel) { + IResource resource = projectModel.getUnderlyingResource(); + if (resource != null) { + IProject eclipseProject = resource.getProject(); + IJavaProject javaProject = JavaCore.create(eclipseProject); + try { + IClasspathEntry[] classpath = javaProject.getRawClasspath(); + for (IClasspathEntry cp : classpath) { + if (cp.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { + if (JUNIT5_CONTAINER_PATH.equals(cp.getPath())) { + return bundles(true); + } + if (JUNIT4_CONTAINER_PATH.equals(cp.getPath())) { + return bundles(false); + } + } + } + } catch (JavaModelException e) { + // can't check, fall through and assume not enabled... + } + } + return Stream.empty(); + } + + @Override + public List getEntriesForDependency(BundleDescription project, BundleDescription addedDependency) { + return Collections.emptyList(); + } + + @Override + public void stateResolved(StateDelta delta) { + if (delta == null) { + stateChanged(null); + } else { + // just refresh the items in the map if they have any changes... + for (BundleDelta bundleDelta : delta.getChanges(CHANGE_FLAGS, false)) { + entryMap.remove(bundleDelta.getBundle().getSymbolicName()); + } + } + + } + + @Override + public void stateChanged(State newState) { + // we need to refresh everything + entryMap.clear(); + } + +} From 0c33ce1fa334d584cdd3bb3cf2d11b7fc0fe8c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 16 Feb 2025 18:10:13 +0100 Subject: [PATCH 2/2] Add support for launching OSGi Tests Currently PDE supports running a JUnit Plug-in Test, but this is tightly coupled to PDE and Eclipse Applications. On the other hand PDE offers to launch plain OSGi Frameworks but something similar for tests is missing. This adds a new launch type "OSGi Test" that offers such type of tests that are not tied to PDE/Eclipse but targeting generic OSGi applications, this is very similar to what Tycho offers as 'tycho-surefire-plugin:bnd-test' and will also allow better interfacing with the OSGi Test framework. --- features/org.eclipse.pde-feature/feature.xml | 6 + ui/org.eclipse.pde.bnd.ui/README.MD | 49 ++++- ui/org.eclipse.pde.core/META-INF/MANIFEST.MF | 4 +- .../eclipse/pde/core/build/IBuildEntry.java | 27 ++- .../pde/internal/core/ClasspathUtilCore.java | 7 +- .../internal/core/PDEClasspathContainer.java | 59 +++++- .../internal/core/bnd/BndProjectManager.java | 53 ++++- .../pde/internal/core/bnd/PdeBndAdapter.java | 22 +- .../pde/internal/core/bnd/PdeBndrun.java | 113 +++++++++++ .../pde/internal/core/bnd/PdeBuildJar.java | 96 +++++++++ .../internal/core/bnd/TargetRepository.java | 23 +++ .../META-INF/MANIFEST.MF | 8 +- .../plugin.properties | 4 + ui/org.eclipse.pde.launching/plugin.xml | 14 ++ .../OSGiTestLaunchConfigurationDelegate.java | 190 ++++++++++++++++++ ui/org.eclipse.pde.ui/icons/osgi/osgi.png | Bin 0 -> 4797 bytes ui/org.eclipse.pde.ui/icons/osgi/osgi@2x.png | Bin 0 -> 5716 bytes ui/org.eclipse.pde.ui/plugin.xml | 15 ++ .../pde/ui/launcher/JUnitTabGroup.java | 4 + .../pde/ui/launcher/OSGiTestTabGroup.java | 67 ++++++ 20 files changed, 731 insertions(+), 30 deletions(-) create mode 100644 ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndrun.java create mode 100644 ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBuildJar.java create mode 100644 ui/org.eclipse.pde.launching/src/org/eclipse/pde/launching/OSGiTestLaunchConfigurationDelegate.java create mode 100644 ui/org.eclipse.pde.ui/icons/osgi/osgi.png create mode 100644 ui/org.eclipse.pde.ui/icons/osgi/osgi@2x.png create mode 100644 ui/org.eclipse.pde.ui/src/org/eclipse/pde/ui/launcher/OSGiTestTabGroup.java diff --git a/features/org.eclipse.pde-feature/feature.xml b/features/org.eclipse.pde-feature/feature.xml index e075368b7d..790c3917a7 100644 --- a/features/org.eclipse.pde-feature/feature.xml +++ b/features/org.eclipse.pde-feature/feature.xml @@ -28,6 +28,7 @@ + @@ -35,6 +36,11 @@ + + + + + T getAdapter(Object adaptableObject, Class adapterType) { @@ -46,6 +46,51 @@ public class BndPluginAdapter implements IAdapterFactory { } ``` +### The `Bndrun` adapter + +For some cases one would need to get hold of a `Bndrun` (e.g. for launching tests), to support this similar is needed as to create a template that then can further be customized. +For this use-case it is required to provide an adapter that can transform an (Eclipse) `IProject` into a (bndlib) `Bndrun` that is initialized with the runrequires contain the identity +of the project and the workspace be able to resolve such bundle. + +``` +@Component +@AdapterTypes(adaptableClass = IProject.class, adapterNames = Bndrun.class) +public class BndRunAdapter implements IAdapterFactory { + + @Override + public T getAdapter(Object adaptableObject, Class adapterType) { + if (adaptableObject instanceof IProject eclipseProject) { + if (adapterType == Project.class) { + //... here we need to determine if the project is managed by our tooling, e.g. it is a PDE, Bndtool, Maven, ... backed project + if (/*... is relevant ... */) { + Workspace workspace = .... find the workspace according to your tooling implementation + Path base = workspace.getBase().toPath(); + Path file = Files.createTempFile(base, project.getName(), ".bndrun"); + file.toFile().deleteOnExit(); + Files.createDirectories(file.getParent()); + Properties properties = new Properties(); + String bsn = ... derive the symbolic name ... + String version = ... derive the version name ... + properties.setProperty(Constants.RUNREQUIRES, String.format("bnd.identity; id=%s;version=%s", bsn, version)); + //maybe more customizations.... + Bndrun bndrun = Bndrun.createBndrun(workspace, file.toFile()); + //make sure the file is deleted when the bndrun is closed... + bndrun.addClose(new AutoCloseable() { + + @Override + public void close() throws Exception { + Files.delete(file); + } + }); + } + } + } + return null; + } + +} +``` + ## Available components Beside some integration stuff (e.g. enable to [discover bndlib plugins](https://github.com/eclipse-pde/eclipse.pde/blob/master/ui/org.eclipse.pde.bnd.ui/src/org/eclipse/pde/bnd/ui/internal/Auxiliary.java) inside an OSGi runtime) diff --git a/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF index fa63280643..f96b378f97 100644 --- a/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF +++ b/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %name Bundle-SymbolicName: org.eclipse.pde.core; singleton:=true -Bundle-Version: 3.20.100.qualifier +Bundle-Version: 3.21.0.qualifier Bundle-Activator: org.eclipse.pde.internal.core.PDECore Bundle-Vendor: %provider-name Bundle-Localization: plugin @@ -48,6 +48,7 @@ Export-Package: org.eclipse.pde.ui, org.eclipse.pde.api.tools.tests, org.eclipse.pde.api.tools", + org.eclipse.pde.internal.core.osgitest;x-internal:=true, org.eclipse.pde.internal.core.plugin; x-friends:="org.eclipse.pde.ui, org.eclipse.pde.ds.ui, @@ -97,6 +98,7 @@ Import-Package: aQute.bnd.build;version="[4.4.0,5.0.0)", aQute.bnd.service.progress;version="[1.3.0,2.0.0)", aQute.bnd.version;version="[2.2.0,3.0.0)", aQute.service.reporter;version="[1.2.0,2.0.0)", + biz.aQute.resolve;version="[9.1.0,10.0.0)", org.bndtools.versioncontrol.ignores.manager.api;version="[1.0.0,2.0.0)", org.eclipse.equinox.internal.p2.publisher.eclipse, org.eclipse.equinox.p2.publisher, diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/build/IBuildEntry.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/build/IBuildEntry.java index 0d7da2ef76..3da5d852ae 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/build/IBuildEntry.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/core/build/IBuildEntry.java @@ -27,39 +27,39 @@ public interface IBuildEntry extends IWritable { /** * A property name for changes to the 'name' field. */ - public static final String P_NAME = "name"; //$NON-NLS-1$ + String P_NAME = "name"; //$NON-NLS-1$ /** * The prefix for any key denoting the source folders that * should be compiled into a JAR. The suffix will * be the name of the JAR. */ - public static final String JAR_PREFIX = "source."; //$NON-NLS-1$ + String JAR_PREFIX = "source."; //$NON-NLS-1$ /** * The prefix for any key denoting output folders for a particular * JAR. The suffix will be the name of the JAR. */ - public static final String OUTPUT_PREFIX = "output."; //$NON-NLS-1$ + String OUTPUT_PREFIX = "output."; //$NON-NLS-1$ /** * The name of the key that lists all the folders and files * to be included in the binary build. */ - public static final String BIN_INCLUDES = "bin.includes"; //$NON-NLS-1$ + String BIN_INCLUDES = "bin.includes"; //$NON-NLS-1$ /** * The name of the key that lists all the folders and files * to be included in the source build. */ - public static final String SRC_INCLUDES = "src.includes"; //$NON-NLS-1$ + String SRC_INCLUDES = "src.includes"; //$NON-NLS-1$ /** * The name of the key that declares extra library entries to be added * to the class path at build time only.. */ - public static final String JARS_EXTRA_CLASSPATH = "jars.extra.classpath"; //$NON-NLS-1$ + String JARS_EXTRA_CLASSPATH = "jars.extra.classpath"; //$NON-NLS-1$ /** * The name of the key that declares additional plug-in dependencies to augment development classpath * * @since 3.2 */ - public static final String SECONDARY_DEPENDENCIES = "additional.bundles"; //$NON-NLS-1$ + String SECONDARY_DEPENDENCIES = "additional.bundles"; //$NON-NLS-1$ /** * Adds the token to the list of token for this entry. @@ -89,6 +89,19 @@ public interface IBuildEntry extends IWritable { */ String[] getTokens(); + /** + * Returns the first token for this entry + * + * @since 3.21 + */ + default String getFirstToken() { + String[] tokens = getTokens(); + if (tokens == null || tokens.length == 0) { + return null; + } + return tokens[0]; + } + /** * Returns true if the provided token exists in this entry. * @param token the string token to look for diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathUtilCore.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathUtilCore.java index ee2d779b46..1f2b92432c 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathUtilCore.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathUtilCore.java @@ -152,9 +152,14 @@ protected static Stream classpathEntriesForModelBundle(IPluginM } boolean isJarShape = new File(location).isFile(); IPluginLibrary[] libraries = model.getPluginBase().getLibraries(); - if (isJarShape || libraries.length == 0) { + if (isJarShape) { return Stream.of(getEntryForPath(IPath.fromOSString(location), extra)); } + if (libraries.length == 0) { + List entries = new ArrayList<>(); + PDEClasspathContainer.addExternalPlugin(model, null, entries); + return entries.stream(); + } return Arrays.stream(libraries).filter(library -> !IPluginLibrary.RESOURCE.equals(library.getType())) .map(library -> { String name = library.getName(); diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java index 3615375ebf..182617b345 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java @@ -30,9 +30,13 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.pde.core.build.IBuild; +import org.eclipse.pde.core.build.IBuildEntry; +import org.eclipse.pde.core.plugin.IPluginBase; import org.eclipse.pde.core.plugin.IPluginLibrary; import org.eclipse.pde.core.plugin.IPluginModelBase; import org.eclipse.pde.core.plugin.PluginRegistry; +import org.eclipse.pde.internal.core.build.ExternalBuildModel; import org.osgi.resource.Resource; public class PDEClasspathContainer { @@ -78,7 +82,8 @@ public static IClasspathEntry[] getExternalEntries(IPluginModelBase model) { } protected static void addExternalPlugin(IPluginModelBase model, List rules, List entries) { - boolean isJarShape = new File(model.getInstallLocation()).isFile(); + File file = new File(model.getInstallLocation()); + boolean isJarShape = file.isFile(); if (isJarShape) { IPath srcPath = ClasspathUtilCore.getSourceAnnotation(model, ".", isJarShape); //$NON-NLS-1$ if (srcPath == null) { @@ -93,14 +98,19 @@ protected static void addExternalPlugin(IPluginModelBase model, List rules addLibraryEntry(path, path, rules, getClasspathAttributes(model), entries); } } else { - IPluginLibrary[] libraries = model.getPluginBase().getLibraries(); + IPluginBase pluginBase = model.getPluginBase(); + IPluginLibrary[] libraries = pluginBase.getLibraries(); if (libraries.length == 0) { - // If there are no libraries, assume the root of the plug-in is the library '.' - IPath srcPath = ClasspathUtilCore.getSourceAnnotation(model, ".", isJarShape); //$NON-NLS-1$ - if (srcPath == null) { - srcPath = IPath.fromOSString(model.getInstallLocation()); + if (!addEntriesFromHostEclipse(model, rules, entries, file)) { + // If there are no libraries, assume the root of the plug-in + // is the library '.' + IPath srcPath = ClasspathUtilCore.getSourceAnnotation(model, ".", isJarShape); //$NON-NLS-1$ + if (srcPath == null) { + srcPath = IPath.fromOSString(model.getInstallLocation()); + } + addLibraryEntry(IPath.fromOSString(model.getInstallLocation()), srcPath, rules, + getClasspathAttributes(model), entries); } - addLibraryEntry(IPath.fromOSString(model.getInstallLocation()), srcPath, rules, getClasspathAttributes(model), entries); } else { for (IPluginLibrary library : libraries) { if (IPluginLibrary.RESOURCE.equals(library.getType())) { @@ -125,6 +135,41 @@ protected static void addExternalPlugin(IPluginModelBase model, List rules } } + protected static boolean addEntriesFromHostEclipse(IPluginModelBase model, List rules, + List entries, File file) { + boolean hasBuildEntries = false; + // if build properties exits in folder than this is a local + // project from an Eclipse started from a workspace + if (new File(file, ICoreConstants.BUILD_FILENAME_DESCRIPTOR).isFile()) { + IBuild build = new ExternalBuildModel(model.getInstallLocation()).getBuild(); + IBuildEntry[] buildEntries = build.getBuildEntries(); + for (IBuildEntry entry : buildEntries) { + String name = entry.getName(); + if (name.startsWith(IBuildEntry.OUTPUT_PREFIX)) { + String folder = entry.getFirstToken(); + if (folder != null) { + File outputFolder = new File(file, folder); + if (outputFolder.isDirectory()) { + hasBuildEntries = true; + IBuildEntry sourceEntry = build.getEntry(IBuildEntry.JAR_PREFIX + + name.substring(IBuildEntry.OUTPUT_PREFIX.length())); + IPath srcPath = null; + if (sourceEntry != null) { + String firstToken = sourceEntry.getFirstToken(); + if (firstToken != null) { + srcPath = IPath.fromOSString(new File(file, firstToken).getAbsolutePath()); + } + } + addLibraryEntry(IPath.fromOSString(outputFolder.getAbsolutePath()), srcPath, rules, + getClasspathAttributes(model), entries); + } + } + } + } + } + return hasBuildEntries; + } + protected static void addLibraryEntry(IPath path, IPath srcPath, List rules, IClasspathAttribute[] attributes, List entries) { IClasspathEntry entry = null; diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/BndProjectManager.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/BndProjectManager.java index b1974a515b..86d490dcbc 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/BndProjectManager.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/BndProjectManager.java @@ -14,11 +14,15 @@ package org.eclipse.pde.internal.core.bnd; import java.io.File; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,6 +37,8 @@ import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.pde.core.plugin.IPluginModelBase; import org.eclipse.pde.internal.core.PDECore; import org.eclipse.pde.internal.core.annotations.OSGiAnnotationsClasspathContributor; import org.eclipse.pde.internal.core.natures.BndProject; @@ -44,6 +50,7 @@ import aQute.bnd.build.Workspace; import aQute.bnd.osgi.Constants; import aQute.bnd.osgi.Processor; +import biz.aQute.resolve.Bndrun; public class BndProjectManager { @@ -96,8 +103,13 @@ static synchronized Workspace getWorkspace() throws Exception { if (workspace == null) { try (Processor run = new Processor()) { run.setProperty(Constants.STANDALONE, TRUE); - IPath path = PDECore.getDefault().getStateLocation().append(Project.BNDCNF); - workspace = Workspace.createStandaloneWorkspace(run, path.toFile().toURI()); + IPath path = PDECore.getDefault().getStateLocation().append(Constants.DEFAULT_BND_EXTENSION) + .append(Project.BNDCNF); + File file = path.toFile(); + file.mkdirs(); + workspace = Workspace.createStandaloneWorkspace(run, file.toURI()); + workspace.setBase(file.getParentFile()); + workspace.setBuildDir(file); workspace.set("workspaceName", Messages.BndProjectManager_WorkspaceName); //$NON-NLS-1$ workspace.set("workspaceDescription", Messages.BndProjectManager_WorkspaceDescription); //$NON-NLS-1$ workspace.addBasicPlugin(TargetRepository.getTargetRepository()); @@ -111,8 +123,8 @@ static synchronized Workspace getWorkspace() throws Exception { return workspace; } - static void publishContainerEntries(List entries, Collection containers, - boolean isTest, Set mapped) { + static void publishContainerEntries(List entries, Collection containers, boolean isTest, + Set mapped) { for (Container container : containers) { TYPE type = container.getType(); if (type == TYPE.PROJECT) { @@ -159,4 +171,37 @@ public static List getClasspathEntries(Project project, IWorksp return entries; } + public static Optional createBndrun(IProject project) throws Exception { + if (BndProject.isBndProject(project) || PluginProject.isPluginProject(project)) { + IPluginModelBase model = PDECore.getDefault().getModelManager().findModel(project); + if (model == null) { + return Optional.empty(); + } + Workspace workspace = getWorkspace(); + Path base = workspace.getBase().toPath(); + String name = project.getName(); + Path file = Files.createTempFile(base, name, Constants.DEFAULT_BNDRUN_EXTENSION); + file.toFile().deleteOnExit(); + Files.createDirectories(file.getParent()); + Properties properties = new Properties(); + BundleDescription description = model.getBundleDescription(); + properties.setProperty(Constants.RUNREQUIRES, String.format("bnd.identity;id=%s;version=%s", //$NON-NLS-1$ + description.getSymbolicName(), description.getVersion())); + try (OutputStream stream = Files.newOutputStream(file)) { + properties.store(stream, + String.format("Bndrun generated by PDE for project %s", name)); //$NON-NLS-1$ + } + Bndrun bndrun = new PdeBndrun(workspace, file.toFile(), String.format("Bndrun for %s", name)); //$NON-NLS-1$ + bndrun.addClose(new AutoCloseable() { + + @Override + public void close() throws Exception { + Files.delete(file); + } + }); + return Optional.of(bndrun); + } + return Optional.empty(); + } + } diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndAdapter.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndAdapter.java index b7730d2041..2d3886171f 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndAdapter.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndAdapter.java @@ -23,9 +23,11 @@ import org.osgi.service.component.annotations.ReferencePolicy; import aQute.bnd.build.Project; +import biz.aQute.resolve.Bndrun; @Component(service = IAdapterFactory.class) -@AdapterTypes(adaptableClass = IProject.class, adapterNames = { Project.class, VersionControlIgnoresManager.class }) +@AdapterTypes(adaptableClass = IProject.class, adapterNames = { Project.class, VersionControlIgnoresManager.class, + Bndrun.class }) public class PdeBndAdapter implements IAdapterFactory { @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) @@ -33,8 +35,8 @@ public class PdeBndAdapter implements IAdapterFactory { @Override public T getAdapter(Object adaptableObject, Class adapterType) { - if (adapterType == Project.class) { - if (adaptableObject instanceof IProject project) { + if (adaptableObject instanceof IProject project) { + if (adapterType == Project.class) { try { return adapterType.cast(BndProjectManager.getBndProject(project).orElse(null)); } catch (Exception e) { @@ -42,9 +44,17 @@ public T getAdapter(Object adaptableObject, Class adapterType) { return null; } } - } - if (adapterType == VersionControlIgnoresManager.class) { - return adapterType.cast(versionControlIgnoresManager); + if (adapterType == VersionControlIgnoresManager.class) { + return adapterType.cast(versionControlIgnoresManager); + } + if (adapterType == Bndrun.class) { + try { + return adapterType.cast(BndProjectManager.createBndrun(project).orElse(null)); + } catch (Exception e) { + // can't adapt then... + return null; + } + } } return null; } diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndrun.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndrun.java new file mode 100644 index 0000000000..7a39440817 --- /dev/null +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBndrun.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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.pde.internal.core.bnd; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; + +import aQute.bnd.build.Container; +import aQute.bnd.build.Container.TYPE; +import aQute.bnd.build.DownloadBlocker; +import aQute.bnd.build.Workspace; +import aQute.bnd.osgi.Verifier; +import aQute.bnd.service.RepositoryPlugin; +import aQute.bnd.service.Strategy; +import aQute.bnd.version.Version; +import aQute.bnd.version.VersionRange; +import biz.aQute.resolve.Bndrun; + +class PdeBndrun extends Bndrun { + + private String name; + + PdeBndrun(Workspace workspace, File propertiesFile, String name) throws Exception { + super(workspace, propertiesFile); + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public Container getBundle(String bsn, String range, Strategy strategy, Map attrs) + throws Exception { + Container bundle = super.getBundle(bsn, range, strategy, attrs); + if (bundle == null || bundle.getType() == TYPE.ERROR) { + // workaround for https://github.com/bndtools/bnd/issues/6481 + // and https://github.com/bndtools/bnd/issues/6482 + // derived from the super class + List plugins = getPlugins(RepositoryPlugin.class); + range = Objects.requireNonNullElse(range, "0"); //$NON-NLS-1$ + attrs = Objects.requireNonNullElse(attrs, Collections.emptyMap()); + if (strategy == Strategy.EXACT) { + if (!Verifier.isVersion(range)) { + return bundle; + } + Version version = new Version(range); + for (RepositoryPlugin plugin : plugins) { + DownloadBlocker blocker = new DownloadBlocker(this); + File result = plugin.get(bsn, version, attrs, blocker); + if (result != null) { + return toContainer(bsn, range, attrs, result, blocker); + } + } + } else { + VersionRange versionRange = VERSION_ATTR_LATEST.equals(range) ? new VersionRange("0") //$NON-NLS-1$ + : new VersionRange(range); + SortedMap versions = new TreeMap<>(); + for (RepositoryPlugin plugin : plugins) { + try { + SortedSet vs = plugin.versions(bsn); + if (vs != null) { + for (Version v : vs) { + if (!versions.containsKey(v) && versionRange.includes(v)) { + versions.put(v, plugin); + } + } + } + } catch (UnsupportedOperationException ose) { + } + } + if (!versions.isEmpty()) { + Version provider = switch (strategy) { + case HIGHEST -> versions.lastKey(); + case LOWEST -> versions.firstKey(); + case EXACT -> null; + }; + if (provider != null) { + RepositoryPlugin repo = versions.get(provider); + if (repo != null) { + String version = provider.toString(); + DownloadBlocker blocker = new DownloadBlocker(this); + File result = repo.get(bsn, provider, attrs, blocker); + if (result != null) { + return toContainer(bsn, version, attrs, result, blocker); + } + } + } + } + } + } + return bundle; + } + +} diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBuildJar.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBuildJar.java new file mode 100644 index 0000000000..a432d8d71b --- /dev/null +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/PdeBuildJar.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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.pde.internal.core.bnd; + +import java.util.Objects; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.pde.core.build.IBuild; +import org.eclipse.pde.core.build.IBuildEntry; +import org.eclipse.pde.internal.core.ICoreConstants; +import org.eclipse.pde.internal.core.build.WorkspaceBuildModel; + +import aQute.bnd.osgi.Jar; + +/** + * This jar behaves like a pde-build on the java project that is including all + * compiled class files from the binaries, and evaluate the bin includes + */ +public class PdeBuildJar extends Jar { + + public PdeBuildJar(IJavaProject javaProject) throws CoreException { + this(javaProject, false); + } + + public PdeBuildJar(IJavaProject javaProject, boolean includeTest) throws CoreException { + super(javaProject.getProject().getName()); + IProject project = javaProject.getProject(); + IFile buildFile = project.getFile(ICoreConstants.BUILD_FILENAME_DESCRIPTOR); + if (buildFile.exists()) { + IBuild build = new WorkspaceBuildModel(buildFile).getBuild(); + IBuildEntry[] buildEntries = build.getBuildEntries(); + for (IBuildEntry entry : buildEntries) { + String name = entry.getName(); + if (name.startsWith(IBuildEntry.OUTPUT_PREFIX)) { + String folder = entry.getFirstToken(); + if (folder != null) { + IFolder outputFolder = project.getFolder(folder); + if (outputFolder.exists()) { + // TODO if the library is not '.' then it should + // actually become an embedded jar! + include(outputFolder, ""); //$NON-NLS-1$ + } + } + } + } + IBuildEntry entry = build.getEntry(IBuildEntry.BIN_INCLUDES); + if (entry != null) { + // TODO + } + } + if (includeTest) { + // TODO + IClasspathEntry[] classpath = javaProject.getRawClasspath(); + IPath outputLocation = javaProject.getOutputLocation(); + for (IClasspathEntry entry : classpath) { + System.out.println(entry); + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE && entry.isTest()) { + IPath testOutput = Objects.requireNonNullElse(entry.getOutputLocation(), outputLocation); + System.out.println(testOutput); + IFolder otherOutputFolder = project.getWorkspace().getRoot().getFolder(testOutput); + include(otherOutputFolder, ""); //$NON-NLS-1$ + } + } + } + + } + + private void include(IFolder folder, String prefix) throws CoreException { + for (IResource resource : folder.members()) { + if (resource instanceof IFile file) { + putResource(prefix + file.getName(), new FileResource(file)); + } else if (resource instanceof IFolder subfolder) { + include(subfolder, prefix + subfolder.getName() + "/"); //$NON-NLS-1$ + } + } + } + +} diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java index f4f75be900..c906e1f112 100644 --- a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java +++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/TargetRepository.java @@ -57,6 +57,7 @@ import org.osgi.service.repository.RepositoryContent; import aQute.bnd.osgi.Instruction; +import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.repository.BaseRepository; import aQute.bnd.osgi.resource.CapReqBuilder; import aQute.bnd.osgi.resource.ResourceUtils; @@ -73,6 +74,7 @@ private TargetRepository() { @Override public File get(String bsn, aQute.bnd.version.Version version, Map properties, DownloadListener... listeners) throws Exception { + System.out.println("GET: " + bsn); Optional description = getTargetPlatformState() .map(state -> state.getBundle(bsn, convert(version))); if (description.isEmpty()) { @@ -100,7 +102,28 @@ public File get(String bsn, aQute.bnd.version.Version version, Map + + + + JUNIT5_BUNDLES = List.of("junit-jupiter-api", "junit-jupiter-engine", "junit-jupiter-migrationsupport", "junit-jupiter-params", "junit-platform-commons", "junit-platform-engine", "junit-platform-launcher", "junit-platform-runner", "junit-platform-suite-api", "junit-vintage-engine", "org.opentest4j", "org.apiguardian.api", "org.junit", "org.hamcrest"); + private static final List JUNIT4_BUNDLES = List.of("org.junit", "org.hamcrest"); + + @Override + public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) throws CoreException { + System.out.println("######## Launch ######"); + try { + IJavaProject javaProject = verifyJavaProject(configuration); + IProject project = javaProject.getProject(); + try (Bndrun bndrun = Adapters.adapt(project, Bndrun.class)) { + if (bndrun == null) { + throw new CoreException(Status.error("Can't get bndrun template from project " + project.getName())); + } + bndrun.set(Constants.RUNTRACE, "true"); + Path testJar = createTestJar(javaProject, bndrun); + FileSetRepository repository = new FileSetRepository("extra", List.of(testJar.toFile())); + bndrun.addBasicPlugin(repository); + setupFramework(bndrun); + setupRunRequires(configuration, bndrun); + setupJava(configuration, bndrun); + //TODO create a fragment to our project that imports all packages so we do not need any manual imports e.g. + // for junit (should be providede by the junit classpath container already!) + //also there might be other things used e.g. extra-classpath or additional bundles! + Collection runbundles = doResolve(bndrun); + for (Container container : runbundles) { + System.out.println("runbundle: " + container.getFile()); + } + ProjectTester tester = bndrun.getProjectTester(); + tester.getProjectLauncher().addRunBundle(testJar.toString()); +// tester.getProjectLauncher().addBasicPlugin(repository); + tester.addTest("osgitest.HelloOSGiTest"); + //TODO add tests + tester.prepare(); + int errors = tester.test(); + printErrors(bndrun); + System.out.println("Errors: " + errors); + //TODO connect to the running process and show results in the junit view, for this we should migrate the code from bndtools about launching so it can be shared with pde + } + } catch (CoreException e) { + throw e; + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Path createTestJar(IJavaProject javaProject, Bndrun bndrun) throws Exception { + Path jarFile = Files.createTempFile("test-probe", ".jar"); + bndrun.addClose(() -> Files.delete(jarFile)); + try (PdeBuildJar jar = new PdeBuildJar(javaProject, true); Analyzer analyzer = new Analyzer(jar)) { + analyzer.setBundleVersion("1.0.0"); + analyzer.setBundleSymbolicName("test-probe"); + analyzer.set("Test-Cases", "osgitest.HelloOSGiTest"); + Manifest manifest = jar.getManifest(); + jar.remove(JarFile.MANIFEST_NAME); + jar.setManifest(new Manifest()); + //TODO find all testclasses! + //TODO make fragment host if project is a fragment + jar.setManifest(analyzer.calcManifest()); + jar.write(Files.newOutputStream(jarFile)); + } + System.out.println("Testprobe written to " + jarFile); + return jarFile; + } + + protected void printErrors(Report bndrun) { + //TODO throw on error? + bndrun.getErrors().forEach(err -> System.out.println("ERROR:" + err)); + bndrun.getWarnings().forEach(err -> System.out.println("WARN:" + err)); + } + + protected void setupFramework(Bndrun bndrun) { + bndrun.setRunfw("org.eclipse.osgi"); //$NON-NLS-1$ //TODO fetch from selection + } + + protected Collection doResolve(Bndrun bndrun) throws Exception { + String resolved = bndrun.resolve(false, false); + bndrun.set(Constants.RUNBUNDLES, resolved); + return bndrun.getRunbundles(); + } + + protected void setupJava(ILaunchConfiguration configuration, Bndrun bndrun) throws CoreException { + IVMInstall vmInstall = verifyVMInstall(configuration); + File executable = getJavaExecutable(vmInstall); + if (executable != null) { + bndrun.setProperty("java", executable.getAbsolutePath()); //$NON-NLS-1$ + } + } + + protected void setupRunRequires(ILaunchConfiguration configuration, Bndrun bndrun) { + Parameters runrequires = bndrun.getParameters(Constants.RUNREQUIRES); + String tester = addTestFramework(runrequires, configuration); + //TODO check if JUnit Classpath container is given + OSGiTestClasspathContributor.bundles(true).forEach(b -> { + runrequires.add(BND_IDENTITY, Attrs.create(ATTR_ID, b)); + }); + runrequires.add(BND_IDENTITY, Attrs.create(ATTR_ID, "test-probe")); + //TODO add extra bundles selected by the user! + StringBuilder builder = new StringBuilder(); + runrequires.append(builder); + bndrun.setRunRequires(builder.toString()); + bndrun.set(Constants.TESTER, tester); + } + + @SuppressWarnings("restriction") + private String addTestFramework(Parameters runrequires, ILaunchConfiguration configuration) { + ITestKind testKind = JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration); + if (TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(testKind.getId())) { + runrequires.add(BND_IDENTITY, Attrs.create(ATTR_ID, TESTER_JUNIT_PLATFORM)); + for (String bundle : JUNIT5_BUNDLES) { + runrequires.add(BND_IDENTITY, Attrs.create(ATTR_ID, bundle)); + } + return TESTER_JUNIT_PLATFORM; + } + runrequires.add(BND_IDENTITY, Attrs.create(ATTR_ID, TESTER_CLASSIC)); + for (String bundle : JUNIT4_BUNDLES) { + runrequires.add(BND_IDENTITY, Attrs.create(ATTR_ID, bundle)); + } + return TESTER_CLASSIC; + } + + @SuppressWarnings("restriction") + private static File getJavaExecutable(IVMInstall vmInstall) { + File installLocation = vmInstall.getInstallLocation(); + if (installLocation != null) { + return StandardVMType.findJavaExecutable(installLocation); + } + return null; + } + +} diff --git a/ui/org.eclipse.pde.ui/icons/osgi/osgi.png b/ui/org.eclipse.pde.ui/icons/osgi/osgi.png new file mode 100644 index 0000000000000000000000000000000000000000..f688893ce92a0b4ba4d9a81620f611ba243a41f1 GIT binary patch literal 4797 zcmeHKdsGuw8V?T(O%td@w?ym z`@Vb6+#G3ez&Pq(sT2xjoH$Sv3Z69cW&Z)VyLJy?;Bh8R7GVsob+XTa;2DTZg<|MxpvHblZ~&x?WUG)tICgqsQI~f z^Eqj8R~!GG)Aj`~Pip7Cc2?wrCljtOzEgSrivHQ|q|*&K`A3Yb!|C=n=gP*d>SgC{ zC<-|f`~vwFbN3oU&E%cTs*0*wiOtiEE3iY=ZRpI?w?E%Vk|F(@5LchIo9L)r-pYxA zQ;MnzYd071Lu_g%Zhhs`|EP1U$;NYGWOJQgW(9>}l}Y&eO2xjuZ}I_I6lQD|2L5ZI z%lg~Ji~QzNRkXzKr8^()qfbkilq;Qng<6L0_`JtX61BzAIpEaM*48HZ)l&_T;dbHP zv_7ju=R(KmqBC7vd^Wmt@gKLRiMMBUM*#2$*WB>bBxt*x{jvqN80H`Vl3 z_r4${HLDgJvDeq%YpV?hZD%4tdqqfsPz9-BU`kSsGfWyC=sOCwME#~}}f2LplqOhhu9KA#GC>6I!pROCMd0p5i4IHOUA zGMOnUDU1{jgVe_|SptE8iLjY$HViCaL#oz@nP9EK%?vS!A;Jv`J)tuaq!uz`VsbLc zD5TSY9(og>MkkRB!)pygEC4>3CQQdo>VujDeLzf{I8*k~uuFNJux&k19z8p+pCV7)Oq=xE?TD zjwoOcIgbYmlmZ2es|2134=xwM`1lAYvDRS3vBD z6*5ADhZ!+5n=DU`z?02II6OYb!;{N>YqS*C8$c(TFUU5)IYK|FCNHiu;hV+0nDFj5e=@WFg$VaZSYl1Se6n5Sr!F~DF#z8V9B`B5+}eK zR4L*xZ7dFUkD+#bBPV{R6;z%G#}knYV1$d~um?-Pg82v+gXMedM^gv z%XqZA{%>?q-yWxME%*vb0f(ik+T&H=p!I=#ae#<2VE*r^J+Ku>KGX$98YmQpGv>=` z+b0fFfY8n;miXCqTGMC(+V!v-H57_>oK3+w4m7)|DQo?lrYVOXIRdkNv_wTq9jGd!Ikev~_zfb;deBQO&**`zq*i z_T+gN{?l42aP8_md&#@-P(^JFFt^#M72lk4O3kY>S!snd_Zb^Jto{MBT(|U;ThsdLt0+yc==p z>y{b2scQ;&RdGxC@g=VdULGS2Pw#GgwL9qPv(-z5j%#DiNzxT}w|}F)XDfbS#iDj+ zv}?cqrR6!9UZyt%T@yd)jha)F6nNRG>2`c)Nvp$sHlK`uOPb?qqlJk**KU!E9v}B6 zXxvHHHmFqvp?fD6y<9V|)w`K`Ds{}G83m=-h0?Z5s8e!jUd-8B{@a?iICn1)q|HO- zR@^MkM()D_t`+C-dz0$gCrp|7RkyauJ8Cc62g1Uu57d^k=JyE7zf;xU`)CYzMvvOn zI-aWD*Z)Htd#k3gd@3XgLmrl&t4c20?Q$}-c|yUG`aSzomOtI{!mID~=?HZQgYAfP z$zJ@$XVIreBPs3|AC|`O3;hd4|787|-7s-`{etS`j0q|ZZEV80zf+0^9_9TQmtM4_ zE__>wTYP=So``|A^L@tz${oL6-*s&I(Jo0prFu-p!n5Vok)JXWxHY7Ebk_by4>EIl zt{*y-_O#LNtj)q@``lke+{vXhx^A|;nBYQDRW=s6{xr6I?}3-439Et+q^?Tu=N@iE z=6v?W<_c&oXS^4o+@S4xSo*3g>Hg#NruiS&-|nsaXkB)`4;yqLMeG+WI_R@}?f(Fe CPY{~` literal 0 HcmV?d00001 diff --git a/ui/org.eclipse.pde.ui/icons/osgi/osgi@2x.png b/ui/org.eclipse.pde.ui/icons/osgi/osgi@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4aff61da91b87a1eb37364d3fa0092e9acede11d GIT binary patch literal 5716 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 L#gkI#6teTb(`DpU literal 0 HcmV?d00001 diff --git a/ui/org.eclipse.pde.ui/plugin.xml b/ui/org.eclipse.pde.ui/plugin.xml index cb7928f446..492108135e 100644 --- a/ui/org.eclipse.pde.ui/plugin.xml +++ b/ui/org.eclipse.pde.ui/plugin.xml @@ -687,6 +687,21 @@ id="org.eclipse.pde.ui.JunitLaunchImage"> + + + + + + + + +