diff --git a/features/org.eclipse.pde-feature/feature.xml b/features/org.eclipse.pde-feature/feature.xml
index d52425401a..790c3917a7 100644
--- a/features/org.eclipse.pde-feature/feature.xml
+++ b/features/org.eclipse.pde-feature/feature.xml
@@ -28,6 +28,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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 495c43634d..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,
@@ -134,4 +136,5 @@ Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: org.eclipse.pde.core
Service-Component: OSGI-INF/org.eclipse.pde.internal.core.annotations.OSGiAnnotationsClasspathContributor.xml,
- OSGI-INF/org.eclipse.pde.internal.core.bnd.PdeBndAdapter.xml
+ OSGI-INF/org.eclipse.pde.internal.core.bnd.PdeBndAdapter.xml,
+ OSGI-INF/org.eclipse.pde.internal.core.osgitest.OSGiTestClasspathContributor.xml
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 9b551483ba..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
@@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
@@ -33,6 +34,7 @@
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.build.IBuild;
import org.eclipse.pde.core.build.IBuildModel;
import org.eclipse.pde.core.plugin.IFragment;
@@ -50,6 +52,10 @@
import org.eclipse.pde.internal.core.plugin.Fragment;
import org.eclipse.pde.internal.core.plugin.Plugin;
import org.eclipse.pde.internal.core.plugin.PluginBase;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
import org.osgi.resource.Resource;
public class ClasspathUtilCore {
@@ -87,34 +93,97 @@ private static Collection 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) {
+ 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();
+ 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 +196,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/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 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();
+ }
+
+}
diff --git a/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
index 3ef88e7cdb..806c153442 100644
--- a/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.launching/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %name
Bundle-SymbolicName: org.eclipse.pde.launching;singleton:=true
-Bundle-Version: 3.13.300.qualifier
+Bundle-Version: 3.14.0.qualifier
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-Vendor: %provider-name
Require-Bundle: org.eclipse.jdt.junit.core;bundle-version="[3.6.0,4.0.0)",
@@ -14,7 +14,10 @@ Require-Bundle: org.eclipse.jdt.junit.core;bundle-version="[3.6.0,4.0.0)",
org.eclipse.pde.build;bundle-version="[3.12.300,4.0.0)",
org.eclipse.pde.core;bundle-version="[3.2.0,4.0.0)",
org.eclipse.jdt.debug;bundle-version="[3.2.0,4.0.0)",
- org.eclipse.core.filesystem;bundle-version="[1.0.0,2.0.0)"
+ org.eclipse.core.filesystem;bundle-version="[1.0.0,2.0.0)",
+ biz.aQute.resolve;bundle-version="[7.1.0,8.0.0)",
+ biz.aQute.bndlib;bundle-version="[7.1.0,8.0.0)",
+ biz.aQute.repository
Bundle-Activator: org.eclipse.pde.internal.launching.PDELaunchingPlugin
Export-Package: org.eclipse.pde.internal.launching;x-friends:="org.eclipse.pde.ui,
org.eclipse.pde.unittest.junit",
@@ -22,6 +25,7 @@ Export-Package: org.eclipse.pde.internal.launching;x-friends:="org.eclipse.pde.u
org.eclipse.pde.unittest.junit",
org.eclipse.pde.internal.launching.sourcelookup;x-internal:=true,
org.eclipse.pde.launching
+Import-Package: org.osgi.service.repository;version="1.1.0"
Bundle-ActivationPolicy: lazy
Bundle-Localization: plugin
Automatic-Module-Name: org.eclipse.pde.launching
diff --git a/ui/org.eclipse.pde.launching/plugin.properties b/ui/org.eclipse.pde.launching/plugin.properties
index a92014d652..31496d2051 100644
--- a/ui/org.eclipse.pde.launching/plugin.properties
+++ b/ui/org.eclipse.pde.launching/plugin.properties
@@ -25,10 +25,14 @@ OSGiLaunchDelegate.name=OSGi Framework Launcher
OSGiLaunchDelegate.description=The OSGi Framework launcher is used to launch an OSGi framework and run or debug OSGi bundles
JUnitPluginLaunchDelegate.name= JUnit Plugin Test Launcher
JUnitPluginLaunchDelegate.description=The JUnit Plugin Test launcher is used to run and debug test suites for plug-in projects
+OSGiTestLaunchDelegate.name= OSGi Test Launcher
+JUnitPluginLaunchDelegate.description=The OSGi Test launcher is used to run and debug test suites for OSGi projects
Equinox.shortcut.label = Equinox
launcher.junit.name = JUnit Plug-in Test
+launcher.osgitest.name = OSGi Test
+
launchConfigurationType.name = Eclipse Application
launcher.framework.name = OSGi Framework
diff --git a/ui/org.eclipse.pde.launching/plugin.xml b/ui/org.eclipse.pde.launching/plugin.xml
index 5b290b7d61..17587ae067 100644
--- a/ui/org.eclipse.pde.launching/plugin.xml
+++ b/ui/org.eclipse.pde.launching/plugin.xml
@@ -38,6 +38,19 @@
sourceLocatorId="org.eclipse.pde.ui.launcher.PDESourceLookupDirector"
sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer">
+
+
+
+
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 0000000000..f688893ce9
Binary files /dev/null and b/ui/org.eclipse.pde.ui/icons/osgi/osgi.png differ
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 0000000000..4aff61da91
Binary files /dev/null and b/ui/org.eclipse.pde.ui/icons/osgi/osgi@2x.png differ
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">
+
+
+
+
+
+
+
+
+