Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for launching OSGi Tests #1620

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions features/org.eclipse.pde-feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@
<import plugin="org.bndtools.templates.template"/>
<import plugin="biz.aQute.repository"/>
<import plugin="bndtools.jareditor"/>
<!-- TODO maybe better use a dedicated feature for easier inclusion in target platforms -->
<import plugin="org.osgi.test.common"/>
<import plugin="org.osgi.test.junit5"/>
<import plugin="org.osgi.test.junit5.cm"/>
<import plugin="org.osgi.test.junit4"/>
<import plugin="org.osgi.test.assertj.framework"/>
<import plugin="org.osgi.test.assertj.log"/>
<import plugin="org.osgi.test.assertj.promise"/>

<import plugin="biz.aQute.tester"/>
<import plugin="biz.aQute.tester.junit-platform"/>
<import plugin="biz.aQute.bnd.embedded-repo"/>

</requires>

<plugin
Expand Down
49 changes: 47 additions & 2 deletions ui/org.eclipse.pde.bnd.ui/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ One problem for such a reusable component is that it usually needs to get holds
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
the use of [OSGi services / Dependency Injection](https://eclipse.dev/eclipse/news/4.18/platform_isv.php#dialog-adapterfactory-as-service) already.

### The IProject adapter
### The `IProject` adapter

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
transform an (Eclipse) `IProject` into a (bndlib) `Project` (from were the Workspace then can be derived), an example might look like this:

```
@Component
@AdapterTypes(adaptableClass = IProject.class, adapterNames = Project.class)
public class BndPluginAdapter implements IAdapterFactory {
public class BndProjectAdapter implements IAdapterFactory {

@Override
public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) {
Expand All @@ -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> T getAdapter(Object adaptableObject, Class<T> 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)
Expand Down
7 changes: 5 additions & 2 deletions ui/org.eclipse.pde.core/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -87,34 +93,97 @@ private static Collection<ClasspathLibrary> collectLibraryEntries(IPluginModelBa
}

public static Stream<IClasspathEntry> classpathEntriesForBundle(String id) {
return classpathEntriesForBundle(id, false, new IClasspathAttribute[0]);
}

public static Stream<IClasspathEntry> 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<IClasspathEntry> 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<IClasspathEntry> bundleClasspath = classpathEntriesForRuntimeBundle(runtimeBundle, extra).stream();
if (includeRequired) {
return Stream.concat(bundleClasspath, getRequiredByWire(runtimeBundle, extra));
}
return bundleClasspath;
}

private static Stream<IClasspathEntry> getRequiredByDescription(BundleDescription description,
IClasspathAttribute[] extra) {
BundleWiring wiring = description.getWiring();
if (wiring == null) {
return Stream.empty();
}

List<BundleWire> 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<IClasspathEntry> 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<IClasspathEntry> 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<IClasspathEntry> getRequiredByWire(Bundle bundle, IClasspathAttribute[] extra) {
BundleWiring wiring = bundle.adapt(BundleWiring.class);
if (wiring == null) {
return Stream.empty();
}
List<BundleWire> 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<IClasspathEntry> 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) {
Expand All @@ -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<ClasspathLibrary> entries) {
Expand Down
Loading
Loading