Skip to content

Commit cb97965

Browse files
committed
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.
1 parent b54cb7b commit cb97965

File tree

18 files changed

+477
-30
lines changed

18 files changed

+477
-30
lines changed

features/org.eclipse.pde-feature/feature.xml

+6
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@
2828
<import plugin="org.bndtools.templates.template"/>
2929
<import plugin="biz.aQute.repository"/>
3030
<import plugin="bndtools.jareditor"/>
31+
<!-- TODO maybe better use a dedicated feature for easier inclusion in target platforms -->
3132
<import plugin="org.osgi.test.common"/>
3233
<import plugin="org.osgi.test.junit5"/>
3334
<import plugin="org.osgi.test.junit5.cm"/>
3435
<import plugin="org.osgi.test.junit4"/>
3536
<import plugin="org.osgi.test.assertj.framework"/>
3637
<import plugin="org.osgi.test.assertj.log"/>
3738
<import plugin="org.osgi.test.assertj.promise"/>
39+
40+
<import plugin="biz.aQute.tester"/>
41+
<import plugin="biz.aQute.tester.junit-platform"/>
42+
<import plugin="biz.aQute.bnd.embedded-repo"/>
43+
3844
</requires>
3945

4046
<plugin

ui/org.eclipse.pde.bnd.ui/README.MD

+47-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ One problem for such a reusable component is that it usually needs to get holds
1818
To mitigate we use the [Eclipse Adapter Pattern](https://www.eclipse.org/articles/Article-Adapters/) as it is widely used in Eclipse, flexible and allows
1919
the use of [OSGi services / Dependency Injection](https://eclipse.dev/eclipse/news/4.18/platform_isv.php#dialog-adapterfactory-as-service) already.
2020

21-
### The IProject adapter
21+
### The `IProject` adapter
2222

2323
Components need to learn the project and workspace of a bndlib backed project, for this the very first step for an integration is to provide an adapter that can
2424
transform an (Eclipse) `IProject` into a (bndlib) `Project` (from were the Workspace then can be derived), an example might look like this:
2525

2626
```
2727
@Component
2828
@AdapterTypes(adaptableClass = IProject.class, adapterNames = Project.class)
29-
public class BndPluginAdapter implements IAdapterFactory {
29+
public class BndProjectAdapter implements IAdapterFactory {
3030
3131
@Override
3232
public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
@@ -46,6 +46,51 @@ public class BndPluginAdapter implements IAdapterFactory {
4646
}
4747
```
4848

49+
### The `Bndrun` adapter
50+
51+
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.
52+
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
53+
of the project and the workspace be able to resolve such bundle.
54+
55+
```
56+
@Component
57+
@AdapterTypes(adaptableClass = IProject.class, adapterNames = Bndrun.class)
58+
public class BndRunAdapter implements IAdapterFactory {
59+
60+
@Override
61+
public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
62+
if (adaptableObject instanceof IProject eclipseProject) {
63+
if (adapterType == Project.class) {
64+
//... here we need to determine if the project is managed by our tooling, e.g. it is a PDE, Bndtool, Maven, ... backed project
65+
if (/*... is relevant ... */) {
66+
Workspace workspace = .... find the workspace according to your tooling implementation
67+
Path base = workspace.getBase().toPath();
68+
Path file = Files.createTempFile(base, project.getName(), ".bndrun");
69+
file.toFile().deleteOnExit();
70+
Files.createDirectories(file.getParent());
71+
Properties properties = new Properties();
72+
String bsn = ... derive the symbolic name ...
73+
String version = ... derive the version name ...
74+
properties.setProperty(Constants.RUNREQUIRES, String.format("bnd.identity; id=%s;version=%s", bsn, version));
75+
//maybe more customizations....
76+
Bndrun bndrun = Bndrun.createBndrun(workspace, file.toFile());
77+
//make sure the file is deleted when the bndrun is closed...
78+
bndrun.addClose(new AutoCloseable() {
79+
80+
@Override
81+
public void close() throws Exception {
82+
Files.delete(file);
83+
}
84+
});
85+
}
86+
}
87+
}
88+
return null;
89+
}
90+
91+
}
92+
```
93+
4994
## Available components
5095

5196
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)

ui/org.eclipse.pde.core/META-INF/MANIFEST.MF

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
22
Bundle-ManifestVersion: 2
33
Bundle-Name: %name
44
Bundle-SymbolicName: org.eclipse.pde.core; singleton:=true
5-
Bundle-Version: 3.20.100.qualifier
5+
Bundle-Version: 3.21.0.qualifier
66
Bundle-Activator: org.eclipse.pde.internal.core.PDECore
77
Bundle-Vendor: %provider-name
88
Bundle-Localization: plugin
@@ -97,6 +97,7 @@ Import-Package: aQute.bnd.build;version="[4.4.0,5.0.0)",
9797
aQute.bnd.service.progress;version="[1.3.0,2.0.0)",
9898
aQute.bnd.version;version="[2.2.0,3.0.0)",
9999
aQute.service.reporter;version="[1.2.0,2.0.0)",
100+
biz.aQute.resolve;version="[9.1.0,10.0.0)",
100101
org.bndtools.versioncontrol.ignores.manager.api;version="[1.0.0,2.0.0)",
101102
org.eclipse.equinox.internal.p2.publisher.eclipse,
102103
org.eclipse.equinox.p2.publisher,

ui/org.eclipse.pde.core/src/org/eclipse/pde/core/build/IBuildEntry.java

+20-7
Original file line numberDiff line numberDiff line change
@@ -27,39 +27,39 @@ public interface IBuildEntry extends IWritable {
2727
/**
2828
* A property name for changes to the 'name' field.
2929
*/
30-
public static final String P_NAME = "name"; //$NON-NLS-1$
30+
String P_NAME = "name"; //$NON-NLS-1$
3131
/**
3232
* The prefix for any key denoting the source folders that
3333
* should be compiled into a JAR. The suffix will
3434
* be the name of the JAR.
3535
*/
36-
public static final String JAR_PREFIX = "source."; //$NON-NLS-1$
36+
String JAR_PREFIX = "source."; //$NON-NLS-1$
3737
/**
3838
* The prefix for any key denoting output folders for a particular
3939
* JAR. The suffix will be the name of the JAR.
4040
*/
41-
public static final String OUTPUT_PREFIX = "output."; //$NON-NLS-1$
41+
String OUTPUT_PREFIX = "output."; //$NON-NLS-1$
4242
/**
4343
* The name of the key that lists all the folders and files
4444
* to be included in the binary build.
4545
*/
46-
public static final String BIN_INCLUDES = "bin.includes"; //$NON-NLS-1$
46+
String BIN_INCLUDES = "bin.includes"; //$NON-NLS-1$
4747
/**
4848
* The name of the key that lists all the folders and files
4949
* to be included in the source build.
5050
*/
51-
public static final String SRC_INCLUDES = "src.includes"; //$NON-NLS-1$
51+
String SRC_INCLUDES = "src.includes"; //$NON-NLS-1$
5252
/**
5353
* The name of the key that declares extra library entries to be added
5454
* to the class path at build time only..
5555
*/
56-
public static final String JARS_EXTRA_CLASSPATH = "jars.extra.classpath"; //$NON-NLS-1$
56+
String JARS_EXTRA_CLASSPATH = "jars.extra.classpath"; //$NON-NLS-1$
5757
/**
5858
* The name of the key that declares additional plug-in dependencies to augment development classpath
5959
*
6060
* @since 3.2
6161
*/
62-
public static final String SECONDARY_DEPENDENCIES = "additional.bundles"; //$NON-NLS-1$
62+
String SECONDARY_DEPENDENCIES = "additional.bundles"; //$NON-NLS-1$
6363

6464
/**
6565
* Adds the token to the list of token for this entry.
@@ -89,6 +89,19 @@ public interface IBuildEntry extends IWritable {
8989
*/
9090
String[] getTokens();
9191

92+
/**
93+
* Returns the first token for this entry
94+
*
95+
* @since 3.21
96+
*/
97+
default String getFirstToken() {
98+
String[] tokens = getTokens();
99+
if (tokens == null || tokens.length == 0) {
100+
return null;
101+
}
102+
return tokens[0];
103+
}
104+
92105
/**
93106
* Returns true if the provided token exists in this entry.
94107
* @param token the string token to look for

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/ClasspathUtilCore.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,14 @@ protected static Stream<IClasspathEntry> classpathEntriesForModelBundle(IPluginM
152152
}
153153
boolean isJarShape = new File(location).isFile();
154154
IPluginLibrary[] libraries = model.getPluginBase().getLibraries();
155-
if (isJarShape || libraries.length == 0) {
155+
if (isJarShape) {
156156
return Stream.of(getEntryForPath(IPath.fromOSString(location), extra));
157157
}
158+
if (libraries.length == 0) {
159+
List<IClasspathEntry> entries = new ArrayList<>();
160+
PDEClasspathContainer.addExternalPlugin(model, null, entries);
161+
return entries.stream();
162+
}
158163
return Arrays.stream(libraries).filter(library -> !IPluginLibrary.RESOURCE.equals(library.getType()))
159164
.map(library -> {
160165
String name = library.getName();

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/PDEClasspathContainer.java

+52-7
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@
3030
import org.eclipse.jdt.core.JavaCore;
3131
import org.eclipse.jdt.core.JavaModelException;
3232
import org.eclipse.osgi.service.resolver.BundleDescription;
33+
import org.eclipse.pde.core.build.IBuild;
34+
import org.eclipse.pde.core.build.IBuildEntry;
35+
import org.eclipse.pde.core.plugin.IPluginBase;
3336
import org.eclipse.pde.core.plugin.IPluginLibrary;
3437
import org.eclipse.pde.core.plugin.IPluginModelBase;
3538
import org.eclipse.pde.core.plugin.PluginRegistry;
39+
import org.eclipse.pde.internal.core.build.ExternalBuildModel;
3640
import org.osgi.resource.Resource;
3741

3842
public class PDEClasspathContainer {
@@ -78,7 +82,8 @@ public static IClasspathEntry[] getExternalEntries(IPluginModelBase model) {
7882
}
7983

8084
protected static void addExternalPlugin(IPluginModelBase model, List<Rule> rules, List<IClasspathEntry> entries) {
81-
boolean isJarShape = new File(model.getInstallLocation()).isFile();
85+
File file = new File(model.getInstallLocation());
86+
boolean isJarShape = file.isFile();
8287
if (isJarShape) {
8388
IPath srcPath = ClasspathUtilCore.getSourceAnnotation(model, ".", isJarShape); //$NON-NLS-1$
8489
if (srcPath == null) {
@@ -93,14 +98,19 @@ protected static void addExternalPlugin(IPluginModelBase model, List<Rule> rules
9398
addLibraryEntry(path, path, rules, getClasspathAttributes(model), entries);
9499
}
95100
} else {
96-
IPluginLibrary[] libraries = model.getPluginBase().getLibraries();
101+
IPluginBase pluginBase = model.getPluginBase();
102+
IPluginLibrary[] libraries = pluginBase.getLibraries();
97103
if (libraries.length == 0) {
98-
// If there are no libraries, assume the root of the plug-in is the library '.'
99-
IPath srcPath = ClasspathUtilCore.getSourceAnnotation(model, ".", isJarShape); //$NON-NLS-1$
100-
if (srcPath == null) {
101-
srcPath = IPath.fromOSString(model.getInstallLocation());
104+
if (!addEntriesFromHostEclipse(model, rules, entries, file)) {
105+
// If there are no libraries, assume the root of the plug-in
106+
// is the library '.'
107+
IPath srcPath = ClasspathUtilCore.getSourceAnnotation(model, ".", isJarShape); //$NON-NLS-1$
108+
if (srcPath == null) {
109+
srcPath = IPath.fromOSString(model.getInstallLocation());
110+
}
111+
addLibraryEntry(IPath.fromOSString(model.getInstallLocation()), srcPath, rules,
112+
getClasspathAttributes(model), entries);
102113
}
103-
addLibraryEntry(IPath.fromOSString(model.getInstallLocation()), srcPath, rules, getClasspathAttributes(model), entries);
104114
} else {
105115
for (IPluginLibrary library : libraries) {
106116
if (IPluginLibrary.RESOURCE.equals(library.getType())) {
@@ -125,6 +135,41 @@ protected static void addExternalPlugin(IPluginModelBase model, List<Rule> rules
125135
}
126136
}
127137

138+
protected static boolean addEntriesFromHostEclipse(IPluginModelBase model, List<Rule> rules,
139+
List<IClasspathEntry> entries, File file) {
140+
boolean hasBuildEntries = false;
141+
// if build properties exits in folder than this is a local
142+
// project from an Eclipse started from a workspace
143+
if (new File(file, ICoreConstants.BUILD_FILENAME_DESCRIPTOR).isFile()) {
144+
IBuild build = new ExternalBuildModel(model.getInstallLocation()).getBuild();
145+
IBuildEntry[] buildEntries = build.getBuildEntries();
146+
for (IBuildEntry entry : buildEntries) {
147+
String name = entry.getName();
148+
if (name.startsWith(IBuildEntry.OUTPUT_PREFIX)) {
149+
String folder = entry.getFirstToken();
150+
if (folder != null) {
151+
File outputFolder = new File(file, folder);
152+
if (outputFolder.isDirectory()) {
153+
hasBuildEntries = true;
154+
IBuildEntry sourceEntry = build.getEntry(IBuildEntry.JAR_PREFIX
155+
+ name.substring(IBuildEntry.OUTPUT_PREFIX.length()));
156+
IPath srcPath = null;
157+
if (sourceEntry != null) {
158+
String firstToken = sourceEntry.getFirstToken();
159+
if (firstToken != null) {
160+
srcPath = IPath.fromOSString(new File(file, firstToken).getAbsolutePath());
161+
}
162+
}
163+
addLibraryEntry(IPath.fromOSString(outputFolder.getAbsolutePath()), srcPath, rules,
164+
getClasspathAttributes(model), entries);
165+
}
166+
}
167+
}
168+
}
169+
}
170+
return hasBuildEntries;
171+
}
172+
128173
protected static void addLibraryEntry(IPath path, IPath srcPath, List<Rule> rules, IClasspathAttribute[] attributes,
129174
List<IClasspathEntry> entries) {
130175
IClasspathEntry entry = null;

ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/bnd/BndProjectManager.java

+54-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414
package org.eclipse.pde.internal.core.bnd;
1515

1616
import java.io.File;
17+
import java.io.OutputStream;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
1720
import java.util.ArrayList;
1821
import java.util.Collection;
1922
import java.util.HashSet;
2023
import java.util.List;
2124
import java.util.Optional;
25+
import java.util.Properties;
2226
import java.util.Set;
2327
import java.util.stream.Collectors;
2428
import java.util.stream.Stream;
@@ -33,6 +37,8 @@
3337
import org.eclipse.jdt.core.IClasspathEntry;
3438
import org.eclipse.jdt.core.IJavaProject;
3539
import org.eclipse.jdt.core.JavaCore;
40+
import org.eclipse.osgi.service.resolver.BundleDescription;
41+
import org.eclipse.pde.core.plugin.IPluginModelBase;
3642
import org.eclipse.pde.internal.core.PDECore;
3743
import org.eclipse.pde.internal.core.annotations.OSGiAnnotationsClasspathContributor;
3844
import org.eclipse.pde.internal.core.natures.BndProject;
@@ -44,6 +50,7 @@
4450
import aQute.bnd.build.Workspace;
4551
import aQute.bnd.osgi.Constants;
4652
import aQute.bnd.osgi.Processor;
53+
import biz.aQute.resolve.Bndrun;
4754

4855
public class BndProjectManager {
4956

@@ -96,8 +103,13 @@ static synchronized Workspace getWorkspace() throws Exception {
96103
if (workspace == null) {
97104
try (Processor run = new Processor()) {
98105
run.setProperty(Constants.STANDALONE, TRUE);
99-
IPath path = PDECore.getDefault().getStateLocation().append(Project.BNDCNF);
100-
workspace = Workspace.createStandaloneWorkspace(run, path.toFile().toURI());
106+
IPath path = PDECore.getDefault().getStateLocation().append(Constants.DEFAULT_BND_EXTENSION)
107+
.append(Project.BNDCNF);
108+
File file = path.toFile();
109+
file.mkdirs();
110+
workspace = Workspace.createStandaloneWorkspace(run, file.toURI());
111+
workspace.setBase(file.getParentFile());
112+
workspace.setBuildDir(file);
101113
workspace.set("workspaceName", Messages.BndProjectManager_WorkspaceName); //$NON-NLS-1$
102114
workspace.set("workspaceDescription", Messages.BndProjectManager_WorkspaceDescription); //$NON-NLS-1$
103115
workspace.addBasicPlugin(TargetRepository.getTargetRepository());
@@ -111,8 +123,8 @@ static synchronized Workspace getWorkspace() throws Exception {
111123
return workspace;
112124
}
113125

114-
static void publishContainerEntries(List<IClasspathEntry> entries, Collection<Container> containers,
115-
boolean isTest, Set<Project> mapped) {
126+
static void publishContainerEntries(List<IClasspathEntry> entries, Collection<Container> containers, boolean isTest,
127+
Set<Project> mapped) {
116128
for (Container container : containers) {
117129
TYPE type = container.getType();
118130
if (type == TYPE.PROJECT) {
@@ -159,4 +171,42 @@ public static List<IClasspathEntry> getClasspathEntries(Project project, IWorksp
159171
return entries;
160172
}
161173

174+
public static Optional<Bndrun> createBndrun(IProject project) throws Exception {
175+
if (BndProject.isBndProject(project) || PluginProject.isPluginProject(project)) {
176+
IPluginModelBase model = PDECore.getDefault().getModelManager().findModel(project);
177+
if (model == null) {
178+
return Optional.empty();
179+
}
180+
Workspace workspace = getWorkspace();
181+
Path base = workspace.getBase().toPath();
182+
String name = project.getName();
183+
Path file = Files.createTempFile(base, name, Constants.DEFAULT_BNDRUN_EXTENSION);
184+
file.toFile().deleteOnExit();
185+
Files.createDirectories(file.getParent());
186+
Properties properties = new Properties();
187+
BundleDescription description = model.getBundleDescription();
188+
properties.setProperty(Constants.RUNREQUIRES, String.format("bnd.identity;id=%s;version=%s", //$NON-NLS-1$
189+
description.getSymbolicName(), description.getVersion()));
190+
try (OutputStream stream = Files.newOutputStream(file)) {
191+
properties.store(stream,
192+
String.format("Bndrun generated by PDE for project %s", name)); //$NON-NLS-1$
193+
}
194+
Bndrun bndrun = new Bndrun(workspace, file.toFile()) {
195+
@Override
196+
public String getName() {
197+
return String.format("Bndrun for %s", name); //$NON-NLS-1$
198+
}
199+
};
200+
bndrun.addClose(new AutoCloseable() {
201+
202+
@Override
203+
public void close() throws Exception {
204+
Files.delete(file);
205+
}
206+
});
207+
return Optional.of(bndrun);
208+
}
209+
return Optional.empty();
210+
}
211+
162212
}

0 commit comments

Comments
 (0)