Skip to content

Commit

Permalink
Startup ordering based on OSGi Capabilities (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Feb 7, 2023
2 parents df8c7c4 + 7e89542 commit 0803730
Show file tree
Hide file tree
Showing 19 changed files with 767 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void apply(Project project) {
} catch (IOException e) {
throw new GradleException("Unable to determine solstice version", e);
}
project.getDependencies().add(EQUO_IDE, "org.slf4j:slf4j-simple:2.0.6");
project.getDependencies().add(EQUO_IDE, "org.slf4j:slf4j-simple:1.7.36");

P2Client.Caching caching = P2ModelDsl.caching(project);
var equoIdeTask =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public void initOnly() throws IOException {
"equoIde {",
" p2repo 'https://download.eclipse.org/eclipse/updates/4.26/'",
" install 'org.eclipse.swt'",
" install 'org.eclipse.equinox.common'",
"}");
runAndAssert("equoIde", "--init-only")
.matches("(?s)(.*)Loaded (\\d+) bundles using Atomos(.*)");
Expand All @@ -81,6 +82,7 @@ public void initOnly() throws IOException {
"equoIde {",
" p2repo 'https://download.eclipse.org/eclipse/updates/4.26/'",
" install 'org.eclipse.swt'",
" install 'org.eclipse.equinox.common'",
" useAtomos = false",
"}");
runAndAssert("equoIde", "--init-only")
Expand All @@ -93,6 +95,7 @@ public void initOnly() throws IOException {
"equoIde {",
" p2repo 'https://download.eclipse.org/eclipse/updates/4.26/'",
" install 'org.eclipse.swt'",
" install 'org.eclipse.equinox.common'",
"}");
runAndAssert("equoIde", "--init-only", "--dont-use-atomos")
.matches("(?s)(.*)Loaded (\\d+) bundles not using Atomos(.*)");
Expand Down
4 changes: 2 additions & 2 deletions plugin-maven/src/main/java/dev/equo/ide/maven/LaunchMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ public void execute() throws MojoExecutionException, MojoFailureException {
new DefaultArtifact("dev.equo.ide:solstice:" + NestedJars.solsticeVersion()), null));
deps.add(
new Dependency(
new DefaultArtifact("org.slf4j:slf4j-api:2.0.6"),
new DefaultArtifact("org.slf4j:slf4j-api:1.7.36"),
null,
null,
EXCLUDE_ALL_TRANSITIVES));
deps.add(
new Dependency(
new DefaultArtifact("org.slf4j:slf4j-simple:2.0.6"),
new DefaultArtifact("org.slf4j:slf4j-simple:1.7.36"),
null,
null,
EXCLUDE_ALL_TRANSITIVES));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ private void integrationTestUseAtomos(boolean useAtomos)
+ "</p2repos>\n"
+ "<installs>\n"
+ " <install>org.eclipse.swt</install>\n"
+ " <install>org.eclipse.equinox.common</install>\n"
+ "</installs>");
var output = mvnw("equo-ide:launch -DinitOnly -DuseAtomos=" + useAtomos);
output.raw().matches("(?s)(.*)Loaded (\\d+) bundles(.*)");
Expand Down
4 changes: 4 additions & 0 deletions solstice/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format.

## [Unreleased]
### Added
- Introduced `Capability` which takes OSGi `Provide-Capability`/`Require-Capability` into account. ([#71](https://github.com/equodev/equo-ide/pull/71))
- In particular, this means that we don't need to do any manual startup ordering anymore.
- Also reverted from SLF4J 2.x to 1.x because 2.x uses fancy parts of the capability system that nothing else in Eclipse seems to use.
### Fixed
- No more errors on filesystems which don't support atomic move ([#73](https://github.com/equodev/equo-ide/pull/73))

Expand Down
6 changes: 5 additions & 1 deletion solstice/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ spotless {
}
}

String VER_SLF4J = '2.0.6'
String VER_SLF4J = '1.7.36'
dependencies {
api 'com.diffplug.durian:durian-swt.os:4.1.1'
implementation 'org.apache.felix:org.apache.felix.scr:2.2.2'
Expand Down Expand Up @@ -97,6 +97,10 @@ p2deps {
addFilter 'multiple-ICompilationUnit', {
it.exclude 'org.apache.jasper.glassfish'
}

install 'org.eclipse.releng.java.languages.categoryIU'
p2repo 'https://download.eclipse.org/buildship/updates/e423/releases/3.x/3.1.6.v20220511-1359/'
install 'org.eclipse.buildship.feature.group'
}
into 'welcomeCompileOnly', {
p2repo 'https://download.eclipse.org/eclipse/updates/4.26/'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@ public static void main(String[] args)
solstice.openSolstice();
SolsticeIdeBootstrapServices.apply(installDir, solstice.getContext());
}
solstice.start("org.apache.felix.scr");
solstice.startAllWithLazy(false);
solstice.start("org.eclipse.ui.ide.application");
if (useAtomos) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
package dev.equo.solstice;

import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -65,10 +64,9 @@ public Optional<Map<String, String>> apply(String location, Map<String, String>

public Map<String, String> atomosHeaders(SolsticeManifest manifest) {
Map<String, String> atomos = new LinkedHashMap<>(manifest.getHeadersOriginal());
setHeader(atomos, Constants.IMPORT_PACKAGE, manifest.getPkgImports());
setHeader(atomos, Constants.EXPORT_PACKAGE, manifest.getPkgExports());
setHeader(atomos, Constants.REQUIRE_BUNDLE, manifest.getRequiredBundles());
setHeader(atomos, Constants.REQUIRE_CAPABILITY, Collections.emptyList());
setHeader(atomos, Constants.IMPORT_PACKAGE, manifest.pkgImports);
setHeader(atomos, Constants.EXPORT_PACKAGE, manifest.pkgExports);
setHeader(atomos, Constants.REQUIRE_BUNDLE, manifest.requiredBundles);
return atomos;
}

Expand Down
74 changes: 48 additions & 26 deletions solstice/src/main/java/dev/equo/solstice/BundleContextSolstice.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
Expand All @@ -37,12 +38,13 @@
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
import org.eclipse.core.internal.runtime.InternalPlatform;
import org.eclipse.osgi.internal.framework.FilterImpl;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.IdentityNamespace;
Expand All @@ -52,15 +54,18 @@
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.resource.Namespace;
import org.osgi.resource.Requirement;
import org.osgi.service.packageadmin.PackageAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A single-classloader implementation of OSGi which eagerly loads all the OSGi plugins it can find
* on the classpath.
*/
public class BundleContextSolstice extends ServiceRegistry {
private final Logger logger = LoggerFactory.getLogger(BundleContextSolstice.class);

private static final Set<String> DONT_ACTIVATE = Set.of("org.eclipse.osgi");

private static BundleContextSolstice instance;
Expand Down Expand Up @@ -134,6 +139,11 @@ public URL getResource(String name) {
return bundles.get(0).getResource(name);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return Class.forName(name);
}

@Override
public Enumeration<URL> getResources(String name) throws IOException {
return bundles.get(0).getResources(name);
Expand Down Expand Up @@ -226,19 +236,34 @@ public Bundle[] getFragments(Bundle bundle) {
}
};

private Capability.SupersetMap<ShimBundle> capabilities = new Capability.SupersetMap<>();

final FrameworkWiring frameworkWiring =
new Unimplemented.FrameworkWiring() {
@Override
public Collection<BundleCapability> findProviders(Requirement requirement) {
String filterSpec =
requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
try {
var requirementFilter = FilterImpl.newInstance(filterSpec).getStandardOSGiAttributes();
var requiredBundle = requirementFilter.get(IdentityNamespace.IDENTITY_NAMESPACE);
var bundle = bundleForSymbolicName(requiredBundle);
public Collection<BundleCapability> findProviders(Requirement req) {
if (!Set.of(Constants.FILTER_DIRECTIVE).equals(req.getDirectives().keySet())) {
throw Unimplemented.onPurpose(
"Solstice supports only filter, this was " + req.getDirectives());
}
String filterRaw = req.getDirectives().get(Constants.FILTER_DIRECTIVE);
var filter = SolsticeManifest.parseSingleFilter(filterRaw);

if (req.getNamespace().equals(IdentityNamespace.IDENTITY_NAMESPACE)) {
if (!filter.getKey().equals(IdentityNamespace.IDENTITY_NAMESPACE)) {
throw Unimplemented.onPurpose(
"Solstice expected " + IdentityNamespace.IDENTITY_NAMESPACE + ", was " + filter);
}
var bundle = bundleForSymbolicName(filter.getValue());
Objects.requireNonNull(bundle, filter.getValue());
return Collections.singleton(new ShimBundleCapability(bundle));
} catch (Exception e) {
throw Unimplemented.onPurpose();
} else {
var cap = new Capability(req.getNamespace(), filter.getKey(), filter.getValue());
var result = capabilities.getAnySupersetOf(cap);
if (result == null) {
throw new NoSuchElementException("No capability matching " + cap);
}
return Collections.singleton(new ShimBundleCapability(result));
}
}
};
Expand Down Expand Up @@ -313,7 +338,12 @@ public void removeFrameworkListener(FrameworkListener listener) {
private synchronized void notifyBundleListeners(int type, ShimBundle bundle) {
var event = new BundleEvent(type, bundle);
for (BundleListener listener : bundleListeners) {
listener.bundleChanged(event);
try {
listener.bundleChanged(event);
} catch (Exception e) {
getService(getServiceReference(FrameworkLog.class))
.log(new FrameworkEvent(FrameworkEvent.ERROR, bundle, e));
}
}
}

Expand Down Expand Up @@ -392,17 +422,18 @@ private void activate() {

state = STARTING;
notifyBundleListeners(BundleEvent.STARTING, this);

for (var cap : manifest.capProvides) {
capabilities.put(cap, this);
}

if (!DONT_ACTIVATE.contains(getSymbolicName()) && activator != null) {
try {
var c = (Constructor<BundleActivator>) Class.forName(activator).getConstructor();
var bundleActivator = c.newInstance();
bundleActivator.start(BundleContextSolstice.this);
} catch (Exception e) {
try {
throw new ActivatorException(this, e);
} catch (ActivatorException ae) {
ae.printStackTrace();
}
logger.warn("Error in activator of " + getSymbolicName(), e);
}
}
state = ACTIVE;
Expand Down Expand Up @@ -576,13 +607,4 @@ public boolean isActivationPolicyUsed() {
}
}
}

static class ActivatorException extends RuntimeException {
final ShimBundle bundle;

ActivatorException(ShimBundle bundle, Exception cause) {
super(cause);
this.bundle = bundle;
}
}
}
Loading

0 comments on commit 0803730

Please sign in to comment.