From abb29034dead1240ca72f0c522be2f583edef3b9 Mon Sep 17 00:00:00 2001 From: Peter Kirschner Date: Mon, 27 Jul 2026 15:17:10 +0200 Subject: [PATCH] Support Eclipse features on -buildpath and container paths (#7322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features are now first-class citizens on -buildpath, -testpath, -runpath and -runbundles. The canonical syntax is: -buildpath: org.eclipse.e4.rcp;version='4.40.0';type=org.eclipse.update.feature A feature expands to its member bundles: references and recursively, with platform filtering and exact→highest version fallback. Core changes (biz.aQute.bndlib): - Container.TYPE.FEATURE with member expansion via Project.getFeatureMembers() - Type-aware version lookup via Repository.findProviders (not RepositoryPlugin.versions) - EclipsePlatform matcher for os/ws/arch platform filters - Project.getBundles() flattens features for all consumers (classpath, launcher, IDE) Index changes (biz.aQute.repository): - Feature.toResource() enrichment with bnd.relation/id/version/type/os/ws/arch attributes - P2Indexer self-healing reindex for stale caches + .feature.jar cache-link suffix UI changes (bndtools.core): - RepositoryBundleSelectionPart: canonical clause on DND and wizard add - SelectionDragAdapter: text drag produces canonical clause - VersionedClauseLabelProvider: feature icon in buildpath list - RepoBundleSelectionWizardPage: type-aware selection keying SWTBot infrastructure (bndtools.core.test): - launch.rendered.bnd: RenderedLauncher for real Eclipse widgets - swtbot.tests.bnd: SWTBot test bundle with fixture workspace - FeatureBuildPathWizardSwtbotTest: Add Bundle dialog feature selection - FeatureBuildPathDndSwtbotTest: Repositories view drag→Build Path Tests: - FeatureBuildpathTest (6 cases: nested, platform filter, cycles, fallback, optional, error) - FeatureParserTest.testMemberRelationAttributes (enrichment contract) - SWTBot UI tests (green on win32) API changes: - aQute.bnd.build 4.7.1 → 4.8.0 (Container.TYPE.FEATURE) - aQute.bnd.osgi.resource 5.1.0 → 5.2.0 (TYPE_ECLIPSE_FEATURE, FEATURE_RELATION_*) - aQute.p2.provider 1.0.0 → 1.1.0 (relation attributes) All changes additive; full backward compatibility. Signed-off-by: Peter Kirschner --- .../README.md | 238 ++++++++++++ .../exporter/subsystem/SubsystemExporter.java | 1 + .../test/test/FeatureBuildpathTest.java | 347 ++++++++++++++++++ .../src/aQute/bnd/build/Container.java | 28 +- .../src/aQute/bnd/build/EclipsePlatform.java | 80 ++++ .../src/aQute/bnd/build/Project.java | 214 ++++++++++- .../src/aQute/bnd/build/RepoCollector.java | 2 +- .../src/aQute/bnd/build/package-info.java | 2 +- .../bnd/osgi/resource/ResourceUtils.java | 21 ++ .../aQute/bnd/osgi/resource/package-info.java | 2 +- .../bnd/repository/p2/provider/P2Indexer.java | 33 +- .../src/aQute/p2/provider/Feature.java | 54 +++ .../src/aQute/p2/provider/package-info.java | 2 +- .../aQute/p2/provider/FeatureParserTest.java | 98 +++++ bndtools.core.test/bnd.bnd | 3 + bndtools.core.test/launch.rendered.bnd | 4 + .../ui/swtbot/features/cnf/.gitignore | 3 + .../ui/swtbot/features/cnf/.project | 11 + .../ui/swtbot/features/cnf/build.bnd | 8 + .../features/cnf/fixture-p2/artifacts.xml | 28 ++ .../features/test.feature_1.0.0.jar | Bin 0 -> 597 bytes .../swtbot/features/cnf/fixture-p2/p2.index | 3 + .../plugins/feature.member.a_1.0.0.jar | Bin 0 -> 581 bytes .../plugins/feature.member.b_1.0.0.jar | Bin 0 -> 581 bytes .../swtbot/features/test.buildpath/.classpath | 7 + .../swtbot/features/test.buildpath/.project | 23 ++ .../ui/swtbot/features/test.buildpath/bnd.bnd | 0 .../features/test.buildpath/src/.gitkeep | 0 .../core/test/launch/rendered/Activator.java | 90 +++++ .../launch/rendered/RenderedLauncher.java | 56 +++ .../ui/swtbot/AbstractFeatureSwtbotTest.java | 173 +++++++++ .../swtbot/FeatureBuildPathDndSwtbotTest.java | 75 ++++ .../FeatureBuildPathWizardSwtbotTest.java | 52 +++ bndtools.core.test/swtbot.tests.bnd | 10 + .../test.swtbot.cocoa.macosx.aarch64.bndrun | 330 +++++++++++++++++ .../test.swtbot.gtk.linux.x86_64.bndrun | 329 +++++++++++++++++ bndtools.core.test/test.swtbot.shared.bndrun | 89 +++++ .../test.swtbot.win32.x86_64.bndrun | 330 +++++++++++++++++ bndtools.core.test/tests.bnd | 2 +- .../RepositoryBundleSelectionPart.java | 25 +- .../clauses/VersionedClauseLabelProvider.java | 10 +- .../model/repo/RepositoryBundleUtils.java | 24 ++ .../bndtools/utils/SelectionDragAdapter.java | 16 +- .../repo/RepoBundleSelectionWizardPage.java | 25 +- docs/_instructions/_ext/buildpath.md | 11 + 45 files changed, 2816 insertions(+), 43 deletions(-) create mode 100644 .github/specs/Support_Eclipse-Feature_buildpath/README.md create mode 100644 biz.aQute.bndlib.tests/test/test/FeatureBuildpathTest.java create mode 100644 biz.aQute.bndlib/src/aQute/bnd/build/EclipsePlatform.java create mode 100644 bndtools.core.test/launch.rendered.bnd create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.gitignore create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.project create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/build.bnd create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/artifacts.xml create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/features/test.feature_1.0.0.jar create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/p2.index create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/plugins/feature.member.a_1.0.0.jar create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/plugins/feature.member.b_1.0.0.jar create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/.classpath create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/.project create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/bnd.bnd create mode 100644 bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/src/.gitkeep create mode 100644 bndtools.core.test/src/bndtools/core/test/launch/rendered/Activator.java create mode 100644 bndtools.core.test/src/bndtools/core/test/launch/rendered/RenderedLauncher.java create mode 100644 bndtools.core.test/src/bndtools/core/test/ui/swtbot/AbstractFeatureSwtbotTest.java create mode 100644 bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathDndSwtbotTest.java create mode 100644 bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathWizardSwtbotTest.java create mode 100644 bndtools.core.test/swtbot.tests.bnd create mode 100644 bndtools.core.test/test.swtbot.cocoa.macosx.aarch64.bndrun create mode 100644 bndtools.core.test/test.swtbot.gtk.linux.x86_64.bndrun create mode 100644 bndtools.core.test/test.swtbot.shared.bndrun create mode 100644 bndtools.core.test/test.swtbot.win32.x86_64.bndrun diff --git a/.github/specs/Support_Eclipse-Feature_buildpath/README.md b/.github/specs/Support_Eclipse-Feature_buildpath/README.md new file mode 100644 index 0000000000..8515f955c4 --- /dev/null +++ b/.github/specs/Support_Eclipse-Feature_buildpath/README.md @@ -0,0 +1,238 @@ +# Support for Eclipse Features on -buildpath + +## Overview + +This spec describes the support for referencing Eclipse features in the bnd `-buildpath` instruction (and all other container paths), implemented for issue [#7322](https://github.com/bndtools/bnd/issues/7322). It builds on top of the p2 feature indexing introduced with [PR #7124](https://github.com/bndtools/bnd/pull/7124) (see `PR7124_Support-Eclipse-Features_p2-repos.md`) and the resolver support from [#7279](https://github.com/bndtools/bnd/issues/7279)/[#7296](https://github.com/bndtools/bnd/pull/7296). + +**Canonical clause syntax:** + +```properties +-buildpath: \ + org.eclipse.e4.rcp;version='4.40.0.v20260516-1214';type=org.eclipse.update.feature +``` + +A feature is a **container of bundles**: on a path it expands to its member bundles — the `` references of its `feature.xml` and, recursively, the members of its `` referenced features. `` imports are dependencies, not members, and are **not** expanded. + +## The Identity Problem + +OSGi uses bsn+version as the unique identifier of a bundle. This is **not sufficient** for Eclipse features: a feature and a bundle may share the same id and version in a p2 repository (e.g. `org.eclipse.emf.ecore` exists both as bundle and as feature). All lookups therefore additionally honor the identity type: + +```java +(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, "org.eclipse.update.feature") +``` + +| Aspect | Bundle entry | Feature entry | +|---|---|---| +| Clause | `bsn;version=V` | `id;version='V';type=org.eclipse.update.feature` | +| Version listing | `RepositoryPlugin.versions(bsn)` | `Repository.findProviders` with `(&(osgi.identity=id)(type=org.eclipse.update.feature))` | +| Fetch | `RepositoryPlugin.get(bsn, v, attrs)` | same — the `type` attribute is part of the `attrs` map and honored by `P2Indexer.get()` | +| Container | `Container.TYPE.REPO` | `Container.TYPE.FEATURE`, expands via `getMembers()` | +| Workspace projects | may shadow repo versions | never consulted (projects deliver bundles, not features) | + +## Design Decisions + +1. **Syntax**: the `type=org.eclipse.update.feature` attribute marks a clause as a feature reference. It matches the `-runrequires` alias vocabulary (`bnd.identity;id=…;type=…`, see `CapReqBuilder.unalias`) and flows through the existing `attrs` parameter of `RepositoryPlugin.get()` unchanged. The previously generated `feature:` bsn prefix and `feature=true` attribute are **not** supported and no longer emitted by the UI. +2. **Single parser**: `aQute.p2.provider.Feature` (from PR #7124) remains the only feature.xml parser. bndlib consumes the feature exclusively through the **repository index** using standard OSGi APIs (`Repository.findProviders`, `Resource.getRequirements`). No new bnd service interface is introduced (a `FeatureProvider` interface was already rejected in [#7133](https://github.com/bndtools/bnd/pull/7133)). +3. **Expansion scope**: `` and `` members only, `` ignored, include recursion with cycle guard. +4. **Platform filter**: plugin/include entries carrying `os`/`ws`/`arch` attributes are skipped when they do not match the running platform. +5. **Member versions**: the version pinned in feature.xml is resolved exactly first; when absent from the repositories, the highest available version is used and a warning is issued. +6. **All paths**: `-buildpath`, `-testpath`, `-runpath` and `-runbundles` share `Project.getBundles()`, so feature clauses work on all of them. + +## Index Enrichment (biz.aQute.repository) + +### Requirement provenance attributes + +`Feature.toResource()` stores every feature.xml reference as an `osgi.identity` requirement (PR #7124). Plugin references and `` imports were structurally indistinguishable, and the parsed `os/ws/arch` data was dropped. The requirements are now enriched with **additive attributes** — the filter and directives stay byte-identical, so resolver behavior and existing indexes remain valid: + +```java +// reference +req.addAttribute(RELATION_ATTRIBUTE, RELATION_PLUGIN); // bnd.relation=plugin +req.addAttribute("id", plugin.id); +addVersionAttribute(req, plugin.version); // exact Version, if valid +req.addAttribute("os", plugin.os); // only when present +req.addAttribute("ws", plugin.ws); +req.addAttribute("arch", plugin.arch); +req.addAttribute("fragment", "true"); // only when fragment + +// reference +req.addAttribute(RELATION_ATTRIBUTE, RELATION_INCLUDE); // bnd.relation=include +req.addAttribute("id", include.id); +req.addAttribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, "org.eclipse.update.feature"); +addVersionAttribute(req, include.version); + +// reference +req.addAttribute(RELATION_ATTRIBUTE, RELATION_REQUIRE); // bnd.relation=require +``` + +The constants are owned by bndlib (`aQute.bnd.osgi.resource.ResourceUtils`) and re-exported by `Feature` for repository-module consumers: + +| Constant | Value | +|---|---| +| `ResourceUtils.TYPE_ECLIPSE_FEATURE` | `org.eclipse.update.feature` | +| `ResourceUtils.FEATURE_RELATION_ATTRIBUTE` | `bnd.relation` | +| `ResourceUtils.FEATURE_RELATION_PLUGIN` | `plugin` | +| `ResourceUtils.FEATURE_RELATION_INCLUDE` | `include` | +| `ResourceUtils.FEATURE_RELATION_REQUIRE` | `require` | + +The attributes use the same `id`/`version`/`type` vocabulary as the `bnd.identity` requirement alias, so consumers need **no LDAP filter parsing**. + +### Index migration (self-healing) + +Cached `index.xml.gz` files written before this change lack the provenance markers. `P2Indexer.readRepository()` detects such stale indexes and re-indexes once: + +```java +private boolean requiresReindex(List resources) { + for (Resource resource : resources) { + if (!ResourceUtils.hasType(resource, ECLIPSE_FEATURE_CAPABILITY)) + continue; + List requirements = resource.getRequirements(IdentityNamespace.IDENTITY_NAMESPACE); + if (requirements.isEmpty()) + continue; + boolean marked = requirements.stream() + .anyMatch(req -> req.getAttributes().containsKey(Feature.RELATION_ATTRIBUTE)); + if (!marked) + return true; // stale cache, re-index + } + return false; +} +``` + +### Cache link collision + +`P2Indexer.get()` linked every artifact as `-.jar`. A feature sharing bsn+version with a bundle would overwrite the bundle in the cache (and vice versa). Feature links now use a distinct suffix: `-.feature.jar`. + +## Core Implementation (biz.aQute.bndlib) + +### Container.TYPE.FEATURE + +`Container.TYPE` gained the `FEATURE` constant. Feature containers behave like `LIBRARY` containers everywhere a container can expand: + +- `Container.getMembers()` — expansion entry point, delegates to `Project.getFeatureMembers(this, visited)` with a cycle guard set +- `Container.contributeFiles()` / `Container.flatten()` — treat `FEATURE` like `LIBRARY` +- `RepoCollector` (`${repo}` macro) and `SubsystemExporter` — expand members + +`Project.toContainer()` creates a `FEATURE` container when the clause attrs carry `type=org.eclipse.update.feature`. Because `Project.getBundles()` flattens `found.getMembers()` into the result, downstream consumers (classpath, launcher, Eclipse classpath container) only ever see the expanded member containers — the feature container itself never surfaces on a path. + +### Type-aware version lookup + +`Project.getBundle()`'s range-search path used `RepositoryPlugin.versions(bsn)`, which only lists bundles (features are intentionally filtered out by `P2Indexer.versions()` since PR #7124 bugfixes). For typed lookups the versions are collected via the OSGi Repository API instead: + +```java +private SortedSet versionsOf(RepositoryPlugin plugin, String bsn, String requestedType) throws Exception { + if (requestedType == null) + return plugin.versions(bsn); // unchanged bundle path + SortedSet versions = new TreeSet<>(); + if (!(plugin instanceof Repository repo)) + return versions; // repo cannot answer typed queries + Requirement identity = identityRequirement(bsn, requestedType, null); + // (&(osgi.identity=)(type=)) + ...collect CAPABILITY_VERSION_ATTRIBUTE of matching identity capabilities... +} +``` + +The workspace-project version merge is skipped for typed lookups — a workspace project with the same name must not shadow a feature. The `EXACT` strategy path needs no change: `plugin.get(bsn, version, attrs)` already receives the `type` attribute and `P2Indexer.findResource()` filters by it. + +### Member expansion + +`Project.getFeatureMembers(Container feature, Set visitedFeatures)`: + +1. Cycle guard on `id:version` (warning on repeated occurrence, empty result) +2. Locate the feature `Resource` via `findFeatureResource(bsn, version)` — first repository implementing `org.osgi.service.repository.Repository` that answers `(&(osgi.identity=…)(type=org.eclipse.update.feature)(version=…))` +3. Iterate `resource.getRequirements("osgi.identity")`: + - no `bnd.relation` attribute → skipped (counted; a fully unmarked requirement set produces a "refresh the repository" warning for pre-enrichment indexes of non-p2 origin) + - `bnd.relation=require` → ignored (dependency, not member) + - platform mismatch (`os`/`ws`/`arch` vs `EclipsePlatform.CURRENT`) → skipped + - `bnd.relation=plugin` → resolve bundle: exact version first, fallback `Strategy.HIGHEST` with warning + - `bnd.relation=include` → resolve nested feature (attrs carry `type=`), recurse via `member.getMembers(visitedFeatures)` + - `resolution:=optional` members that cannot be resolved are skipped silently +4. Members are de-duplicated; unresolvable mandatory members surface as `ERROR` containers which `doPath()` reports with header/context, exactly like ordinary path entries + +### Platform matching + +`EclipsePlatform` (package-private) maps `os.name`/`os.arch` to the Eclipse coordinates (`win32|linux|macosx…` / `win32|gtk|cocoa` / `x86_64|aarch64|x86|…`) and matches the comma-separated filter lists of feature.xml; absent filters match any platform. + +## Bndtools UI (bndtools.core) + +| Component | Change | +|---|---| +| `RepositoryBundleSelectionPart` (shared by **Build Path** and **Run Bundles** panes) | Drag & drop of a `RepositoryFeature` emits the canonical clause via `RepositoryBundleUtils.convertRepoFeature()`; the old `feature:`+`feature=true` generation is gone. `handleAdd()` matches existing entries by name **and** type, so a feature no longer replaces a bundle of the same name | +| `RepositoryBundleUtils.convertRepoFeature()` | Single conversion helper: `id;version='V';type=org.eclipse.update.feature` | +| `SelectionDragAdapter` | Text drag (e.g. into the Source tab) produces the canonical clause instead of `feature:id:version` | +| `VersionedClauseLabelProvider` | Rows with `type=org.eclipse.update.feature` show the feature icon | +| `RepoBundleSelectionWizardPage` ("Add Bundle" dialog) | `RepositoryFeature`/`FeatureVersionNode` selections are convertible; the selected-set is keyed by name+type so features and bundles with equal names coexist | + +Example result of both the wizard and DND flows (note that bnd quotes the attribute value when serializing): + +```properties +-buildpath: test.feature;version='1.0.0';type='org.eclipse.update.feature' +``` + +## SWTBot UI Test Infrastructure (bndtools.core.test) + +The repository previously had no SWTBot support; the existing harness runs a deliberately **non-rendered** workbench (`NullContextPresentationEngine`). The following infrastructure was added: + +| Piece | Purpose | +|---|---| +| `launch.rendered.bnd` → `bndtools.core.test.launch.rendered` | `RenderedLauncher` bridges the bnd launcher main thread to the Eclipse application **without** installing the null presentation engine → real SWT widgets | +| `swtbot.tests.bnd` → `bndtools.core.test.swtbot.tests` | SWTBot test bundle (`bndtools.core.test.ui.swtbot` + a copy of the harness utils); excluded from the headless test bundle | +| `test.swtbot.shared.bndrun` | Overrides `-runrequires` (note the required `~` include: `-include: ~test.shared.bndrun`), swapping headless launch/tests for rendered launch/SWTBot tests | +| `test.swtbot.{win32.x86_64,gtk.linux.x86_64,cocoa.macosx.aarch64}.bndrun` | Platform launches with frozen `-runbundles` | +| `resources/workspaces/ui/swtbot/features/` | Template workspace: cnf configures a **local file: p2 repository** (`fixture-p2`: `p2.index`, `artifacts.xml`, two member bundles, `test.feature_1.0.0.jar`) — offline end-to-end through the real P2 indexing and enrichment | + +SWTBot 4.1.0 resolves from the already-pinned `Eclipse-4_30-2023-12` pobr index — **no additional repository** in cnf. + +Test classes: + +- `FeatureBuildPathWizardSwtbotTest` — Build page → "Add Bundle" → select feature below "Feature Fixture" → Add/Finish → asserts the canonical clause in bnd.bnd +- `FeatureBuildPathDndSwtbotTest` — drags the feature from the Repositories view onto the Build Path table → asserts the canonical clause and the absence of `feature:`/`feature=true` + +Run: + +```bash +./gradlew :bndtools.core.test:testrun.test.swtbot.win32.x86_64 # Windows +./gradlew :bndtools.core.test:testrun.test.swtbot.gtk.linux.x86_64 # Linux (needs a display / xvfb-run) +``` + +Note: the runs are opt-in Gradle tasks (auto-registered per bndrun) and not part of `check`; enabling them on Linux CI requires re-activating `xvfb-run` in the cibuild workflow matrix. + +## Tests + +| Test | Module | Coverage | +|---|---|---| +| `test.FeatureBuildpathTest` | biz.aQute.bndlib.tests | Expansion incl. nested include, platform filter (`os=qnx` skipped), cycle termination, exact→highest fallback with warning, optional include skip, missing feature ERROR container. Uses a P2-mimicking fake repo (type-dispatching `get()`, bundles-only `versions()`, `findProviders`) | +| `FeatureParserTest.testMemberRelationAttributes` | biz.aQute.repository | Enrichment contract: `bnd.relation`, `id`/`version`/`type`, `os/ws/arch/fragment`, unchanged filters/directives, `resolution:=optional` | +| `FeatureBuildPathWizardSwtbotTest`, `FeatureBuildPathDndSwtbotTest` | bndtools.core.test | Rendered-workbench UI flows (see above) | +| Existing PR #7124 suites (`FeatureRequirementsInIndexTest`, `FeatureVersionFilterTest`, `FeaturePropertiesTest`, `Eclipse431FeatureParsingTest`, …) | biz.aQute.repository | Unchanged and green — enrichment is strictly additive | + +```bash +./gradlew :biz.aQute.bndlib.tests:test --tests "test.FeatureBuildpathTest" +./gradlew :biz.aQute.repository:test --tests "aQute.p2.provider.FeatureParserTest" +``` + +## API Compatibility + +All changes are additive; no existing signature changed. + +| Package | Version | Additions | +|---|---|---| +| `aQute.bnd.build` | 4.7.1 → 4.8.0 | `Container.TYPE.FEATURE` | +| `aQute.bnd.osgi.resource` | 5.1.0 → 5.2.0 | `TYPE_ECLIPSE_FEATURE`, `FEATURE_RELATION_*` constants | +| `aQute.p2.provider` | 1.0.0 → 1.1.0 | `Feature.RELATION_*` constants | + +`RepositoryPlugin` is untouched: the `type` attribute travels through the pre-existing `attrs`/`properties` maps, and typed version listing uses the optional `org.osgi.service.repository.Repository` capability of a repository plugin. + +## Known Limitations + +- Wildcard clauses (`bsn*`) do not support the `type` attribute — features must be referenced by exact id. +- `version=hash` lookups are not type-aware. +- Repositories that do not implement the OSGi `Repository` interface cannot answer typed lookups; feature clauses resolve only against repositories that index features (currently `P2Repository` and any OSGi-XML-indexed repository containing enriched feature resources). +- Feature expansion contributes member bundles only; the feature JAR itself never appears on a path. + +## Summary + +Eclipse features are now first-class citizens on all bnd container paths. The critical invariant, carried through every layer from clause parsing over repository lookup to the Bndtools UI: + +- **Features** are identified by id+version **plus** `type=org.eclipse.update.feature` +- **Bundles** remain identified by bsn+version alone + +The feature's member information is read from the (enriched) repository index that PR #7124 already builds — one parser, one source of truth, no new service interfaces, full backward compatibility. diff --git a/biz.aQute.bnd.exporters/src/aQute/bnd/exporter/subsystem/SubsystemExporter.java b/biz.aQute.bnd.exporters/src/aQute/bnd/exporter/subsystem/SubsystemExporter.java index c4dd59a35d..eb59f9f9cf 100644 --- a/biz.aQute.bnd.exporters/src/aQute/bnd/exporter/subsystem/SubsystemExporter.java +++ b/biz.aQute.bnd.exporters/src/aQute/bnd/exporter/subsystem/SubsystemExporter.java @@ -260,6 +260,7 @@ private List getBundles(Collection containers, Processor report files.add(container.getFile()); break; case LIBRARY : + case FEATURE : container.contributeFiles(files, reporter); break; } diff --git a/biz.aQute.bndlib.tests/test/test/FeatureBuildpathTest.java b/biz.aQute.bndlib.tests/test/test/FeatureBuildpathTest.java new file mode 100644 index 0000000000..10bd72c06a --- /dev/null +++ b/biz.aQute.bndlib.tests/test/test/FeatureBuildpathTest.java @@ -0,0 +1,347 @@ +package test; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.assertj.core.api.SoftAssertions; +import org.assertj.core.api.junit.jupiter.InjectSoftAssertions; +import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; + +import aQute.bnd.build.Container; +import aQute.bnd.build.Project; +import aQute.bnd.build.Workspace; +import aQute.bnd.osgi.Jar; +import aQute.bnd.osgi.repository.BaseRepository; +import aQute.bnd.osgi.repository.ResourcesRepository; +import aQute.bnd.osgi.resource.CapReqBuilder; +import aQute.bnd.osgi.resource.ResourceBuilder; +import aQute.bnd.osgi.resource.ResourceUtils; +import aQute.bnd.service.RepositoryPlugin; +import aQute.bnd.service.Strategy; +import aQute.bnd.test.jupiter.InjectTemporaryDirectory; +import aQute.bnd.version.Version; +import aQute.lib.io.IO; + +/** + * Test the expansion of Eclipse features on -buildpath like paths. A feature + * is a container of included bundles ({@code }) and included features + * ({@code }); on a path it expands to its member bundles. Required + * features/plugins ({@code }) are dependencies and are not + * expanded. + */ +@ExtendWith(SoftAssertionsExtension.class) +public class FeatureBuildpathTest { + private static final String FEATURE_TYPE = "org.eclipse.update.feature"; + + @InjectSoftAssertions + SoftAssertions softly; + + @InjectTemporaryDirectory + File tmp; + + /** + * A repository mimicking the P2 repository semantics: bundles and + * features can share bsn+version, get() dispatches on the requested + * identity type, versions() only lists bundles, and the index is + * queryable via the OSGi Repository API. + */ + static class FeatureRepo extends BaseRepository implements RepositoryPlugin { + final ResourcesRepository index = new ResourcesRepository(); + final Map files = new HashMap<>(); + final Map> bundleVersions = new HashMap<>(); + + void addBundle(String bsn, String version, File file) throws Exception { + ResourceBuilder rb = new ResourceBuilder(); + rb.addCapability(new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE) + .addAttribute(IdentityNamespace.IDENTITY_NAMESPACE, bsn) + .addAttribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_BUNDLE) + .addAttribute(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new Version(version))); + index.add(rb.build()); + files.put(bsn + ":" + version + ":" + IdentityNamespace.TYPE_BUNDLE, file); + bundleVersions.computeIfAbsent(bsn, k -> new TreeSet<>()) + .add(new Version(version)); + } + + void addFeature(String id, String version, File file, Collection members) throws Exception { + ResourceBuilder rb = new ResourceBuilder(); + rb.addCapability(new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE) + .addAttribute(IdentityNamespace.IDENTITY_NAMESPACE, id) + .addAttribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, FEATURE_TYPE) + .addAttribute(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, new Version(version))); + for (Requirement member : members) { + rb.addRequirement(CapReqBuilder.clone(member)); + } + index.add(rb.build()); + files.put(id + ":" + version + ":" + FEATURE_TYPE, file); + } + + @Override + public File get(String bsn, Version version, Map properties, DownloadListener... listeners) + throws Exception { + String requestedType = properties != null ? properties.get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE) + : null; + String type = requestedType != null ? requestedType : IdentityNamespace.TYPE_BUNDLE; + File file = files.get(bsn + ":" + version + ":" + type); + if (file == null) + return null; + for (DownloadListener listener : listeners) { + listener.success(file); + } + return file; + } + + @Override + public SortedSet versions(String bsn) throws Exception { + TreeSet versions = bundleVersions.get(bsn); + return versions != null ? versions : new TreeSet<>(); + } + + @Override + public List list(String pattern) throws Exception { + return List.copyOf(bundleVersions.keySet()); + } + + @Override + public Map> findProviders( + Collection requirements) { + return index.findProviders(requirements); + } + + @Override + public PutResult put(java.io.InputStream stream, PutOptions options) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public String getName() { + return "FeatureRepo"; + } + + @Override + public String getLocation() { + return "test"; + } + } + + static Requirement pluginMember(String id, String version, Map extra) throws Exception { + CapReqBuilder builder = new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE) + .addDirective("filter", "(&(osgi.identity=" + id + ")(version=" + version + "))") + .addAttribute(ResourceUtils.FEATURE_RELATION_ATTRIBUTE, ResourceUtils.FEATURE_RELATION_PLUGIN) + .addAttribute("id", id) + .addAttribute("version", new Version(version)); + if (extra != null) { + extra.forEach(builder::addAttribute); + } + return builder.buildSyntheticRequirement(); + } + + static Requirement includeMember(String id, String version, boolean optional) throws Exception { + CapReqBuilder builder = new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE) + .addDirective("filter", + "(&(osgi.identity=" + id + ")(type=" + FEATURE_TYPE + ")(version=" + version + "))") + .addAttribute(ResourceUtils.FEATURE_RELATION_ATTRIBUTE, ResourceUtils.FEATURE_RELATION_INCLUDE) + .addAttribute("id", id) + .addAttribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, FEATURE_TYPE) + .addAttribute("version", new Version(version)); + if (optional) { + builder.addDirective("resolution", "optional"); + } + return builder.buildSyntheticRequirement(); + } + + static Requirement requireMember(String id) throws Exception { + return new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE) + .addDirective("filter", "(osgi.identity=" + id + ")") + .addAttribute(ResourceUtils.FEATURE_RELATION_ATTRIBUTE, ResourceUtils.FEATURE_RELATION_REQUIRE) + .addAttribute("id", id) + .buildSyntheticRequirement(); + } + + File dummyJar(String name) throws Exception { + File file = new File(tmp, name + ".jar"); + try (Jar jar = new Jar(name)) { + jar.putResource("about.txt", new aQute.bnd.osgi.EmbeddedResource(name.getBytes(), 0L)); + jar.write(file); + } + return file; + } + + private Workspace getWorkspace() throws Exception { + File wsDir = new File(tmp, "ws"); + IO.copy(IO.getFile("testresources/ws"), wsDir); + return new Workspace(wsDir); + } + + @Test + public void expandFeatureWithNestedIncludeOnBuildpath() throws Exception { + try (Workspace ws = getWorkspace()) { + FeatureRepo repo = new FeatureRepo(); + repo.addBundle("bundle.a", "1.0.0", dummyJar("bundle.a-1.0.0")); + repo.addBundle("bundle.b", "2.0.0", dummyJar("bundle.b-2.0.0")); + repo.addBundle("bundle.c", "3.0.0", dummyJar("bundle.c-3.0.0")); + repo.addFeature("sub.feature", "2.0.0", dummyJar("sub.feature-2.0.0"), + List.of(pluginMember("bundle.c", "3.0.0", null))); + repo.addFeature("test.feature", "1.0.0", dummyJar("test.feature-1.0.0"), List.of( + pluginMember("bundle.a", "1.0.0", null), // + pluginMember("bundle.b", "2.0.0", null), // + includeMember("sub.feature", "2.0.0", false), // + requireMember("required.bundle.not.in.repo"))); + ws.addBasicPlugin(repo); + + try (Project project = ws.getProject("p1")) { + List containers = project.getBundles(Strategy.LOWEST, + "test.feature;version='1.0.0';type=org.eclipse.update.feature", "-buildpath"); + + softly.assertThat(project.check()) + .as("no errors or warnings expected") + .isTrue(); + softly.assertThat(containers) + .extracting(Container::getBundleSymbolicName) + .containsExactly("bundle.a", "bundle.b", "bundle.c"); + softly.assertThat(containers) + .allMatch(c -> c.getType() == Container.TYPE.REPO) + .allMatch(c -> c.getError() == null); + } + } + } + + @Test + public void expandFeatureSkipsForeignPlatformMembers() throws Exception { + try (Workspace ws = getWorkspace()) { + FeatureRepo repo = new FeatureRepo(); + repo.addBundle("bundle.a", "1.0.0", dummyJar("bundle.a-1.0.0")); + repo.addBundle("bundle.foreign", "1.0.0", dummyJar("bundle.foreign-1.0.0")); + repo.addFeature("test.feature", "1.0.0", dummyJar("test.feature-1.0.0"), List.of( + pluginMember("bundle.a", "1.0.0", null), // + pluginMember("bundle.foreign", "1.0.0", Map.of("os", "qnx")))); + ws.addBasicPlugin(repo); + + try (Project project = ws.getProject("p1")) { + List containers = project.getBundles(Strategy.LOWEST, + "test.feature;version='1.0.0';type=org.eclipse.update.feature", "-buildpath"); + + softly.assertThat(project.check()) + .isTrue(); + softly.assertThat(containers) + .extracting(Container::getBundleSymbolicName) + .containsExactly("bundle.a"); + } + } + } + + @Test + public void expandFeatureCycleTerminates() throws Exception { + try (Workspace ws = getWorkspace()) { + FeatureRepo repo = new FeatureRepo(); + repo.addBundle("bundle.a", "1.0.0", dummyJar("bundle.a-1.0.0")); + repo.addBundle("bundle.b", "1.0.0", dummyJar("bundle.b-1.0.0")); + repo.addFeature("feature.a", "1.0.0", dummyJar("feature.a-1.0.0"), List.of( + pluginMember("bundle.a", "1.0.0", null), // + includeMember("feature.b", "1.0.0", false))); + repo.addFeature("feature.b", "1.0.0", dummyJar("feature.b-1.0.0"), List.of( + pluginMember("bundle.b", "1.0.0", null), // + includeMember("feature.a", "1.0.0", false))); + ws.addBasicPlugin(repo); + + try (Project project = ws.getProject("p1")) { + List containers = project.getBundles(Strategy.LOWEST, + "feature.a;version='1.0.0';type=org.eclipse.update.feature", "-buildpath"); + + softly.assertThat(project.check("Detected a cycle in the includes of feature")) + .isTrue(); + softly.assertThat(containers) + .extracting(Container::getBundleSymbolicName) + .containsExactly("bundle.a", "bundle.b"); + } + } + } + + @Test + public void expandFeatureFallsBackToHighestWhenExactVersionMissing() throws Exception { + try (Workspace ws = getWorkspace()) { + FeatureRepo repo = new FeatureRepo(); + repo.addBundle("bundle.a", "1.0.0", dummyJar("bundle.a-1.0.0")); + repo.addFeature("test.feature", "1.0.0", dummyJar("test.feature-1.0.0"), + List.of(pluginMember("bundle.a", "9.9.9", null))); + ws.addBasicPlugin(repo); + + try (Project project = ws.getProject("p1")) { + List containers = project.getBundles(Strategy.LOWEST, + "test.feature;version='1.0.0';type=org.eclipse.update.feature", "-buildpath"); + + softly.assertThat(project + .check("Member bundle.a;version=9.9.9 of feature test.feature:1.0.0 not found with the exact")) + .isTrue(); + softly.assertThat(containers) + .extracting(Container::getBundleSymbolicName) + .containsExactly("bundle.a"); + softly.assertThat(containers.get(0) + .getVersion()) + .isEqualTo("1.0.0"); + } + } + } + + @Test + public void expandFeatureSkipsMissingOptionalInclude() throws Exception { + try (Workspace ws = getWorkspace()) { + FeatureRepo repo = new FeatureRepo(); + repo.addBundle("bundle.a", "1.0.0", dummyJar("bundle.a-1.0.0")); + repo.addFeature("test.feature", "1.0.0", dummyJar("test.feature-1.0.0"), List.of( + pluginMember("bundle.a", "1.0.0", null), // + includeMember("no.such.feature", "1.0.0", true))); + ws.addBasicPlugin(repo); + + try (Project project = ws.getProject("p1")) { + List containers = project.getBundles(Strategy.LOWEST, + "test.feature;version='1.0.0';type=org.eclipse.update.feature", "-buildpath"); + + softly.assertThat(project.check()) + .as("missing optional include must not cause errors") + .isTrue(); + softly.assertThat(containers) + .extracting(Container::getBundleSymbolicName) + .containsExactly("bundle.a"); + } + } + } + + @Test + public void missingFeatureReportsError() throws Exception { + try (Workspace ws = getWorkspace()) { + ws.addBasicPlugin(new FeatureRepo()); + + try (Project project = ws.getProject("p1")) { + List containers = project.getBundles(Strategy.LOWEST, + "no.such.feature;version='1.0.0';type=org.eclipse.update.feature", "-buildpath"); + + softly.assertThat(project.check()) + .as("the error is carried by the container and reported by doPath later") + .isTrue(); + softly.assertThat(containers) + .hasSize(1); + softly.assertThat(containers.get(0) + .getType()) + .isEqualTo(Container.TYPE.ERROR); + softly.assertThat(containers.get(0) + .getError()) + .contains("Not found in"); + } + } + } +} diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/Container.java b/biz.aQute.bndlib/src/aQute/bnd/build/Container.java index 75a624820c..f2a05c325c 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/Container.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/Container.java @@ -7,8 +7,10 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.Manifest; @@ -31,7 +33,14 @@ public enum TYPE { PROJECT, EXTERNAL, LIBRARY, - ERROR + ERROR, + /** + * An Eclipse feature (identity type + * {@code org.eclipse.update.feature}). A feature is a container of + * included bundles and included features; on a path it expands to its + * members, see {@link Container#getMembers()}. + */ + FEATURE } private volatile File file; @@ -160,6 +169,7 @@ public boolean contributeFiles(List files, Processor reporter) throws Exce return true; case LIBRARY : + case FEATURE : List containers = getMembers(); for (Container container : containers) { if (!container.contributeFiles(files, reporter)) @@ -232,13 +242,19 @@ private synchronized Map getWritableAttributes() { } /** - * Return the this if this is anything else but a library. If it is a - * library, return the members. This could work recursively, e.g., libraries - * can point to libraries. + * Return the this if this is anything else but a library or a feature. If + * it is a library, return the members. This could work recursively, e.g., + * libraries can point to libraries. If it is an Eclipse feature, return + * the contained bundles and the members of included features, + * recursively. * * @throws Exception */ public List getMembers() throws Exception { + return getMembers(new HashSet<>()); + } + + List getMembers(Set visitedFeatures) throws Exception { List result = project.newList(); // Are ww a library? If no, we are the result @@ -257,6 +273,8 @@ public List getMembers() throws Exception { } } } + } else if (getType() == TYPE.FEATURE) { + result.addAll(project.getFeatureMembers(this, visitedFeatures)); } else result.add(this); @@ -270,7 +288,7 @@ public List getMembers() throws Exception { * @param list the result list */ public static void flatten(Container container, List list) throws Exception { - if (container.getType() == TYPE.LIBRARY) { + if (container.getType() == TYPE.LIBRARY || container.getType() == TYPE.FEATURE) { flatten(container.getMembers(), list); } else list.add(container); diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/EclipsePlatform.java b/biz.aQute.bndlib/src/aQute/bnd/build/EclipsePlatform.java new file mode 100644 index 0000000000..348c59c1ca --- /dev/null +++ b/biz.aQute.bndlib/src/aQute/bnd/build/EclipsePlatform.java @@ -0,0 +1,80 @@ +package aQute.bnd.build; + +import java.util.Locale; + +import aQute.lib.strings.Strings; + +/** + * Matches the Eclipse platform coordinates (os/ws/arch as used in Eclipse + * feature.xml plugin and includes elements) against the running platform. + * Values follow the Eclipse conventions, e.g. os=win32|linux|macosx, + * ws=win32|gtk|cocoa, arch=x86_64|aarch64. + */ +class EclipsePlatform { + static final EclipsePlatform CURRENT = new EclipsePlatform( + toOs(System.getProperty("os.name", "")), toArch(System.getProperty("os.arch", ""))); + + private final String os; + private final String ws; + private final String arch; + + EclipsePlatform(String os, String arch) { + this.os = os; + this.ws = toWs(os); + this.arch = arch; + } + + /** + * Match this platform against the given filter values. Each filter value + * is a comma separated list of accepted values as used in feature.xml; a + * null or empty filter matches any platform. + */ + boolean matches(String osFilter, String wsFilter, String archFilter) { + return matches(osFilter, os) && matches(wsFilter, ws) && matches(archFilter, arch); + } + + private static boolean matches(String filter, String value) { + if (filter == null || filter.isBlank()) + return true; + return Strings.splitAsStream(filter) + .anyMatch(value::equals); + } + + static String toOs(String osName) { + String name = osName.toLowerCase(Locale.ROOT); + if (name.contains("win")) + return "win32"; + if (name.contains("mac") || name.contains("darwin")) + return "macosx"; + if (name.contains("linux")) + return "linux"; + if (name.contains("sunos") || name.contains("solaris")) + return "solaris"; + if (name.contains("aix")) + return "aix"; + if (name.contains("hp-ux")) + return "hpux"; + if (name.contains("qnx")) + return "qnx"; + return name; + } + + static String toWs(String os) { + return switch (os) { + case "win32" -> "win32"; + case "macosx" -> "cocoa"; + default -> "gtk"; + }; + } + + static String toArch(String osArch) { + String arch = osArch.toLowerCase(Locale.ROOT); + return switch (arch) { + case "amd64", "x86_64", "x86-64", "em64t" -> "x86_64"; + case "aarch64", "arm64" -> "aarch64"; + case "x86", "i386", "i486", "i586", "i686", "pentium" -> "x86"; + case "ppc64le" -> "ppc64le"; + default -> arch; + }; + } +} diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/Project.java b/biz.aQute.bndlib/src/aQute/bnd/build/Project.java index 8434f4f2b3..1798cea468 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/Project.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/Project.java @@ -31,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Optional; import java.util.OptionalLong; import java.util.Properties; @@ -39,6 +40,7 @@ import java.util.SortedSet; import java.util.StringTokenizer; import java.util.TreeMap; +import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -49,6 +51,7 @@ import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; import org.osgi.resource.Requirement; import org.osgi.service.repository.ContentNamespace; import org.osgi.service.repository.Repository; @@ -1327,6 +1330,7 @@ public Container getBundle(String bsn, String _range, Strategy strategy, Map vs = plugin.versions(bsn); + SortedSet vs = versionsOf(plugin, bsn, requestedType); if (vs != null) { for (Version v : vs) { if (!versions.containsKey(v) && versionRange.includes(v)) @@ -1393,14 +1397,18 @@ public Container getBundle(String bsn, String _range, Strategy strategy, Map localVersions = getWorkspace().getWorkspaceRepository() - .versions(bsn); - for (Version v : localVersions) { - if (!versions.containsKey(v) && versionRange.includes(v)) - versions.put(v, null); + if (requestedType == null) { + SortedSet localVersions = getWorkspace().getWorkspaceRepository() + .versions(bsn); + for (Version v : localVersions) { + if (!versions.containsKey(v) && versionRange.includes(v)) + versions.put(v, null); + } } // Verify if we found any, if so, we use the strategy to pick @@ -1528,12 +1536,204 @@ protected Container toContainer(String bsn, String range, Map at if (f.getName() .endsWith(".lib")) container = new Container(this, bsn, range, Container.TYPE.LIBRARY, f, null, attrs, db); + else if (attrs != null && ResourceUtils.TYPE_ECLIPSE_FEATURE.equals(attrs.get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE))) + container = new Container(this, bsn, range, Container.TYPE.FEATURE, f, null, attrs, db); else container = new Container(this, bsn, range, Container.TYPE.REPO, f, null, attrs, db); return container; } + /** + * List the available versions of a bsn in a repository. When a type is + * requested (e.g. {@code org.eclipse.update.feature}), the normal + * {@link RepositoryPlugin#versions(String)} cannot be used since it only + * lists bundles. In that case we query the repository via the OSGi + * Repository API for identity capabilities carrying the requested type. + * + * @param plugin the repository + * @param bsn the identity to look up + * @param requestedType the requested identity type or null for the + * default bundle lookup + */ + private SortedSet versionsOf(RepositoryPlugin plugin, String bsn, String requestedType) throws Exception { + if (requestedType == null) + return plugin.versions(bsn); + + SortedSet versions = new TreeSet<>(); + if (!(plugin instanceof Repository repo)) + return versions; + + Requirement identity = identityRequirement(bsn, requestedType, null); + Map> providers = repo.findProviders(Collections.singleton(identity)); + Collection capabilities = providers != null ? providers.get(identity) : null; + if (capabilities == null) + return versions; + + for (Capability capability : capabilities) { + Object version = capability.getAttributes() + .get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE); + if (version != null) { + versions.add(Version.parseVersion(version.toString())); + } + } + return versions; + } + + private static Requirement identityRequirement(String bsn, String type, String version) { + StringBuilder filter = new StringBuilder(); + filter.append("(&(") + .append(IdentityNamespace.IDENTITY_NAMESPACE) + .append('=') + .append(bsn) + .append(')'); + if (type != null) { + filter.append('(') + .append(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE) + .append('=') + .append(type) + .append(')'); + } + if (version != null) { + filter.append('(') + .append(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE) + .append('=') + .append(version) + .append(')'); + } + filter.append(')'); + return new CapReqBuilder(IdentityNamespace.IDENTITY_NAMESPACE) + .addDirective(Namespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()) + .buildSyntheticRequirement(); + } + + /** + * Find the resource of an Eclipse feature in the available repositories + * via the OSGi Repository API. + */ + private org.osgi.resource.Resource findFeatureResource(String bsn, String version) { + Requirement requirement = identityRequirement(bsn, ResourceUtils.TYPE_ECLIPSE_FEATURE, version); + for (RepositoryPlugin plugin : getRepositories()) { + if (!(plugin instanceof Repository repo)) + continue; + Map> providers = repo.findProviders(Collections.singleton(requirement)); + Collection capabilities = providers != null ? providers.get(requirement) : null; + if (capabilities != null && !capabilities.isEmpty()) { + return capabilities.iterator() + .next() + .getResource(); + } + } + return null; + } + + /** + * Expand an Eclipse feature container into its member bundles. Members + * are the {@code } references and, recursively, the members of + * {@code } referenced features. {@code } imports are + * dependencies, not members, and are ignored. Members whose os/ws/arch + * does not match the running platform are skipped. + * + * @param feature the feature container to expand + * @param visitedFeatures guard against include cycles + */ + List getFeatureMembers(Container feature, Set visitedFeatures) throws Exception { + List result = newList(); + String featureId = feature.getBundleSymbolicName(); + String key = featureId + ":" + feature.getVersion(); + if (!visitedFeatures.add(key)) { + warning("Detected a cycle in the includes of feature %s, ignoring repeated occurrence", key); + return result; + } + + org.osgi.resource.Resource resource = findFeatureResource(featureId, feature.getVersion()); + if (resource == null) { + result.add(new Container(this, featureId, feature.getVersion(), Container.TYPE.ERROR, null, + "Cannot find the resource of feature " + key + " in the repositories to expand its members", + feature.getAttributes(), null)); + return result; + } + + List requirements = resource.getRequirements(IdentityNamespace.IDENTITY_NAMESPACE); + int marked = 0; + for (Requirement requirement : requirements) { + Map reqAttrs = requirement.getAttributes(); + String relation = Objects.toString(reqAttrs.get(ResourceUtils.FEATURE_RELATION_ATTRIBUTE), null); + if (relation == null) + continue; + marked++; + + boolean include = ResourceUtils.FEATURE_RELATION_INCLUDE.equals(relation); + if (!include && !ResourceUtils.FEATURE_RELATION_PLUGIN.equals(relation)) + continue; // a requires import is a dependency, not a member + + if (!EclipsePlatform.CURRENT.matches(Objects.toString(reqAttrs.get("os"), null), + Objects.toString(reqAttrs.get("ws"), null), Objects.toString(reqAttrs.get("arch"), null))) + continue; + + String id = Objects.toString(reqAttrs.get("id"), null); + if (id == null) + continue; + String version = Objects.toString(reqAttrs.get(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE), null); + boolean optional = Namespace.RESOLUTION_OPTIONAL.equals(requirement.getDirectives() + .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE)); + + Container member = getFeatureMember(key, id, version, include, optional); + if (member == null) + continue; + + for (Container c : member.getMembers(visitedFeatures)) { + if (!result.contains(c)) + result.add(c); + } + } + + if (marked == 0 && !requirements.isEmpty()) { + warning( + "The index of the repository containing feature %s predates feature member support, refresh the repository to expand the feature", + key); + } + return result; + } + + /** + * Resolve a single feature member. The version pinned in the feature is + * tried exactly first; if absent from the repositories the highest + * available version is used with a warning. + * + * @return the member container, an error container, or null when an + * optional member is not available + */ + private Container getFeatureMember(String featureKey, String id, String version, boolean include, + boolean optional) throws Exception { + Map attrs = include + ? Collections.singletonMap(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, ResourceUtils.TYPE_ECLIPSE_FEATURE) + : null; + + Container member = null; + if (version != null && Verifier.isVersion(version)) { + member = getBundle(id, version, Strategy.EXACT, attrs); + } + if (member == null || member.getError() != null) { + Container highest = getBundle(id, null, Strategy.HIGHEST, attrs); + if (highest.getError() == null) { + if (member != null) { + warning("Member %s;version=%s of feature %s not found with the exact version, using %s instead", + id, version, featureKey, highest.getVersion()); + } + member = highest; + } else if (member == null) { + member = highest; + } + } + + if (member.getError() != null && optional) { + logger.debug("skipping optional member {} of feature {}: {}", id, featureKey, member.getError()); + return null; + } + return member; + } + /** * Look for the bundle in the workspace. The premise is that the bsn must * start with the project name. diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/RepoCollector.java b/biz.aQute.bndlib/src/aQute/bnd/build/RepoCollector.java index 01d053e25f..e1e6d09ae8 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/RepoCollector.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/RepoCollector.java @@ -103,7 +103,7 @@ Collection repoContainers(String[] args) throws Exception { } private void add(Collection containers, Container container) throws Exception { - if (container.getType() == Container.TYPE.LIBRARY) { + if (container.getType() == Container.TYPE.LIBRARY || container.getType() == Container.TYPE.FEATURE) { List members = container.getMembers(); for (Container sub : members) { add(containers, sub); diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java index 4418f74ad6..10e2d30d07 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java @@ -1,6 +1,6 @@ /** */ -@Version("4.7.1") +@Version("4.8.0") package aQute.bnd.build; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java index c1b352c555..2290345dd3 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/ResourceUtils.java @@ -99,6 +99,27 @@ public abstract class ResourceUtils { public static final Resource DUMMY_RESOURCE = new ResourceBuilder().build(); public static final String WORKSPACE_NAMESPACE = "bnd.workspace.project"; + /** + * The identity type of an Eclipse feature resource, see + * {@link IdentityNamespace#CAPABILITY_TYPE_ATTRIBUTE}. + */ + public static final String TYPE_ECLIPSE_FEATURE = "org.eclipse.update.feature"; + + /** + * Requirement attribute conveying the provenance of an osgi.identity + * requirement of an Eclipse feature resource: + * {@link #FEATURE_RELATION_PLUGIN} for {@code } references, + * {@link #FEATURE_RELATION_INCLUDE} for {@code } references and + * {@link #FEATURE_RELATION_REQUIRE} for {@code } + * references. The attribute does not take part in filter matching; it + * allows consumers (e.g. -buildpath feature expansion) to distinguish + * feature members from mere dependencies. + */ + public static final String FEATURE_RELATION_ATTRIBUTE = "bnd.relation"; + public static final String FEATURE_RELATION_PLUGIN = "plugin"; + public static final String FEATURE_RELATION_INCLUDE = "include"; + public static final String FEATURE_RELATION_REQUIRE = "require"; + private static final Converter cnv = new Converter() .hook(Version.class, (dest, o) -> toVersion(o)); diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/package-info.java index 68c8db5b29..ecc6c451af 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/resource/package-info.java @@ -1,4 +1,4 @@ -@Version("5.1.0") +@Version("5.2.0") package aQute.bnd.osgi.resource; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.repository/src/aQute/bnd/repository/p2/provider/P2Indexer.java b/biz.aQute.repository/src/aQute/bnd/repository/p2/provider/P2Indexer.java index 4caa3099ba..1d16f34962 100644 --- a/biz.aQute.repository/src/aQute/bnd/repository/p2/provider/P2Indexer.java +++ b/biz.aQute.repository/src/aQute/bnd/repository/p2/provider/P2Indexer.java @@ -121,7 +121,10 @@ File get(String bsn, Version version, Map properties, DownloadLi URI url = contentCapability.url(); final File source = client.getCacheFileFor(url); - final File link = new File(location, bsn + "-" + version + ".jar"); + // Features may share bsn+version with a bundle; use a distinct link + // name to avoid overwriting the bundle in the cache (and vice versa) + boolean isFeature = ResourceUtils.hasType(resource, ECLIPSE_FEATURE_CAPABILITY); + final File link = new File(location, bsn + "-" + version + (isFeature ? ".feature.jar" : ".jar")); IO.createSymbolicLinkOrCopy(link, source); @@ -214,7 +217,7 @@ private ResourcesRepository readRepository(File index) throws Exception { if (index.isFile()) { try (XMLResourceParser xp = new XMLResourceParser(index.toURI())) { List resources = xp.parse(); - if (urlHash.equals(xp.name())) { + if (urlHash.equals(xp.name()) && !requiresReindex(resources)) { return new ResourcesRepository(resources); } } @@ -222,6 +225,32 @@ private ResourcesRepository readRepository(File index) throws Exception { return save(readRepository()); } + /** + * Detect indexes written before feature requirements carried the + * {@link Feature#RELATION_ATTRIBUTE} provenance marker. Such stale caches + * are re-indexed once so feature expansion can distinguish members + * (plugins/includes) from mere dependencies (requires). + */ + private boolean requiresReindex(List resources) { + for (Resource resource : resources) { + if (!ResourceUtils.hasType(resource, ECLIPSE_FEATURE_CAPABILITY)) + continue; + + List requirements = resource.getRequirements(IdentityNamespace.IDENTITY_NAMESPACE); + if (requirements.isEmpty()) + continue; + + boolean marked = requirements.stream() + .anyMatch(req -> req.getAttributes() + .containsKey(Feature.RELATION_ATTRIBUTE)); + if (!marked) { + logger.info("p2 index {} lacks feature relation markers, re-indexing", indexFile); + return true; + } + } + return false; + } + private ResourcesRepository readRepository() throws Exception { ArtifactProvider p2; if (isTargetPlatform(this.url)) diff --git a/biz.aQute.repository/src/aQute/p2/provider/Feature.java b/biz.aQute.repository/src/aQute/p2/provider/Feature.java index 8e3f3abefe..1e1f5891a0 100644 --- a/biz.aQute.repository/src/aQute/p2/provider/Feature.java +++ b/biz.aQute.repository/src/aQute/p2/provider/Feature.java @@ -19,6 +19,7 @@ import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.resource.CapReqBuilder; import aQute.bnd.osgi.resource.ResourceBuilder; +import aQute.bnd.osgi.resource.ResourceUtils; /** * Parser for Eclipse feature.xml files. This class parses Eclipse features and @@ -27,6 +28,21 @@ */ public class Feature extends XMLBase { + /** + * Requirement attribute marking the relation of an osgi.identity + * requirement to the feature: {@value #RELATION_PLUGIN} for + * {@code } references, {@value #RELATION_INCLUDE} for + * {@code } references and {@value #RELATION_REQUIRE} for + * {@code } references. This attribute does not take part + * in filter matching, it only conveys provenance so that consumers (e.g. + * -buildpath feature expansion) can distinguish members from mere + * dependencies. + */ + public static final String RELATION_ATTRIBUTE = ResourceUtils.FEATURE_RELATION_ATTRIBUTE; + public static final String RELATION_PLUGIN = ResourceUtils.FEATURE_RELATION_PLUGIN; + public static final String RELATION_INCLUDE = ResourceUtils.FEATURE_RELATION_INCLUDE; + public static final String RELATION_REQUIRE = ResourceUtils.FEATURE_RELATION_REQUIRE; + /** * Represents a plugin reference in a feature */ @@ -312,6 +328,17 @@ public Resource toResource() throws Exception { req.addDirective("filter", String.format("(&(osgi.identity=%s)(version=%s))", plugin.id, plugin.version)); } + req.addAttribute(RELATION_ATTRIBUTE, RELATION_PLUGIN); + req.addAttribute("id", plugin.id); + addVersionAttribute(req, plugin.version); + if (plugin.os != null) + req.addAttribute("os", plugin.os); + if (plugin.ws != null) + req.addAttribute("ws", plugin.ws); + if (plugin.arch != null) + req.addAttribute("arch", plugin.arch); + if (plugin.fragment) + req.addAttribute("fragment", "true"); rb.addRequirement(req); } @@ -327,6 +354,16 @@ public Resource toResource() throws Exception { if (include.optional) { req.addDirective("resolution", "optional"); } + req.addAttribute(RELATION_ATTRIBUTE, RELATION_INCLUDE); + req.addAttribute("id", include.id); + req.addAttribute(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, "org.eclipse.update.feature"); + addVersionAttribute(req, include.version); + if (include.os != null) + req.addAttribute("os", include.os); + if (include.ws != null) + req.addAttribute("ws", include.ws); + if (include.arch != null) + req.addAttribute("arch", include.arch); rb.addRequirement(req); } @@ -339,12 +376,29 @@ public Resource toResource() throws Exception { String filter = buildRequirementFilter(reqIdentity, requirement.version, requirement.match, requirement.feature != null); req.addDirective("filter", filter); + req.addAttribute(RELATION_ATTRIBUTE, RELATION_REQUIRE); rb.addRequirement(req); } return rb.build(); } + /** + * Add an exact version attribute to a requirement when the version is a + * valid OSGi version and not the 0.0.0 placeholder. The filter directive + * remains the authoritative constraint; the attribute only conveys the + * exact pinned version to consumers without requiring filter parsing. + */ + private static void addVersionAttribute(CapReqBuilder req, String version) { + if (version == null || version.equals("0.0.0")) + return; + try { + req.addAttribute("version", Version.parseVersion(version)); + } catch (IllegalArgumentException e) { + // not a valid OSGi version, the filter still carries it + } + } + /** * Build an LDAP filter for a requirement with version constraint. * Handles Eclipse match rules: "perfect", "equivalent", "compatible", "greaterOrEqual" diff --git a/biz.aQute.repository/src/aQute/p2/provider/package-info.java b/biz.aQute.repository/src/aQute/p2/provider/package-info.java index 0c619a201b..afdb25f8e2 100644 --- a/biz.aQute.repository/src/aQute/p2/provider/package-info.java +++ b/biz.aQute.repository/src/aQute/p2/provider/package-info.java @@ -1,4 +1,4 @@ -@Version("1.0.0") +@Version("1.1.0") package aQute.p2.provider; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.repository/test/aQute/p2/provider/FeatureParserTest.java b/biz.aQute.repository/test/aQute/p2/provider/FeatureParserTest.java index 91594c0100..137aa657c5 100644 --- a/biz.aQute.repository/test/aQute/p2/provider/FeatureParserTest.java +++ b/biz.aQute.repository/test/aQute/p2/provider/FeatureParserTest.java @@ -302,4 +302,102 @@ public void testFeatureParsingDetails() throws Exception { .hasSizeGreaterThanOrEqualTo(4); } } + + /** + * Test that the osgi.identity requirements created by toResource() carry + * the provenance/relation attributes needed by the -buildpath feature + * expansion: bnd.relation, id, version, type and the os/ws/arch platform + * attributes. The filter directives must remain untouched. + */ + @Test + public void testMemberRelationAttributes() throws Exception { + String featureXml = """ + + + + + + + + + + + + """; + + File featureFile = new File(tmp, "feature.xml"); + IO.store(featureXml, featureFile); + File featureJar = new File(tmp, "test.feature_1.0.0.jar"); + try (aQute.bnd.osgi.Jar jar = new aQute.bnd.osgi.Jar("test.feature")) { + jar.putResource("feature.xml", new aQute.bnd.osgi.FileResource(featureFile)); + jar.write(featureJar); + } + + try (InputStream is = IO.stream(featureJar)) { + Feature feature = new Feature(is); + feature.parse(); + Resource resource = feature.toResource(); + + List requirements = resource.getRequirements(IdentityNamespace.IDENTITY_NAMESPACE); + softly.assertThat(requirements) + .hasSize(6); + + Requirement plainPlugin = requirementById(requirements, "plain.plugin"); + softly.assertThat(plainPlugin.getAttributes()) + .containsEntry(Feature.RELATION_ATTRIBUTE, Feature.RELATION_PLUGIN) + .containsEntry("id", "plain.plugin") + .doesNotContainKeys("os", "ws", "arch", "fragment", + IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE); + softly.assertThat(String.valueOf(plainPlugin.getAttributes() + .get("version"))) + .isEqualTo("1.2.3"); + softly.assertThat(plainPlugin.getDirectives() + .get("filter")) + .isEqualTo("(&(osgi.identity=plain.plugin)(version=1.2.3))"); + + Requirement platformPlugin = requirementById(requirements, "platform.plugin"); + softly.assertThat(platformPlugin.getAttributes()) + .containsEntry(Feature.RELATION_ATTRIBUTE, Feature.RELATION_PLUGIN) + .containsEntry("os", "win32,linux") + .containsEntry("ws", "gtk") + .containsEntry("arch", "x86_64") + .containsEntry("fragment", "true"); + + Requirement include = requirementById(requirements, "included.feature"); + softly.assertThat(include.getAttributes()) + .containsEntry(Feature.RELATION_ATTRIBUTE, Feature.RELATION_INCLUDE) + .containsEntry("id", "included.feature") + .containsEntry(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, "org.eclipse.update.feature"); + softly.assertThat(include.getDirectives() + .get("filter")) + .isEqualTo("(&(osgi.identity=included.feature)(type=org.eclipse.update.feature)(version=3.0.0))"); + softly.assertThat(include.getDirectives()) + .doesNotContainKey("resolution"); + + Requirement optionalInclude = requirementById(requirements, "optional.feature"); + softly.assertThat(optionalInclude.getAttributes()) + .containsEntry(Feature.RELATION_ATTRIBUTE, Feature.RELATION_INCLUDE); + softly.assertThat(optionalInclude.getDirectives()) + .containsEntry("resolution", "optional"); + + List requireReqs = requirements.stream() + .filter(r -> Feature.RELATION_REQUIRE.equals(r.getAttributes() + .get(Feature.RELATION_ATTRIBUTE))) + .toList(); + softly.assertThat(requireReqs) + .as("requires imports must be marked as require relation") + .hasSize(2); + } + } + + private static Requirement requirementById(List requirements, String id) { + return requirements.stream() + .filter(r -> id.equals(r.getAttributes() + .get("id")) + || String.valueOf(r.getDirectives() + .get("filter")) + .contains("(osgi.identity=" + id + ")")) + .findFirst() + .orElseThrow(() -> new AssertionError("No requirement found for " + id)); + } } diff --git a/bndtools.core.test/bnd.bnd b/bndtools.core.test/bnd.bnd index ec7726e185..e95bba3653 100644 --- a/bndtools.core.test/bnd.bnd +++ b/bndtools.core.test/bnd.bnd @@ -54,6 +54,9 @@ org.apiguardian:apiguardian-api,\ org.opentest4j,\ org.apache.servicemix.bundles.junit,\ + org.eclipse.swtbot.swt.finder,\ + org.eclipse.swtbot.eclipse.finder,\ + org.hamcrest,\ junit-platform-commons,\ junit-platform-engine,\ junit-jupiter-api,\ diff --git a/bndtools.core.test/launch.rendered.bnd b/bndtools.core.test/launch.rendered.bnd new file mode 100644 index 0000000000..169d9a649c --- /dev/null +++ b/bndtools.core.test/launch.rendered.bnd @@ -0,0 +1,4 @@ +-privatepackage: bndtools.core.test.launch.rendered +Bundle-Activator: bndtools.core.test.launch.rendered.Activator + +Import-Package: ${eclipse.importpackage},* diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.gitignore b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.gitignore new file mode 100644 index 0000000000..fe3881116b --- /dev/null +++ b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.gitignore @@ -0,0 +1,3 @@ +/generated/ +/bin/ +/cache/ diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.project b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.project new file mode 100644 index 0000000000..1397bbe620 --- /dev/null +++ b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/.project @@ -0,0 +1,11 @@ + + + cnf + + + + + + + + diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/build.bnd b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/build.bnd new file mode 100644 index 0000000000..2100c2e97a --- /dev/null +++ b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/build.bnd @@ -0,0 +1,8 @@ +# Test workspace for the Eclipse feature -buildpath SWTBot tests. +# The Feature Fixture repository is a minimal local p2 repository containing +# the feature test.feature with the member bundles feature.member.a/b. + +-plugin.0.FeatureFixture:\ + aQute.bnd.repository.p2.provider.P2Repository;\ + name = "Feature Fixture";\ + url = "${fileuri;${.}/fixture-p2}" diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/artifacts.xml b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/artifacts.xml new file mode 100644 index 0000000000..4a4bd16c70 --- /dev/null +++ b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/artifacts.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/features/test.feature_1.0.0.jar b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/features/test.feature_1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..a7d08208ecfd56505bda43e230b415fddddc53c0 GIT binary patch literal 597 zcmWIWW@Zs#;Nak35DWhu!+-=h8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gU#tyaTDPkhawJerB5ucJW?OE2RqK`$4U`IUMzPrvDXoV9HBOD=$<^yh^ zY+7n!Noi54UPW$B?-@tFCIf-ihqlXSoSC=CT0Hk=A(MrJxBI0Vd`mxz9@qUhMwWw07N-sXq6F8q2wi*G5ih5a?ODV1vC< z$@?Fib|+QO?k=B_Wj0YHs%(We!vyaeYUlO0btnaGnbPtz%q;Fqh4nPGH7#fL5B^g; zv20GsSFkr8^fKS-13HqM5#$X!29jh3!fimh6X+8L1^~a?qOkw~ literal 0 HcmV?d00001 diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/p2.index b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/p2.index new file mode 100644 index 0000000000..03370d5985 --- /dev/null +++ b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/p2.index @@ -0,0 +1,3 @@ +version = 1 +metadata.repository.factory.order = content.xml,! +artifact.repository.factory.order = artifacts.xml,! diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/plugins/feature.member.a_1.0.0.jar b/bndtools.core.test/resources/workspaces/ui/swtbot/features/cnf/fixture-p2/plugins/feature.member.a_1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..aa6967925d303ca588de86c6537609405b698de1 GIT binary patch literal 581 zcmWIWW@Zs#;Nak35Dotw!+-=h8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gWO1bJY{-Jt9*Pfmabyq zn#p`r=L?UhNzCM8BkrzXQJLUR++IfB#io@zD|Uh%{oel8g<_zSW&<&9M{|Q5O$)a^ zd&&fM#(B?09ezMdionibWD;RO4SHC(f`T3uz{3(05dq$)T9JJR3U~yt1v24Uk-Ua( d0WO1bJY{-Jt9*Pfmabyq zn#p`r=L?UhNzCM8BkrzXQJLUR++IfB#io@zD|Uh%t+jVaNion#vw;}lXi+pDbAudB z3%5Rd$^>@C3%{B!KcFQ=U}rEgi7=oBJuF;7L5~XHVF`+e0B=;S$i4#wJObDPnQ*O0 iUPCqk6bi@=1BC(td;&62G_taRB$ + + + + + + diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/.project b/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/.project new file mode 100644 index 0000000000..830905b5f3 --- /dev/null +++ b/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/.project @@ -0,0 +1,23 @@ + + + test.buildpath + + + + + + org.eclipse.jdt.core.javabuilder + + + + + bndtools.core.bndbuilder + + + + + + org.eclipse.jdt.core.javanature + bndtools.core.bndnature + + diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/bnd.bnd b/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/bnd.bnd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/src/.gitkeep b/bndtools.core.test/resources/workspaces/ui/swtbot/features/test.buildpath/src/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bndtools.core.test/src/bndtools/core/test/launch/rendered/Activator.java b/bndtools.core.test/src/bndtools/core/test/launch/rendered/Activator.java new file mode 100644 index 0000000000..7c28c31706 --- /dev/null +++ b/bndtools.core.test/src/bndtools/core/test/launch/rendered/Activator.java @@ -0,0 +1,90 @@ +package bndtools.core.test.launch.rendered; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.osgi.service.runnable.ApplicationLauncher; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Activator bridging the bnd launcher to a rendered Eclipse + * workbench for SWTBot tests. + * + * @see RenderedLauncher + */ +public class Activator implements BundleActivator { + @Override + public void start(BundleContext context) throws Exception { + new LauncherTracker(context).open(true); + } + + @Override + public void stop(BundleContext context) throws Exception {} +} + +class LauncherTracker extends ServiceTracker> { + + private final Logger log = Logger.getLogger(Activator.class.getPackage() + .getName()); + + public LauncherTracker(BundleContext context) { + super(context, createFilter(), null); + } + + private static Filter createFilter() { + try { + return FrameworkUtil.createFilter("(&(objectClass=aQute.launcher.Launcher)(launcher.ready=true))"); //$NON-NLS-1$ + } catch (InvalidSyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public ServiceRegistration addingService(ServiceReference reference) { + // Find and start the Equinox Application bundle + boolean found = false; + Bundle[] bundles = context.getBundles(); + for (Bundle bundle : bundles) { + if ("org.eclipse.equinox.app".equals(getBsn(bundle))) { + found = true; + try { + bundle.start(); + } catch (BundleException e) { + log.log(Level.SEVERE, + "Unable to start bundle org.eclipse.equinox.app. Eclipse application cannot start.", e); + } + break; + } + } + if (!found) + log.warning("Unable to find bundle org.eclipse.equinox.app. Eclipse application will not start."); + + // Register the ApplicationLauncher + log.fine("Registering ApplicationLauncher service."); + return context.registerService(ApplicationLauncher.class, new RenderedLauncher(context), null); + } + + @Override + public void removedService(ServiceReference reference, ServiceRegistration service) { + service.unregister(); + } + + private String getBsn(Bundle bundle) { + String bsn = bundle.getHeaders() + .get(Constants.BUNDLE_SYMBOLICNAME); + int semiColonIndex = bsn.indexOf(';'); + if (semiColonIndex > -1) + bsn = bsn.substring(0, semiColonIndex); + return bsn; + } +} diff --git a/bndtools.core.test/src/bndtools/core/test/launch/rendered/RenderedLauncher.java b/bndtools.core.test/src/bndtools/core/test/launch/rendered/RenderedLauncher.java new file mode 100644 index 0000000000..4b97ade527 --- /dev/null +++ b/bndtools.core.test/src/bndtools/core/test/launch/rendered/RenderedLauncher.java @@ -0,0 +1,56 @@ +package bndtools.core.test.launch.rendered; + +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.eclipse.osgi.service.runnable.ApplicationLauncher; +import org.eclipse.osgi.service.runnable.ParameterizedRunnable; +import org.osgi.framework.BundleContext; + +/** + * Variant of the headless launcher of {@code bndtools.core.test.launch} that + * renders the workbench with the default SWT presentation engine. This is + * required for SWTBot tests which drive real widgets. It only bridges the bnd + * launcher main thread to the Eclipse application; it does not register a + * {@code NullContextPresentationEngine}. + */ +class RenderedLauncher implements ApplicationLauncher { + + private final Logger log = Logger.getLogger(RenderedLauncher.class.getPackage() + .getName()); + private final BundleContext bc; + + public RenderedLauncher(BundleContext bc) { + this.bc = bc; + } + + @Override + public void launch(final ParameterizedRunnable runnable, final Object context) { + log.log(Level.FINE, + "Received launch request from Eclipse application service, registering java.lang.Runnable{main.thread=true}"); + Runnable service = () -> { + try { + log.log(Level.FINE, "Executing application on thread {0} ({1}).", new Object[] { + Thread.currentThread() + .getName(), + Thread.currentThread() + .getId() + }); + runnable.run(context); + } catch (Exception e) { + log.log(Level.SEVERE, "Error executing application", e); + } + }; + + Dictionary svcProps = new Hashtable<>(); + svcProps.put("main.thread", "true"); + bc.registerService(Runnable.class, service, svcProps); + } + + @Override + public void shutdown() { + log.fine("Received shutdown request"); + } +} diff --git a/bndtools.core.test/src/bndtools/core/test/ui/swtbot/AbstractFeatureSwtbotTest.java b/bndtools.core.test/src/bndtools/core/test/ui/swtbot/AbstractFeatureSwtbotTest.java new file mode 100644 index 0000000000..e7fe2ae5b4 --- /dev/null +++ b/bndtools.core.test/src/bndtools/core/test/ui/swtbot/AbstractFeatureSwtbotTest.java @@ -0,0 +1,173 @@ +package bndtools.core.test.ui.swtbot; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotMultiPageEditor; +import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; +import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; + +import aQute.bnd.exceptions.Exceptions; +import aQute.lib.io.IO; + +/** + * Base class for SWTBot driven UI tests of the Eclipse feature support on + * -buildpath. Requires a rendered workbench, i.e. the bndrun must + * use the {@code bndtools.core.test.launch.rendered} bundle instead of the + * headless {@code bndtools.core.test.launch} bundle. + */ +public abstract class AbstractFeatureSwtbotTest { + + protected static final String FEATURE_CLAUSE = "test.feature;version='1.0.0';type=org.eclipse.update.feature"; + protected static final String FIXTURE_REPO = "Feature Fixture"; + protected static final String TEST_PROJECT = "test.buildpath"; + + protected static SWTWorkbenchBot bot; + + @BeforeAll + static void setUpBot() { + SWTBotPreferences.TIMEOUT = 30000; + SWTBotPreferences.PLAYBACK_DELAY = 20; + bot = new SWTWorkbenchBot(); + try { + bot.viewByTitle("Welcome") + .close(); + } catch (WidgetNotFoundException e) { + // Welcome view not present, continue + } + } + + @AfterEach + void closeEditors() { + bot.saveAllEditors(); + bot.closeAllEditors(); + } + + /** + * Open the bnd.bnd file of the test project in the BndEditor and activate + * its "Build" page. The editor is opened programmatically to keep the + * test focused on the Build Path UI itself. + */ + protected SWTBotMultiPageEditor openBndEditorBuildPage() { + IProject project = ResourcesPlugin.getWorkspace() + .getRoot() + .getProject(TEST_PROJECT); + IFile bndFile = project.getFile("bnd.bnd"); + assertThat(bndFile.exists()).as("bnd.bnd of %s exists", TEST_PROJECT) + .isTrue(); + + AtomicReference failure = new AtomicReference<>(); + Display.getDefault() + .syncExec(() -> { + try { + IEditorPart part = IDE.openEditor(PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage(), bndFile, "bndtools.bndEditor", true); + assertThat(part).isNotNull(); + } catch (Exception e) { + failure.set(e); + } + }); + if (failure.get() != null) { + throw Exceptions.duck(failure.get()); + } + + SWTBotEditor editor = bot.editorByTitle(TEST_PROJECT); + SWTBotMultiPageEditor multiPage = new SWTBotMultiPageEditor(editor.getReference(), bot); + multiPage.activatePage("Build"); + return multiPage; + } + + /** + * Locate the feature node below the fixture repository node. Repository + * children are loaded lazily and the tree may be refreshed asynchronously, + * so all nodes are re-resolved on every poll. + */ + protected SWTBotTreeItem findFeatureNode(SWTBotTree tree) { + AtomicReference result = new AtomicReference<>(); + bot.waitUntilWidgetAppears(new DefaultCondition() { + @Override + public boolean test() { + for (SWTBotTreeItem repoNode : tree.getAllItems()) { + String repoText = repoNode.getText(); + if (repoText == null || !repoText.startsWith(FIXTURE_REPO)) { + continue; + } + SWTBotTreeItem feature = featureChild(repoNode); + if (feature != null) { + result.set(feature); + return true; + } + // The node may report expanded although JFace never + // populated it (native dummy child). Cycle + // collapse/expand to force the content provider to run. + if (repoNode.isExpanded()) { + repoNode.collapse(); + } + repoNode.expand(); + feature = featureChild(repoNode); + if (feature != null) { + result.set(feature); + return true; + } + } + return false; + } + + private SWTBotTreeItem featureChild(SWTBotTreeItem repoNode) { + for (SWTBotTreeItem child : repoNode.getItems()) { + String text = child.getText(); + if (text != null && text.startsWith("test.feature")) { + return child; + } + } + return null; + } + + @Override + public String getFailureMessage() { + StringBuilder sb = new StringBuilder( + "Feature node test.feature did not appear below repository " + FIXTURE_REPO + ". Tree state:"); + for (SWTBotTreeItem repoNode : tree.getAllItems()) { + sb.append("\n- ") + .append(repoNode.getText()); + for (SWTBotTreeItem child : repoNode.getItems()) { + sb.append("\n - ") + .append(child.getText()); + } + } + return sb.toString(); + } + }); + return result.get(); + } + + protected String readTestProjectBndBnd() { + try { + IFile bndFile = ResourcesPlugin.getWorkspace() + .getRoot() + .getProject(TEST_PROJECT) + .getFile("bnd.bnd"); + bndFile.refreshLocal(0, null); + return IO.collect(bndFile.getLocation() + .toFile()); + } catch (Exception e) { + throw Exceptions.duck(e); + } + } +} diff --git a/bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathDndSwtbotTest.java b/bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathDndSwtbotTest.java new file mode 100644 index 0000000000..293bcd7c55 --- /dev/null +++ b/bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathDndSwtbotTest.java @@ -0,0 +1,75 @@ +package bndtools.core.test.ui.swtbot; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotMultiPageEditor; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.eclipse.ui.PlatformUI; +import org.junit.jupiter.api.Test; + +import aQute.bnd.exceptions.Exceptions; +import bndtools.core.test.utils.WorkbenchTest; + +/** + * Drags an Eclipse feature from the Repositories view onto the Build Path + * table of the BndEditor and asserts that the canonical feature clause is + * written to bnd.bnd. This exercises the same drop handler that is shared + * with the Run Bundles section. + */ +@WorkbenchTest("ui/swtbot/features") +class FeatureBuildPathDndSwtbotTest extends AbstractFeatureSwtbotTest { + + private static final String REPOSITORIES_VIEW_ID = "bndtools.repositoriesView"; + + @Test + void dragFeatureToBuildPath_writesCanonicalClause() throws Exception { + SWTBotView repositoriesView = openRepositoriesView(); + SWTBotMultiPageEditor editor = openBndEditorBuildPage(); + + // Locate the feature in the Repositories view + SWTBotTreeItem featureNode = findFeatureNode(repositoriesView.bot() + .tree()); + featureNode.select(); + + // The Build Path table is the only table on the Build page + SWTBotTable buildPathTable = editor.bot() + .table(0); + + featureNode.dragAndDrop(buildPathTable); + + editor.save(); + + String bndBnd = readTestProjectBndBnd(); + assertThat(bndBnd).as("bnd.bnd contains the canonical feature clause") + .contains("test.feature") + .containsPattern("type='?org\\.eclipse\\.update\\.feature'?") + .doesNotContain("feature:") + .doesNotContain("feature=true"); + } + + private SWTBotView openRepositoriesView() { + AtomicReference failure = new AtomicReference<>(); + Display.getDefault() + .syncExec(() -> { + try { + PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .showView(REPOSITORIES_VIEW_ID); + } catch (Exception e) { + failure.set(e); + } + }); + if (failure.get() != null) { + throw Exceptions.duck(failure.get()); + } + SWTBotView view = bot.viewById(REPOSITORIES_VIEW_ID); + view.show(); + return view; + } +} diff --git a/bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathWizardSwtbotTest.java b/bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathWizardSwtbotTest.java new file mode 100644 index 0000000000..9c8d0203a7 --- /dev/null +++ b/bndtools.core.test/src/bndtools/core/test/ui/swtbot/FeatureBuildPathWizardSwtbotTest.java @@ -0,0 +1,52 @@ +package bndtools.core.test.ui.swtbot; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotMultiPageEditor; +import org.eclipse.swtbot.swt.finder.SWTBot; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.junit.jupiter.api.Test; + +import bndtools.core.test.utils.WorkbenchTest; + +/** + * Adds an Eclipse feature to -buildpath through the "Add Bundle" wizard of + * the Build Path section and asserts that the canonical feature clause + * {@code id;version='V';type=org.eclipse.update.feature} is written to + * bnd.bnd. + */ +@WorkbenchTest("ui/swtbot/features") +class FeatureBuildPathWizardSwtbotTest extends AbstractFeatureSwtbotTest { + + @Test + void addFeatureViaWizard_writesCanonicalClause() throws Exception { + SWTBotMultiPageEditor editor = openBndEditorBuildPage(); + + // Open the bundle selection wizard of the Build Path section + editor.bot() + .toolbarButtonWithTooltip("Add Bundle") + .click(); + + // The wizard dialog is the active shell after the click + SWTBotShell shell = bot.activeShell(); + SWTBot wizardBot = shell.bot(); + + // Select the feature below the fixture repository + SWTBotTreeItem featureNode = findFeatureNode(wizardBot.tree()); + featureNode.select(); + + wizardBot.button("Add -->") + .click(); + wizardBot.button("Finish") + .click(); + + editor.save(); + + String bndBnd = readTestProjectBndBnd(); + assertThat(bndBnd).as("bnd.bnd contains the canonical feature clause") + .contains("test.feature") + .containsPattern("type='?org\\.eclipse\\.update\\.feature'?") + .containsPattern("version='?1\\.0\\.0'?"); + } +} diff --git a/bndtools.core.test/swtbot.tests.bnd b/bndtools.core.test/swtbot.tests.bnd new file mode 100644 index 0000000000..e73c7787a5 --- /dev/null +++ b/bndtools.core.test/swtbot.tests.bnd @@ -0,0 +1,10 @@ +Test-Cases: ${classes;CONCRETE;HIERARCHY_INDIRECTLY_ANNOTATED;org.junit.platform.commons.annotation.Testable} + +# The harness utilities (WorkbenchTest/WorkspaceImporter/TaskUtils) are +# duplicated into this bundle since the headless test bundle is not part of +# the SWTBot launch. +-privatepackage: \ + bndtools.core.test.ui.swtbot,\ + bndtools.core.test.utils + +Import-Package: ${eclipse.importpackage},* diff --git a/bndtools.core.test/test.swtbot.cocoa.macosx.aarch64.bndrun b/bndtools.core.test/test.swtbot.cocoa.macosx.aarch64.bndrun new file mode 100644 index 0000000000..8cde032dfb --- /dev/null +++ b/bndtools.core.test/test.swtbot.cocoa.macosx.aarch64.bndrun @@ -0,0 +1,330 @@ +-include: test.swtbot.shared.bndrun + +-runrequires.macos: \ + bnd.identity;id='org.eclipse.core.filesystem.macosx',\ + bnd.identity;id='org.eclipse.e4.ui.workbench.renderers.swt.cocoa',\ + bnd.identity;id='org.eclipse.equinox.security.macosx',\ + bnd.identity;id='org.eclipse.jdt.launching.macosx',\ + bnd.identity;id='org.eclipse.jdt.launching.ui.macosx',\ + bnd.identity;id='org.eclipse.swt.cocoa.macosx.aarch64',\ + bnd.identity;id='org.eclipse.ui.cocoa' + +-runvm.macos: \ + -XstartOnFirstThread, \ + -Dorg.eclipse.swt.internal.carbon.smallFonts + +-runsystemcapabilities.macos: \ + osgi.native;osgi.native.osname:List="macosx";osgi.native.processor:List="aarch64" + +-runproperties.macos: \ + osgi.ws=cocoa,\ + osgi.arch=aarch64,\ + osgi.os=macosx + +-runblacklist.macos: \ + osgi.identity;filter:='(osgi.identity=*linux*)',\ + osgi.identity;filter:='(osgi.identity=*win32*)',\ + +-resolve: auto +-runbundles: \ + assertj-core;version='[3.24.2,3.24.3)';startlevel=3,\ + bcpg;version='[1.77.0,1.77.1)';startlevel=3,\ + bcpkix;version='[1.77.0,1.77.1)';startlevel=3,\ + bcprov;version='[1.77.0,1.77.1)';startlevel=3,\ + bcutil;version='[1.77.0,1.77.1)';startlevel=3,\ + biz.aQute.bnd.embedded-repo;version=snapshot;startlevel=3,\ + biz.aQute.bnd.util;version=snapshot;startlevel=3,\ + biz.aQute.bndlib;version=snapshot;startlevel=3,\ + biz.aQute.repository;version=snapshot;startlevel=3,\ + biz.aQute.resolve;version=snapshot;startlevel=3,\ + biz.aQute.tester.junit-platform;version=snapshot;startlevel=3,\ + bndtools.api;version=snapshot;startlevel=4,\ + bndtools.builder;version=snapshot;startlevel=4,\ + bndtools.core;version=snapshot;startlevel=4,\ + bndtools.core.services;version=snapshot;startlevel=1,\ + bndtools.core.test.launch.rendered;version=snapshot;startlevel=5,\ + bndtools.core.test.swtbot.tests;version=snapshot;startlevel=5,\ + com.google.gson;version='[2.10.1,2.10.2)';startlevel=3,\ + com.google.guava;version='[33.4.8,33.4.9)';startlevel=3,\ + com.google.guava.failureaccess;version='[1.0.3,1.0.4)';startlevel=3,\ + com.googlecode.javaewah.JavaEWAH;version='[1.2.3,1.2.4)';startlevel=3,\ + com.ibm.icu;version='[74.1.0,74.1.1)';startlevel=3,\ + com.sun.jna;version='[5.13.0,5.13.1)';startlevel=3,\ + com.sun.jna.platform;version='[5.13.0,5.13.1)';startlevel=3,\ + jakarta.annotation-api;version='[1.3.5,1.3.6)';startlevel=3,\ + jakarta.annotation-api;version='[2.1.1,2.1.2)';startlevel=3,\ + jakarta.inject.jakarta.inject-api;version='[1.0.5,1.0.6)';startlevel=3,\ + jakarta.inject.jakarta.inject-api;version='[2.0.1,2.0.2)';startlevel=3,\ + jaxen;version='[2.0.0,2.0.1)';startlevel=3,\ + junit-jupiter-api;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-jupiter-engine;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-jupiter-params;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-platform-commons;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-platform-engine;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-platform-launcher;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-vintage-engine;version='[5.10.1,5.10.2)';startlevel=3,\ + net.bytebuddy.byte-buddy;version='[1.14.9,1.14.10)';startlevel=3,\ + net.i2p.crypto.eddsa;version='[0.3.0,0.3.1)';startlevel=3,\ + org.apache.ant;version='[1.10.14,1.10.15)';startlevel=3,\ + org.apache.batik.constants;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.css;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.i18n;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.util;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.commons.cli;version='[1.6.0,1.6.1)';startlevel=3,\ + org.apache.commons.commons-codec;version='[1.16.0,1.16.1)';startlevel=3,\ + org.apache.commons.commons-io;version='[2.15.0,2.15.1)';startlevel=3,\ + org.apache.commons.jxpath;version='[1.3.0,1.3.1)';startlevel=3,\ + org.apache.commons.lang3;version='[3.13.0,3.13.1)';startlevel=3,\ + org.apache.commons.logging;version='[1.2.0,1.2.1)';startlevel=3,\ + org.apache.felix.scr;version='[2.2.6,2.2.7)';startlevel=3,\ + org.apache.httpcomponents.httpclient;version='[4.5.14,4.5.15)';startlevel=3,\ + org.apache.httpcomponents.httpcore;version='[4.4.16,4.4.17)';startlevel=3,\ + org.apache.lucene.analysis-common;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.lucene.analysis-smartcn;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.lucene.core;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.sshd.osgi;version='[2.11.0,2.11.1)';startlevel=3,\ + org.apache.sshd.sftp;version='[2.11.0,2.11.1)';startlevel=3,\ + org.apache.xerces;version='[2.12.2,2.12.3)';startlevel=3,\ + org.apache.xml.resolver;version='[1.2.0,1.2.1)';startlevel=3,\ + org.apache.xmlgraphics;version='[2.9.0,2.9.1)';startlevel=3,\ + org.bndtools.headless.build.manager;version=snapshot;startlevel=3,\ + org.bndtools.headless.build.plugin.ant;version=snapshot;startlevel=3,\ + org.bndtools.headless.build.plugin.gradle;version=snapshot;startlevel=3,\ + org.bndtools.remoteinstall;version=snapshot;startlevel=3,\ + org.bndtools.templates.template;version=snapshot;startlevel=3,\ + org.bndtools.templating;version=snapshot;startlevel=3,\ + org.bndtools.templating.gitrepo;version=snapshot;startlevel=3,\ + org.bndtools.versioncontrol.ignores.manager;version=snapshot;startlevel=3,\ + org.bndtools.versioncontrol.ignores.plugin.git;version=snapshot;startlevel=3,\ + org.eclipse.ant.core;version='[3.7.200,3.7.201)';startlevel=3,\ + org.eclipse.ant.launching;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.ant.ui;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.compare;version='[3.9.300,3.9.301)';startlevel=3,\ + org.eclipse.compare.core;version='[3.8.300,3.8.301)';startlevel=3,\ + org.eclipse.core.commands;version='[3.11.200,3.11.201)';startlevel=3,\ + org.eclipse.core.contenttype;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.core.databinding;version='[1.13.100,1.13.101)';startlevel=3,\ + org.eclipse.core.databinding.beans;version='[1.10.100,1.10.101)';startlevel=3,\ + org.eclipse.core.databinding.observable;version='[1.13.100,1.13.101)';startlevel=3,\ + org.eclipse.core.databinding.property;version='[1.10.100,1.10.101)';startlevel=3,\ + org.eclipse.core.expressions;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.core.externaltools;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.core.filebuffers;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.core.filesystem;version='[1.10.200,1.10.201)';startlevel=3,\ + org.eclipse.core.filesystem.macosx;version='[1.3.400,1.3.401)';startlevel=3,\ + org.eclipse.core.jobs;version='[3.15.100,3.15.101)';startlevel=3,\ + org.eclipse.core.net;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.core.resources;version='[3.20.0,3.20.1)';startlevel=3,\ + org.eclipse.core.runtime;version='[3.30.0,3.30.1)';startlevel=3,\ + org.eclipse.core.variables;version='[3.6.200,3.6.201)';startlevel=3,\ + org.eclipse.debug.core;version='[3.21.200,3.21.201)';startlevel=3,\ + org.eclipse.debug.ui;version='[3.18.200,3.18.201)';startlevel=3,\ + org.eclipse.e4.core.commands;version='[1.1.200,1.1.201)';startlevel=3,\ + org.eclipse.e4.core.contexts;version='[1.12.400,1.12.401)';startlevel=3,\ + org.eclipse.e4.core.di;version='[1.9.200,1.9.201)';startlevel=3,\ + org.eclipse.e4.core.di.annotations;version='[1.8.200,1.8.201)';startlevel=3,\ + org.eclipse.e4.core.di.extensions;version='[0.18.100,0.18.101)';startlevel=3,\ + org.eclipse.e4.core.di.extensions.supplier;version='[0.17.300,0.17.301)';startlevel=3,\ + org.eclipse.e4.core.services;version='[2.4.200,2.4.201)';startlevel=1,\ + org.eclipse.e4.emf.xpath;version='[0.4.100,0.4.101)';startlevel=3,\ + org.eclipse.e4.tools.services;version='[4.10.300,4.10.301)';startlevel=1,\ + org.eclipse.e4.ui.bindings;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.css.core;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.css.swt;version='[0.15.200,0.15.201)';startlevel=3,\ + org.eclipse.e4.ui.css.swt.theme;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.di;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.e4.ui.dialogs;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.e4.ui.ide;version='[3.17.100,3.17.101)';startlevel=3,\ + org.eclipse.e4.ui.model.workbench;version='[2.4.100,2.4.101)';startlevel=3,\ + org.eclipse.e4.ui.progress;version='[0.4.200,0.4.201)';startlevel=3,\ + org.eclipse.e4.ui.services;version='[1.6.200,1.6.201)';startlevel=1,\ + org.eclipse.e4.ui.widgets;version='[1.4.0,1.4.1)';startlevel=3,\ + org.eclipse.e4.ui.workbench;version='[1.15.200,1.15.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.addons.swt;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.renderers.swt;version='[0.16.200,0.16.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.renderers.swt.cocoa;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.swt;version='[0.17.200,0.17.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench3;version='[0.17.200,0.17.201)';startlevel=3,\ + org.eclipse.ecf;version='[3.11.0,3.11.1)';startlevel=3,\ + org.eclipse.ecf.filetransfer;version='[5.1.103,5.1.104)';startlevel=3,\ + org.eclipse.ecf.identity;version='[3.10.0,3.10.1)';startlevel=3,\ + org.eclipse.ecf.provider.filetransfer;version='[3.3.0,3.3.1)';startlevel=3,\ + org.eclipse.egit.core;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.egit.ui;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.emf.ant;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.emf.codegen;version='[2.23.0,2.23.1)';startlevel=3,\ + org.eclipse.emf.codegen.ecore;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.codegen.ecore.ui;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.codegen.ui;version='[2.24.0,2.24.1)';startlevel=3,\ + org.eclipse.emf.common;version='[2.29.0,2.29.1)';startlevel=3,\ + org.eclipse.emf.common.ui;version='[2.22.0,2.22.1)';startlevel=3,\ + org.eclipse.emf.converter;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.emf.databinding;version='[1.7.0,1.7.1)';startlevel=3,\ + org.eclipse.emf.databinding.edit;version='[1.8.0,1.8.1)';startlevel=3,\ + org.eclipse.emf.ecore;version='[2.35.0,2.35.1)';startlevel=3,\ + org.eclipse.emf.ecore.change;version='[2.15.0,2.15.1)';startlevel=3,\ + org.eclipse.emf.ecore.edit;version='[2.14.0,2.14.1)';startlevel=3,\ + org.eclipse.emf.ecore.xmi;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.edit;version='[2.20.0,2.20.1)';startlevel=3,\ + org.eclipse.emf.edit.ui;version='[2.23.0,2.23.1)';startlevel=3,\ + org.eclipse.emf.exporter;version='[2.11.0,2.11.1)';startlevel=3,\ + org.eclipse.emf.importer;version='[2.13.0,2.13.1)';startlevel=3,\ + org.eclipse.emf.importer.ecore;version='[2.11.0,2.11.1)';startlevel=3,\ + org.eclipse.emf.importer.java;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.equinox.app;version='[1.6.400,1.6.401)';startlevel=2,\ + org.eclipse.equinox.bidi;version='[1.4.400,1.4.401)';startlevel=3,\ + org.eclipse.equinox.common;version='[3.18.200,3.18.201)';startlevel=3,\ + org.eclipse.equinox.concurrent;version='[1.2.200,1.2.201)';startlevel=3,\ + org.eclipse.equinox.event;version='[1.6.300,1.6.301)';startlevel=3,\ + org.eclipse.equinox.frameworkadmin;version='[2.3.0,2.3.1)';startlevel=3,\ + org.eclipse.equinox.frameworkadmin.equinox;version='[1.3.0,1.3.1)';startlevel=3,\ + org.eclipse.equinox.p2.artifact.repository;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.core;version='[2.10.200,2.10.201)';startlevel=3,\ + org.eclipse.equinox.p2.director;version='[2.6.200,2.6.201)';startlevel=3,\ + org.eclipse.equinox.p2.directorywatcher;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.equinox.p2.discovery;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.discovery.compatibility;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.engine;version='[2.9.0,2.9.1)';startlevel=3,\ + org.eclipse.equinox.p2.extensionlocation;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.garbagecollector;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.jarprocessor;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.equinox.p2.metadata;version='[2.8.0,2.8.1)';startlevel=3,\ + org.eclipse.equinox.p2.metadata.repository;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.operations;version='[2.7.200,2.7.201)';startlevel=3,\ + org.eclipse.equinox.p2.publisher;version='[1.9.0,1.9.1)';startlevel=3,\ + org.eclipse.equinox.p2.publisher.eclipse;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.repository;version='[2.8.0,2.8.1)';startlevel=3,\ + org.eclipse.equinox.p2.repository.tools;version='[2.4.200,2.4.201)';startlevel=3,\ + org.eclipse.equinox.p2.touchpoint.eclipse;version='[2.4.100,2.4.101)';startlevel=3,\ + org.eclipse.equinox.p2.transport.ecf;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.p2.ui;version='[2.8.200,2.8.201)';startlevel=3,\ + org.eclipse.equinox.p2.ui.discovery;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.preferences;version='[3.10.400,3.10.401)';startlevel=3,\ + org.eclipse.equinox.registry;version='[3.11.400,3.11.401)';startlevel=3,\ + org.eclipse.equinox.security;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.security.macosx;version='[1.102.100,1.102.101)';startlevel=3,\ + org.eclipse.equinox.security.ui;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.simpleconfigurator;version='[1.5.0,1.5.1)';startlevel=3,\ + org.eclipse.equinox.simpleconfigurator.manipulator;version='[2.3.0,2.3.1)';startlevel=3,\ + org.eclipse.help;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.help.base;version='[4.4.200,4.4.201)';startlevel=3,\ + org.eclipse.help.ui;version='[4.6.100,4.6.101)';startlevel=3,\ + org.eclipse.jdt.annotation;version='[2.2.800,2.2.801)';startlevel=3,\ + org.eclipse.jdt.core;version='[3.36.0,3.36.1)';startlevel=3,\ + org.eclipse.jdt.core.compiler.batch;version='[3.36.0,3.36.1)';startlevel=3,\ + org.eclipse.jdt.core.manipulation;version='[1.20.0,1.20.1)';startlevel=3,\ + org.eclipse.jdt.debug;version='[3.21.200,3.21.201)';startlevel=3,\ + org.eclipse.jdt.debug.ui;version='[3.13.200,3.13.201)';startlevel=3,\ + org.eclipse.jdt.junit;version='[3.16.200,3.16.201)';startlevel=3,\ + org.eclipse.jdt.junit.core;version='[3.13.0,3.13.1)';startlevel=3,\ + org.eclipse.jdt.junit.runtime;version='[3.7.300,3.7.301)';startlevel=3,\ + org.eclipse.jdt.launching;version='[3.21.0,3.21.1)';startlevel=3,\ + org.eclipse.jdt.launching.macosx;version='[3.6.100,3.6.101)';startlevel=3,\ + org.eclipse.jdt.launching.ui.macosx;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.jdt.ui;version='[3.31.0,3.31.1)';startlevel=3,\ + org.eclipse.jface;version='[3.32.0,3.32.1)';startlevel=3,\ + org.eclipse.jface.databinding;version='[1.15.100,1.15.101)';startlevel=3,\ + org.eclipse.jface.text;version='[3.24.200,3.24.201)';startlevel=3,\ + org.eclipse.jgit;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.gpg.bc;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.http.apache;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.ssh.apache;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.ltk.core.refactoring;version='[3.14.200,3.14.201)';startlevel=3,\ + org.eclipse.ltk.ui.refactoring;version='[3.13.200,3.13.201)';startlevel=3,\ + org.eclipse.m2e.archetype.catalog;version='[3.2.1,3.2.2)';startlevel=3,\ + org.eclipse.m2e.archetype.common;version='[3.2.104,3.2.105)';startlevel=3,\ + org.eclipse.m2e.archetype.descriptor;version='[3.2.1,3.2.2)';startlevel=3,\ + org.eclipse.m2e.archetype.maven-artifact-transfer;version='[0.13.1,0.13.2)';startlevel=3,\ + org.eclipse.m2e.binaryproject;version='[2.1.202,2.1.203)';startlevel=3,\ + org.eclipse.m2e.binaryproject.ui;version='[2.0.200,2.0.201)';startlevel=3,\ + org.eclipse.m2e.core;version='[2.4.1,2.4.2)';startlevel=3,\ + org.eclipse.m2e.core.ui;version='[2.0.7,2.0.8)';startlevel=3,\ + org.eclipse.m2e.discovery;version='[2.0.201,2.0.202)';startlevel=3,\ + org.eclipse.m2e.editor;version='[2.0.302,2.0.303)';startlevel=3,\ + org.eclipse.m2e.jdt;version='[2.3.3,2.3.4)';startlevel=3,\ + org.eclipse.m2e.jdt.ui;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.maven.runtime;version='[3.9.500,3.9.501)';startlevel=3,\ + org.eclipse.m2e.model.edit;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.profiles.core;version='[2.1.201,2.1.202)';startlevel=3,\ + org.eclipse.m2e.profiles.ui;version='[2.0.301,2.0.302)';startlevel=3,\ + org.eclipse.m2e.refactoring;version='[2.0.302,2.0.303)';startlevel=3,\ + org.eclipse.m2e.scm;version='[2.0.201,2.0.202)';startlevel=3,\ + org.eclipse.m2e.sourcelookup;version='[2.0.301,2.0.302)';startlevel=3,\ + org.eclipse.m2e.sourcelookup.ui;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.workspace.cli;version='[0.3.1,0.3.2)';startlevel=3,\ + org.eclipse.orbit.xml-apis-ext;version='[1.0.0,1.0.1)';startlevel=3,\ + org.eclipse.osgi.compatibility.state;version='[1.2.900,1.2.901)';startlevel=3,\ + org.eclipse.osgi.services;version='[3.11.200,3.11.201)';startlevel=1,\ + org.eclipse.osgi.util;version='[3.7.300,3.7.301)';startlevel=3,\ + org.eclipse.pde.build;version='[3.12.200,3.12.201)';startlevel=3,\ + org.eclipse.pde.core;version='[3.17.200,3.17.201)';startlevel=3,\ + org.eclipse.pde.genericeditor.extension;version='[1.2.200,1.2.201)';startlevel=3,\ + org.eclipse.pde.launching;version='[3.11.200,3.11.201)';startlevel=3,\ + org.eclipse.pde.runtime;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.pde.ua.core;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.pde.ua.ui;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.pde.ui;version='[3.15.0,3.15.1)';startlevel=3,\ + org.eclipse.platform;version='[4.30.0,4.30.1)';startlevel=3,\ + org.eclipse.sdk;version='[4.30.0,4.30.1)';startlevel=3,\ + org.eclipse.search;version='[3.16.0,3.16.1)';startlevel=3,\ + org.eclipse.search.core;version='[3.16.0,3.16.1)';startlevel=3,\ + org.eclipse.swt;version='[3.126.0,3.126.1)';startlevel=3,\ + org.eclipse.swt.cocoa.macosx.aarch64;version='[3.126.0,3.126.1)';startlevel=3,\ + org.eclipse.swtbot.eclipse.finder;version='[4.1.0,4.1.1)';startlevel=3,\ + org.eclipse.swtbot.swt.finder;version='[4.1.0,4.1.1)';startlevel=3,\ + org.eclipse.team.core;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.team.ui;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.text;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui;version='[3.205.0,3.205.1)';startlevel=3,\ + org.eclipse.ui.browser;version='[3.8.100,3.8.101)';startlevel=3,\ + org.eclipse.ui.cheatsheets;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.ui.cocoa;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.ui.console;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui.editors;version='[3.17.100,3.17.101)';startlevel=3,\ + org.eclipse.ui.externaltools;version='[3.6.200,3.6.201)';startlevel=3,\ + org.eclipse.ui.forms;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui.genericeditor;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.ui.ide;version='[3.22.0,3.22.1)';startlevel=3,\ + org.eclipse.ui.ide.application;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.ui.intro;version='[3.7.200,3.7.201)';startlevel=3,\ + org.eclipse.ui.navigator;version='[3.12.200,3.12.201)';startlevel=3,\ + org.eclipse.ui.navigator.resources;version='[3.9.100,3.9.101)';startlevel=3,\ + org.eclipse.ui.views;version='[3.12.100,3.12.101)';startlevel=3,\ + org.eclipse.ui.views.log;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.ui.views.properties.tabbed;version='[3.10.100,3.10.101)';startlevel=3,\ + org.eclipse.ui.workbench;version='[3.131.0,3.131.1)';startlevel=3,\ + org.eclipse.ui.workbench.texteditor;version='[3.17.200,3.17.201)';startlevel=3,\ + org.eclipse.urischeme;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.wst.common.core;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.wst.common.emf;version='[1.2.700,1.2.701)';startlevel=3,\ + org.eclipse.wst.common.environment;version='[1.1.0,1.1.1)';startlevel=3,\ + org.eclipse.wst.common.frameworks;version='[1.3.0,1.3.1)';startlevel=3,\ + org.eclipse.wst.common.uriresolver;version='[1.4.0,1.4.1)';startlevel=3,\ + org.eclipse.wst.sse.core;version='[1.2.1200,1.2.1201)';startlevel=3,\ + org.eclipse.wst.xml.core;version='[1.2.800,1.2.801)';startlevel=3,\ + org.hamcrest;version='[2.2.0,2.2.1)';startlevel=3,\ + org.hamcrest.core;version='[2.2.0,2.2.1)';startlevel=3,\ + org.hamcrest.library;version='[1.3.0,1.3.1)';startlevel=3,\ + org.jdom2;version='[2.0.6,2.0.7)';startlevel=3,\ + org.junit;version='[4.13.2,4.13.3)';startlevel=3,\ + org.opentest4j;version='[1.3.0,1.3.1)';startlevel=3,\ + org.osgi.service.cm;version='[1.6.1,1.6.2)';startlevel=1,\ + org.osgi.service.component;version='[1.5.1,1.5.2)';startlevel=1,\ + org.osgi.service.coordinator;version='[1.0.2,1.0.3)';startlevel=1,\ + org.osgi.service.device;version='[1.1.1,1.1.2)';startlevel=1,\ + org.osgi.service.event;version='[1.4.1,1.4.2)';startlevel=1,\ + org.osgi.service.metatype;version='[1.4.1,1.4.2)';startlevel=1,\ + org.osgi.service.prefs;version='[1.1.2,1.1.3)';startlevel=1,\ + org.osgi.service.provisioning;version='[1.2.0,1.2.1)';startlevel=1,\ + org.osgi.service.repository;version='[1.1.0,1.1.1)';startlevel=1,\ + org.osgi.service.upnp;version='[1.2.1,1.2.2)';startlevel=1,\ + org.osgi.service.useradmin;version='[1.1.1,1.1.2)';startlevel=1,\ + org.osgi.service.wireadmin;version='[1.0.2,1.0.3)';startlevel=1,\ + org.osgi.util.function;version='[1.2.0,1.2.1)';startlevel=3,\ + org.osgi.util.measurement;version='[1.0.2,1.0.3)';startlevel=3,\ + org.osgi.util.position;version='[1.0.1,1.0.2)';startlevel=3,\ + org.osgi.util.promise;version='[1.3.0,1.3.1)';startlevel=3,\ + org.osgi.util.xml;version='[1.0.2,1.0.3)';startlevel=3,\ + org.sat4j.core;version='[2.3.6,2.3.7)';startlevel=3,\ + org.sat4j.pb;version='[2.3.6,2.3.7)';startlevel=3,\ + org.tukaani.xz;version='[1.9.0,1.9.1)';startlevel=3 \ No newline at end of file diff --git a/bndtools.core.test/test.swtbot.gtk.linux.x86_64.bndrun b/bndtools.core.test/test.swtbot.gtk.linux.x86_64.bndrun new file mode 100644 index 0000000000..34e7f56e34 --- /dev/null +++ b/bndtools.core.test/test.swtbot.gtk.linux.x86_64.bndrun @@ -0,0 +1,329 @@ +-include: test.swtbot.shared.bndrun + +-runrequires.linux: \ + bnd.identity;id='org.eclipse.e4.ui.swt.gtk',\ + bnd.identity;id='org.eclipse.swt.gtk.linux.x86_64',\ + bnd.identity;id='org.eclipse.core.filesystem.linux.x86_64',\ + bnd.identity;id='org.eclipse.core.net.linux',\ + bnd.identity;id='org.eclipse.equinox.security.linux' + +-runvm.linux: \ + -Dosgi.dataAreaRequiresExplicitInit=true, \ + -Dosgi.requiredJavaVersion=1.8, \ + -XX:+UseG1GC, \ + -XX:+UseStringDeduplication + +-runsystemcapabilities.linux: \ + osgi.native;osgi.native.osname:List="linux";osgi.native.processor:List="x86-64" + +-runproperties.linux: \ + osgi.ws=gtk,\ + osgi.arch=x86_64,\ + osgi.os=linux + +-runblacklist.linux: \ + osgi.identity;filter:='(osgi.identity=*macosx*)',\ + osgi.identity;filter:='(osgi.identity=*win32*)',\ + +-resolve: auto +-runbundles: \ + assertj-core;version='[3.24.2,3.24.3)';startlevel=3,\ + bcpg;version='[1.77.0,1.77.1)';startlevel=3,\ + bcpkix;version='[1.77.0,1.77.1)';startlevel=3,\ + bcprov;version='[1.77.0,1.77.1)';startlevel=3,\ + bcutil;version='[1.77.0,1.77.1)';startlevel=3,\ + biz.aQute.bnd.embedded-repo;version=snapshot;startlevel=3,\ + biz.aQute.bnd.util;version=snapshot;startlevel=3,\ + biz.aQute.bndlib;version=snapshot;startlevel=3,\ + biz.aQute.repository;version=snapshot;startlevel=3,\ + biz.aQute.resolve;version=snapshot;startlevel=3,\ + biz.aQute.tester.junit-platform;version=snapshot;startlevel=3,\ + bndtools.api;version=snapshot;startlevel=4,\ + bndtools.builder;version=snapshot;startlevel=4,\ + bndtools.core;version=snapshot;startlevel=4,\ + bndtools.core.services;version=snapshot;startlevel=1,\ + bndtools.core.test.launch.rendered;version=snapshot;startlevel=5,\ + bndtools.core.test.swtbot.tests;version=snapshot;startlevel=5,\ + com.google.gson;version='[2.10.1,2.10.2)';startlevel=3,\ + com.google.guava;version='[33.4.8,33.4.9)';startlevel=3,\ + com.google.guava.failureaccess;version='[1.0.3,1.0.4)';startlevel=3,\ + com.googlecode.javaewah.JavaEWAH;version='[1.2.3,1.2.4)';startlevel=3,\ + com.ibm.icu;version='[74.1.0,74.1.1)';startlevel=3,\ + com.sun.jna;version='[5.13.0,5.13.1)';startlevel=3,\ + com.sun.jna.platform;version='[5.13.0,5.13.1)';startlevel=3,\ + jakarta.annotation-api;version='[1.3.5,1.3.6)';startlevel=3,\ + jakarta.annotation-api;version='[2.1.1,2.1.2)';startlevel=3,\ + jakarta.inject.jakarta.inject-api;version='[1.0.5,1.0.6)';startlevel=3,\ + jakarta.inject.jakarta.inject-api;version='[2.0.1,2.0.2)';startlevel=3,\ + jaxen;version='[2.0.0,2.0.1)';startlevel=3,\ + junit-jupiter-api;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-jupiter-engine;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-jupiter-params;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-platform-commons;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-platform-engine;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-platform-launcher;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-vintage-engine;version='[5.10.1,5.10.2)';startlevel=3,\ + net.bytebuddy.byte-buddy;version='[1.14.9,1.14.10)';startlevel=3,\ + net.i2p.crypto.eddsa;version='[0.3.0,0.3.1)';startlevel=3,\ + org.apache.ant;version='[1.10.14,1.10.15)';startlevel=3,\ + org.apache.batik.constants;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.css;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.i18n;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.util;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.commons.cli;version='[1.6.0,1.6.1)';startlevel=3,\ + org.apache.commons.commons-codec;version='[1.16.0,1.16.1)';startlevel=3,\ + org.apache.commons.commons-io;version='[2.15.0,2.15.1)';startlevel=3,\ + org.apache.commons.jxpath;version='[1.3.0,1.3.1)';startlevel=3,\ + org.apache.commons.lang3;version='[3.13.0,3.13.1)';startlevel=3,\ + org.apache.commons.logging;version='[1.2.0,1.2.1)';startlevel=3,\ + org.apache.felix.scr;version='[2.2.6,2.2.7)';startlevel=3,\ + org.apache.httpcomponents.httpclient;version='[4.5.14,4.5.15)';startlevel=3,\ + org.apache.httpcomponents.httpcore;version='[4.4.16,4.4.17)';startlevel=3,\ + org.apache.lucene.analysis-common;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.lucene.analysis-smartcn;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.lucene.core;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.sshd.osgi;version='[2.11.0,2.11.1)';startlevel=3,\ + org.apache.sshd.sftp;version='[2.11.0,2.11.1)';startlevel=3,\ + org.apache.xerces;version='[2.12.2,2.12.3)';startlevel=3,\ + org.apache.xml.resolver;version='[1.2.0,1.2.1)';startlevel=3,\ + org.apache.xmlgraphics;version='[2.9.0,2.9.1)';startlevel=3,\ + org.bndtools.headless.build.manager;version=snapshot;startlevel=3,\ + org.bndtools.headless.build.plugin.ant;version=snapshot;startlevel=3,\ + org.bndtools.headless.build.plugin.gradle;version=snapshot;startlevel=3,\ + org.bndtools.remoteinstall;version=snapshot;startlevel=3,\ + org.bndtools.templates.template;version=snapshot;startlevel=3,\ + org.bndtools.templating;version=snapshot;startlevel=3,\ + org.bndtools.templating.gitrepo;version=snapshot;startlevel=3,\ + org.bndtools.versioncontrol.ignores.manager;version=snapshot;startlevel=3,\ + org.bndtools.versioncontrol.ignores.plugin.git;version=snapshot;startlevel=3,\ + org.eclipse.ant.core;version='[3.7.200,3.7.201)';startlevel=3,\ + org.eclipse.ant.launching;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.ant.ui;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.compare;version='[3.9.300,3.9.301)';startlevel=3,\ + org.eclipse.compare.core;version='[3.8.300,3.8.301)';startlevel=3,\ + org.eclipse.core.commands;version='[3.11.200,3.11.201)';startlevel=3,\ + org.eclipse.core.contenttype;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.core.databinding;version='[1.13.100,1.13.101)';startlevel=3,\ + org.eclipse.core.databinding.beans;version='[1.10.100,1.10.101)';startlevel=3,\ + org.eclipse.core.databinding.observable;version='[1.13.100,1.13.101)';startlevel=3,\ + org.eclipse.core.databinding.property;version='[1.10.100,1.10.101)';startlevel=3,\ + org.eclipse.core.expressions;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.core.externaltools;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.core.filebuffers;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.core.filesystem;version='[1.10.200,1.10.201)';startlevel=3,\ + org.eclipse.core.filesystem.linux.x86_64;version='[1.2.400,1.2.401)';startlevel=3,\ + org.eclipse.core.jobs;version='[3.15.100,3.15.101)';startlevel=3,\ + org.eclipse.core.net;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.core.net.linux;version='[1.1.0,1.1.1)';startlevel=3,\ + org.eclipse.core.resources;version='[3.20.0,3.20.1)';startlevel=3,\ + org.eclipse.core.runtime;version='[3.30.0,3.30.1)';startlevel=3,\ + org.eclipse.core.variables;version='[3.6.200,3.6.201)';startlevel=3,\ + org.eclipse.debug.core;version='[3.21.200,3.21.201)';startlevel=3,\ + org.eclipse.debug.ui;version='[3.18.200,3.18.201)';startlevel=3,\ + org.eclipse.e4.core.commands;version='[1.1.200,1.1.201)';startlevel=3,\ + org.eclipse.e4.core.contexts;version='[1.12.400,1.12.401)';startlevel=3,\ + org.eclipse.e4.core.di;version='[1.9.200,1.9.201)';startlevel=3,\ + org.eclipse.e4.core.di.annotations;version='[1.8.200,1.8.201)';startlevel=3,\ + org.eclipse.e4.core.di.extensions;version='[0.18.100,0.18.101)';startlevel=3,\ + org.eclipse.e4.core.di.extensions.supplier;version='[0.17.300,0.17.301)';startlevel=3,\ + org.eclipse.e4.core.services;version='[2.4.200,2.4.201)';startlevel=1,\ + org.eclipse.e4.emf.xpath;version='[0.4.100,0.4.101)';startlevel=3,\ + org.eclipse.e4.tools.services;version='[4.10.300,4.10.301)';startlevel=1,\ + org.eclipse.e4.ui.bindings;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.css.core;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.css.swt;version='[0.15.200,0.15.201)';startlevel=3,\ + org.eclipse.e4.ui.css.swt.theme;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.di;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.e4.ui.dialogs;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.e4.ui.ide;version='[3.17.100,3.17.101)';startlevel=3,\ + org.eclipse.e4.ui.model.workbench;version='[2.4.100,2.4.101)';startlevel=3,\ + org.eclipse.e4.ui.progress;version='[0.4.200,0.4.201)';startlevel=3,\ + org.eclipse.e4.ui.services;version='[1.6.200,1.6.201)';startlevel=1,\ + org.eclipse.e4.ui.swt.gtk;version='[1.2.100,1.2.101)';startlevel=3,\ + org.eclipse.e4.ui.widgets;version='[1.4.0,1.4.1)';startlevel=3,\ + org.eclipse.e4.ui.workbench;version='[1.15.200,1.15.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.addons.swt;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.renderers.swt;version='[0.16.200,0.16.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.swt;version='[0.17.200,0.17.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench3;version='[0.17.200,0.17.201)';startlevel=3,\ + org.eclipse.ecf;version='[3.11.0,3.11.1)';startlevel=3,\ + org.eclipse.ecf.filetransfer;version='[5.1.103,5.1.104)';startlevel=3,\ + org.eclipse.ecf.identity;version='[3.10.0,3.10.1)';startlevel=3,\ + org.eclipse.ecf.provider.filetransfer;version='[3.3.0,3.3.1)';startlevel=3,\ + org.eclipse.egit.core;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.egit.ui;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.emf.ant;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.emf.codegen;version='[2.23.0,2.23.1)';startlevel=3,\ + org.eclipse.emf.codegen.ecore;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.codegen.ecore.ui;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.codegen.ui;version='[2.24.0,2.24.1)';startlevel=3,\ + org.eclipse.emf.common;version='[2.29.0,2.29.1)';startlevel=3,\ + org.eclipse.emf.common.ui;version='[2.22.0,2.22.1)';startlevel=3,\ + org.eclipse.emf.converter;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.emf.databinding;version='[1.7.0,1.7.1)';startlevel=3,\ + org.eclipse.emf.databinding.edit;version='[1.8.0,1.8.1)';startlevel=3,\ + org.eclipse.emf.ecore;version='[2.35.0,2.35.1)';startlevel=3,\ + org.eclipse.emf.ecore.change;version='[2.15.0,2.15.1)';startlevel=3,\ + org.eclipse.emf.ecore.edit;version='[2.14.0,2.14.1)';startlevel=3,\ + org.eclipse.emf.ecore.xmi;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.edit;version='[2.20.0,2.20.1)';startlevel=3,\ + org.eclipse.emf.edit.ui;version='[2.23.0,2.23.1)';startlevel=3,\ + org.eclipse.emf.exporter;version='[2.11.0,2.11.1)';startlevel=3,\ + org.eclipse.emf.importer;version='[2.13.0,2.13.1)';startlevel=3,\ + org.eclipse.emf.importer.ecore;version='[2.11.0,2.11.1)';startlevel=3,\ + org.eclipse.emf.importer.java;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.equinox.app;version='[1.6.400,1.6.401)';startlevel=2,\ + org.eclipse.equinox.bidi;version='[1.4.400,1.4.401)';startlevel=3,\ + org.eclipse.equinox.common;version='[3.18.200,3.18.201)';startlevel=3,\ + org.eclipse.equinox.concurrent;version='[1.2.200,1.2.201)';startlevel=3,\ + org.eclipse.equinox.event;version='[1.6.300,1.6.301)';startlevel=3,\ + org.eclipse.equinox.frameworkadmin;version='[2.3.0,2.3.1)';startlevel=3,\ + org.eclipse.equinox.frameworkadmin.equinox;version='[1.3.0,1.3.1)';startlevel=3,\ + org.eclipse.equinox.p2.artifact.repository;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.core;version='[2.10.200,2.10.201)';startlevel=3,\ + org.eclipse.equinox.p2.director;version='[2.6.200,2.6.201)';startlevel=3,\ + org.eclipse.equinox.p2.directorywatcher;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.equinox.p2.discovery;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.discovery.compatibility;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.engine;version='[2.9.0,2.9.1)';startlevel=3,\ + org.eclipse.equinox.p2.extensionlocation;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.garbagecollector;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.jarprocessor;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.equinox.p2.metadata;version='[2.8.0,2.8.1)';startlevel=3,\ + org.eclipse.equinox.p2.metadata.repository;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.operations;version='[2.7.200,2.7.201)';startlevel=3,\ + org.eclipse.equinox.p2.publisher;version='[1.9.0,1.9.1)';startlevel=3,\ + org.eclipse.equinox.p2.publisher.eclipse;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.repository;version='[2.8.0,2.8.1)';startlevel=3,\ + org.eclipse.equinox.p2.repository.tools;version='[2.4.200,2.4.201)';startlevel=3,\ + org.eclipse.equinox.p2.touchpoint.eclipse;version='[2.4.100,2.4.101)';startlevel=3,\ + org.eclipse.equinox.p2.transport.ecf;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.p2.ui;version='[2.8.200,2.8.201)';startlevel=3,\ + org.eclipse.equinox.p2.ui.discovery;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.preferences;version='[3.10.400,3.10.401)';startlevel=3,\ + org.eclipse.equinox.registry;version='[3.11.400,3.11.401)';startlevel=3,\ + org.eclipse.equinox.security;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.security.linux;version='[1.1.100,1.1.101)';startlevel=3,\ + org.eclipse.equinox.security.ui;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.simpleconfigurator;version='[1.5.0,1.5.1)';startlevel=3,\ + org.eclipse.equinox.simpleconfigurator.manipulator;version='[2.3.0,2.3.1)';startlevel=3,\ + org.eclipse.help;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.help.base;version='[4.4.200,4.4.201)';startlevel=3,\ + org.eclipse.help.ui;version='[4.6.100,4.6.101)';startlevel=3,\ + org.eclipse.jdt.annotation;version='[2.2.800,2.2.801)';startlevel=3,\ + org.eclipse.jdt.core;version='[3.36.0,3.36.1)';startlevel=3,\ + org.eclipse.jdt.core.compiler.batch;version='[3.36.0,3.36.1)';startlevel=3,\ + org.eclipse.jdt.core.manipulation;version='[1.20.0,1.20.1)';startlevel=3,\ + org.eclipse.jdt.debug;version='[3.21.200,3.21.201)';startlevel=3,\ + org.eclipse.jdt.debug.ui;version='[3.13.200,3.13.201)';startlevel=3,\ + org.eclipse.jdt.junit;version='[3.16.200,3.16.201)';startlevel=3,\ + org.eclipse.jdt.junit.core;version='[3.13.0,3.13.1)';startlevel=3,\ + org.eclipse.jdt.junit.runtime;version='[3.7.300,3.7.301)';startlevel=3,\ + org.eclipse.jdt.launching;version='[3.21.0,3.21.1)';startlevel=3,\ + org.eclipse.jdt.ui;version='[3.31.0,3.31.1)';startlevel=3,\ + org.eclipse.jface;version='[3.32.0,3.32.1)';startlevel=3,\ + org.eclipse.jface.databinding;version='[1.15.100,1.15.101)';startlevel=3,\ + org.eclipse.jface.text;version='[3.24.200,3.24.201)';startlevel=3,\ + org.eclipse.jgit;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.gpg.bc;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.http.apache;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.ssh.apache;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.ltk.core.refactoring;version='[3.14.200,3.14.201)';startlevel=3,\ + org.eclipse.ltk.ui.refactoring;version='[3.13.200,3.13.201)';startlevel=3,\ + org.eclipse.m2e.archetype.catalog;version='[3.2.1,3.2.2)';startlevel=3,\ + org.eclipse.m2e.archetype.common;version='[3.2.104,3.2.105)';startlevel=3,\ + org.eclipse.m2e.archetype.descriptor;version='[3.2.1,3.2.2)';startlevel=3,\ + org.eclipse.m2e.archetype.maven-artifact-transfer;version='[0.13.1,0.13.2)';startlevel=3,\ + org.eclipse.m2e.binaryproject;version='[2.1.202,2.1.203)';startlevel=3,\ + org.eclipse.m2e.binaryproject.ui;version='[2.0.200,2.0.201)';startlevel=3,\ + org.eclipse.m2e.core;version='[2.4.1,2.4.2)';startlevel=3,\ + org.eclipse.m2e.core.ui;version='[2.0.7,2.0.8)';startlevel=3,\ + org.eclipse.m2e.discovery;version='[2.0.201,2.0.202)';startlevel=3,\ + org.eclipse.m2e.editor;version='[2.0.302,2.0.303)';startlevel=3,\ + org.eclipse.m2e.jdt;version='[2.3.3,2.3.4)';startlevel=3,\ + org.eclipse.m2e.jdt.ui;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.maven.runtime;version='[3.9.500,3.9.501)';startlevel=3,\ + org.eclipse.m2e.model.edit;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.profiles.core;version='[2.1.201,2.1.202)';startlevel=3,\ + org.eclipse.m2e.profiles.ui;version='[2.0.301,2.0.302)';startlevel=3,\ + org.eclipse.m2e.refactoring;version='[2.0.302,2.0.303)';startlevel=3,\ + org.eclipse.m2e.scm;version='[2.0.201,2.0.202)';startlevel=3,\ + org.eclipse.m2e.sourcelookup;version='[2.0.301,2.0.302)';startlevel=3,\ + org.eclipse.m2e.sourcelookup.ui;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.workspace.cli;version='[0.3.1,0.3.2)';startlevel=3,\ + org.eclipse.orbit.xml-apis-ext;version='[1.0.0,1.0.1)';startlevel=3,\ + org.eclipse.osgi.compatibility.state;version='[1.2.900,1.2.901)';startlevel=3,\ + org.eclipse.osgi.services;version='[3.11.200,3.11.201)';startlevel=1,\ + org.eclipse.osgi.util;version='[3.7.300,3.7.301)';startlevel=3,\ + org.eclipse.pde.build;version='[3.12.200,3.12.201)';startlevel=3,\ + org.eclipse.pde.core;version='[3.17.200,3.17.201)';startlevel=3,\ + org.eclipse.pde.genericeditor.extension;version='[1.2.200,1.2.201)';startlevel=3,\ + org.eclipse.pde.launching;version='[3.11.200,3.11.201)';startlevel=3,\ + org.eclipse.pde.runtime;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.pde.ua.core;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.pde.ua.ui;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.pde.ui;version='[3.15.0,3.15.1)';startlevel=3,\ + org.eclipse.platform;version='[4.30.0,4.30.1)';startlevel=3,\ + org.eclipse.sdk;version='[4.30.0,4.30.1)';startlevel=3,\ + org.eclipse.search;version='[3.16.0,3.16.1)';startlevel=3,\ + org.eclipse.search.core;version='[3.16.0,3.16.1)';startlevel=3,\ + org.eclipse.swt;version='[3.126.0,3.126.1)';startlevel=3,\ + org.eclipse.swt.gtk.linux.aarch64;version='[3.124.200,3.124.201)';startlevel=3,\ + org.eclipse.swt.gtk.linux.x86_64;version='[3.124.200,3.124.201)';startlevel=3,\ + org.eclipse.swtbot.eclipse.finder;version='[4.1.0,4.1.1)';startlevel=3,\ + org.eclipse.swtbot.swt.finder;version='[4.1.0,4.1.1)';startlevel=3,\ + org.eclipse.team.core;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.team.ui;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.text;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui;version='[3.205.0,3.205.1)';startlevel=3,\ + org.eclipse.ui.browser;version='[3.8.100,3.8.101)';startlevel=3,\ + org.eclipse.ui.cheatsheets;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.ui.console;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui.editors;version='[3.17.100,3.17.101)';startlevel=3,\ + org.eclipse.ui.externaltools;version='[3.6.200,3.6.201)';startlevel=3,\ + org.eclipse.ui.forms;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui.genericeditor;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.ui.ide;version='[3.22.0,3.22.1)';startlevel=3,\ + org.eclipse.ui.ide.application;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.ui.intro;version='[3.7.200,3.7.201)';startlevel=3,\ + org.eclipse.ui.navigator;version='[3.12.200,3.12.201)';startlevel=3,\ + org.eclipse.ui.navigator.resources;version='[3.9.100,3.9.101)';startlevel=3,\ + org.eclipse.ui.views;version='[3.12.100,3.12.101)';startlevel=3,\ + org.eclipse.ui.views.log;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.ui.views.properties.tabbed;version='[3.10.100,3.10.101)';startlevel=3,\ + org.eclipse.ui.workbench;version='[3.131.0,3.131.1)';startlevel=3,\ + org.eclipse.ui.workbench.texteditor;version='[3.17.200,3.17.201)';startlevel=3,\ + org.eclipse.urischeme;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.wst.common.core;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.wst.common.emf;version='[1.2.700,1.2.701)';startlevel=3,\ + org.eclipse.wst.common.environment;version='[1.1.0,1.1.1)';startlevel=3,\ + org.eclipse.wst.common.frameworks;version='[1.3.0,1.3.1)';startlevel=3,\ + org.eclipse.wst.common.uriresolver;version='[1.4.0,1.4.1)';startlevel=3,\ + org.eclipse.wst.sse.core;version='[1.2.1200,1.2.1201)';startlevel=3,\ + org.eclipse.wst.xml.core;version='[1.2.800,1.2.801)';startlevel=3,\ + org.hamcrest;version='[2.2.0,2.2.1)';startlevel=3,\ + org.hamcrest.core;version='[2.2.0,2.2.1)';startlevel=3,\ + org.hamcrest.library;version='[1.3.0,1.3.1)';startlevel=3,\ + org.jdom2;version='[2.0.6,2.0.7)';startlevel=3,\ + org.junit;version='[4.13.2,4.13.3)';startlevel=3,\ + org.opentest4j;version='[1.3.0,1.3.1)';startlevel=3,\ + org.osgi.service.cm;version='[1.6.1,1.6.2)';startlevel=1,\ + org.osgi.service.component;version='[1.5.1,1.5.2)';startlevel=1,\ + org.osgi.service.coordinator;version='[1.0.2,1.0.3)';startlevel=1,\ + org.osgi.service.device;version='[1.1.1,1.1.2)';startlevel=1,\ + org.osgi.service.event;version='[1.4.1,1.4.2)';startlevel=1,\ + org.osgi.service.metatype;version='[1.4.1,1.4.2)';startlevel=1,\ + org.osgi.service.prefs;version='[1.1.2,1.1.3)';startlevel=1,\ + org.osgi.service.provisioning;version='[1.2.0,1.2.1)';startlevel=1,\ + org.osgi.service.repository;version='[1.1.0,1.1.1)';startlevel=1,\ + org.osgi.service.upnp;version='[1.2.1,1.2.2)';startlevel=1,\ + org.osgi.service.useradmin;version='[1.1.1,1.1.2)';startlevel=1,\ + org.osgi.service.wireadmin;version='[1.0.2,1.0.3)';startlevel=1,\ + org.osgi.util.function;version='[1.2.0,1.2.1)';startlevel=3,\ + org.osgi.util.measurement;version='[1.0.2,1.0.3)';startlevel=3,\ + org.osgi.util.position;version='[1.0.1,1.0.2)';startlevel=3,\ + org.osgi.util.promise;version='[1.3.0,1.3.1)';startlevel=3,\ + org.osgi.util.xml;version='[1.0.2,1.0.3)';startlevel=3,\ + org.sat4j.core;version='[2.3.6,2.3.7)';startlevel=3,\ + org.sat4j.pb;version='[2.3.6,2.3.7)';startlevel=3,\ + org.tukaani.xz;version='[1.9.0,1.9.1)';startlevel=3 \ No newline at end of file diff --git a/bndtools.core.test/test.swtbot.shared.bndrun b/bndtools.core.test/test.swtbot.shared.bndrun new file mode 100644 index 0000000000..a3318036ac --- /dev/null +++ b/bndtools.core.test/test.swtbot.shared.bndrun @@ -0,0 +1,89 @@ +# Shared bndrun for the SWTBot UI tests. Unlike test.shared.bndrun this +# variant launches a RENDERED workbench: it swaps the headless launch bundle +# (bndtools.core.test.launch, which installs a NullContextPresentationEngine) +# for bndtools.core.test.launch.rendered and runs the SWTBot test bundle +# (bndtools.core.test.swtbot.tests) instead of the headless test bundle. +# +# The full -runrequires list of test.shared.bndrun is repeated here (a +# property cannot be partially overridden) with the following delta: +# - bndtools.core.test.launch -> bndtools.core.test.launch.rendered +# - bndtools.core.test.tests -> bndtools.core.test.swtbot.tests +# + org.eclipse.swtbot.swt.finder / org.eclipse.swtbot.eclipse.finder +# +# Note the ~ prefix: without it the -runrequires of the included file would +# override the declaration in this file. + +-include: ~test.shared.bndrun + +-runrequires: \ + bnd.identity;id='biz.aQute.bnd.embedded-repo',\ + bnd.identity;id='biz.aQute.tester.junit-platform',\ + bnd.identity;id='bndtools.api',\ + bnd.identity;id='bndtools.builder',\ + bnd.identity;id='bndtools.core',\ + bnd.identity;id='bndtools.core.services',\ + bnd.identity;id='bndtools.core.test.launch.rendered',\ + bnd.identity;id='bndtools.core.test.swtbot.tests',\ + bnd.identity;id='org.eclipse.swtbot.swt.finder',\ + bnd.identity;id='org.eclipse.swtbot.eclipse.finder',\ + bnd.identity;id='com.google.guava';version='[33.4.8,34.0.0)',\ + bnd.identity;id='com.google.guava.failureaccess',\ + bnd.identity;id='org.apache.ant',\ + bnd.identity;id='org.bndtools.headless.build.manager',\ + bnd.identity;id='org.bndtools.headless.build.plugin.ant',\ + bnd.identity;id='org.bndtools.headless.build.plugin.gradle',\ + bnd.identity;id='org.bndtools.remoteinstall',\ + bnd.identity;id='org.bndtools.templates.template',\ + bnd.identity;id='org.bndtools.templating',\ + bnd.identity;id='org.bndtools.templating.gitrepo',\ + bnd.identity;id='org.bndtools.versioncontrol.ignores.manager',\ + bnd.identity;id='org.bndtools.versioncontrol.ignores.plugin.git',\ + bnd.identity;id='org.eclipse.ant.core',\ + bnd.identity;id='org.eclipse.ant.ui',\ + bnd.identity;id='org.eclipse.emf.ant',\ + bnd.identity;id='org.eclipse.emf.codegen.ecore.ui',\ + bnd.identity;id='org.eclipse.emf.codegen.ui',\ + bnd.identity;id='org.eclipse.emf.common.ui',\ + bnd.identity;id='org.eclipse.emf.databinding',\ + bnd.identity;id='org.eclipse.emf.databinding.edit',\ + bnd.identity;id='org.eclipse.emf.edit.ui',\ + bnd.identity;id='org.eclipse.emf.exporter',\ + bnd.identity;id='org.eclipse.emf.importer',\ + bnd.identity;id='org.eclipse.emf.importer.ecore',\ + bnd.identity;id='org.eclipse.emf.importer.java',\ + bnd.identity;id='org.eclipse.core.databinding.beans',\ + bnd.identity;id='org.eclipse.core.filesystem',\ + bnd.identity;id='org.eclipse.e4.tools.services',\ + bnd.identity;id='org.eclipse.egit.ui',\ + bnd.identity;id='org.eclipse.equinox.event',\ + bnd.identity;id='org.eclipse.jdt.annotation',\ + bnd.identity;id='org.eclipse.m2e.binaryproject.ui',\ + bnd.identity;id='org.eclipse.m2e.discovery',\ + bnd.identity;id='org.eclipse.m2e.editor',\ + bnd.identity;id='org.eclipse.m2e.jdt.ui',\ + bnd.identity;id='org.eclipse.m2e.profiles.core',\ + bnd.identity;id='org.eclipse.m2e.profiles.ui',\ + bnd.identity;id='org.eclipse.m2e.refactoring',\ + bnd.identity;id='org.eclipse.m2e.scm',\ + bnd.identity;id='org.eclipse.m2e.sourcelookup.ui',\ + bnd.identity;id='org.eclipse.osgi.compatibility.state',\ + bnd.identity;id='org.eclipse.pde.build',\ + bnd.identity;id='org.eclipse.pde.core',\ + bnd.identity;id='org.eclipse.pde.genericeditor.extension',\ + bnd.identity;id='org.eclipse.pde.launching',\ + bnd.identity;id='org.eclipse.pde.runtime',\ + bnd.identity;id='org.eclipse.pde.ua.core',\ + bnd.identity;id='org.eclipse.pde.ua.ui',\ + bnd.identity;id='org.eclipse.pde.ui',\ + bnd.identity;id='org.eclipse.sdk',\ + bnd.identity;id='org.eclipse.ui.browser',\ + bnd.identity;id='org.eclipse.ui.console',\ + bnd.identity;id='org.eclipse.ui.ide',\ + bnd.identity;id='org.eclipse.ui.ide.application',\ + bnd.identity;id='junit-jupiter-api';version='${range;[===,==+);${junit.jupiter.eclipse.version}}',\ + bnd.identity;id='junit-jupiter-engine';version='${range;[===,==+);${junit.jupiter.eclipse.version}}',\ + bnd.identity;id='junit-jupiter-params';version='${range;[===,==+);${junit.jupiter.eclipse.version}}',\ + bnd.identity;id='junit-platform-commons';version='${range;[===,==+);${junit.platform.eclipse.version}}',\ + bnd.identity;id='junit-platform-engine';version='${range;[===,==+);${junit.platform.eclipse.version}}',\ + bnd.identity;id='junit-platform-launcher';version='${range;[===,==+);${junit.platform.eclipse.version}}',\ + bnd.identity;id='junit-vintage-engine';version='${range;[===,==+);${junit.jupiter.eclipse.version}}' diff --git a/bndtools.core.test/test.swtbot.win32.x86_64.bndrun b/bndtools.core.test/test.swtbot.win32.x86_64.bndrun new file mode 100644 index 0000000000..4a08ed9c7e --- /dev/null +++ b/bndtools.core.test/test.swtbot.win32.x86_64.bndrun @@ -0,0 +1,330 @@ +-include: test.swtbot.shared.bndrun + +-runrequires.win32: \ + bnd.identity;id='org.eclipse.compare.win32',\ + bnd.identity;id='org.eclipse.core.filesystem.win32.x86_64',\ + bnd.identity;id='org.eclipse.core.net.win32.x86_64',\ + bnd.identity;id='org.eclipse.core.resources.win32.x86_64',\ + bnd.identity;id='org.eclipse.e4.ui.swt.win32',\ + bnd.identity;id='org.eclipse.equinox.security.win32.x86_64',\ + bnd.identity;id='org.eclipse.swt.win32.win32.x86_64',\ + bnd.identity;id='org.eclipse.ui.win32' + +-runvm.win32: + +-runsystemcapabilities.win32: \ + osgi.native;osgi.native.osname:List="win32";osgi.native.processor:List="x86-64" + +-runproperties.win32: \ + osgi.ws=win32,\ + osgi.arch=x86_64,\ + osgi.os=win32 + +-runblacklist.win32: \ + osgi.identity;filter:='(osgi.identity=*macosx*)',\ + osgi.identity;filter:='(osgi.identity=*linux*)',\ + +-resolve: auto +-runbundles: \ + assertj-core;version='[3.24.2,3.24.3)';startlevel=3,\ + bcpg;version='[1.77.0,1.77.1)';startlevel=3,\ + bcpkix;version='[1.77.0,1.77.1)';startlevel=3,\ + bcprov;version='[1.77.0,1.77.1)';startlevel=3,\ + bcutil;version='[1.77.0,1.77.1)';startlevel=3,\ + biz.aQute.bnd.embedded-repo;version=snapshot;startlevel=3,\ + biz.aQute.bnd.util;version=snapshot;startlevel=3,\ + biz.aQute.bndlib;version=snapshot;startlevel=3,\ + biz.aQute.repository;version=snapshot;startlevel=3,\ + biz.aQute.resolve;version=snapshot;startlevel=3,\ + biz.aQute.tester.junit-platform;version=snapshot;startlevel=3,\ + bndtools.api;version=snapshot;startlevel=4,\ + bndtools.builder;version=snapshot;startlevel=4,\ + bndtools.core;version=snapshot;startlevel=4,\ + bndtools.core.services;version=snapshot;startlevel=1,\ + bndtools.core.test.launch.rendered;version=snapshot;startlevel=5,\ + bndtools.core.test.swtbot.tests;version=snapshot;startlevel=5,\ + com.google.gson;version='[2.10.1,2.10.2)';startlevel=3,\ + com.google.guava;version='[33.4.8,33.4.9)';startlevel=3,\ + com.google.guava.failureaccess;version='[1.0.3,1.0.4)';startlevel=3,\ + com.googlecode.javaewah.JavaEWAH;version='[1.2.3,1.2.4)';startlevel=3,\ + com.ibm.icu;version='[74.1.0,74.1.1)';startlevel=3,\ + com.sun.jna;version='[5.13.0,5.13.1)';startlevel=3,\ + com.sun.jna.platform;version='[5.13.0,5.13.1)';startlevel=3,\ + jakarta.annotation-api;version='[1.3.5,1.3.6)';startlevel=3,\ + jakarta.annotation-api;version='[2.1.1,2.1.2)';startlevel=3,\ + jakarta.inject.jakarta.inject-api;version='[1.0.5,1.0.6)';startlevel=3,\ + jakarta.inject.jakarta.inject-api;version='[2.0.1,2.0.2)';startlevel=3,\ + jaxen;version='[2.0.0,2.0.1)';startlevel=3,\ + junit-jupiter-api;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-jupiter-engine;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-jupiter-params;version='[5.10.1,5.10.2)';startlevel=3,\ + junit-platform-commons;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-platform-engine;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-platform-launcher;version='[1.10.1,1.10.2)';startlevel=3,\ + junit-vintage-engine;version='[5.10.1,5.10.2)';startlevel=3,\ + net.bytebuddy.byte-buddy;version='[1.14.9,1.14.10)';startlevel=3,\ + net.i2p.crypto.eddsa;version='[0.3.0,0.3.1)';startlevel=3,\ + org.apache.ant;version='[1.10.14,1.10.15)';startlevel=3,\ + org.apache.batik.constants;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.css;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.i18n;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.batik.util;version='[1.17.0,1.17.1)';startlevel=3,\ + org.apache.commons.cli;version='[1.6.0,1.6.1)';startlevel=3,\ + org.apache.commons.commons-codec;version='[1.16.0,1.16.1)';startlevel=3,\ + org.apache.commons.commons-io;version='[2.15.0,2.15.1)';startlevel=3,\ + org.apache.commons.jxpath;version='[1.3.0,1.3.1)';startlevel=3,\ + org.apache.commons.lang3;version='[3.13.0,3.13.1)';startlevel=3,\ + org.apache.commons.logging;version='[1.2.0,1.2.1)';startlevel=3,\ + org.apache.felix.scr;version='[2.2.6,2.2.7)';startlevel=3,\ + org.apache.httpcomponents.httpclient;version='[4.5.14,4.5.15)';startlevel=3,\ + org.apache.httpcomponents.httpcore;version='[4.4.16,4.4.17)';startlevel=3,\ + org.apache.lucene.analysis-common;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.lucene.analysis-smartcn;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.lucene.core;version='[9.8.0,9.8.1)';startlevel=3,\ + org.apache.sshd.osgi;version='[2.11.0,2.11.1)';startlevel=3,\ + org.apache.sshd.sftp;version='[2.11.0,2.11.1)';startlevel=3,\ + org.apache.xerces;version='[2.12.2,2.12.3)';startlevel=3,\ + org.apache.xml.resolver;version='[1.2.0,1.2.1)';startlevel=3,\ + org.apache.xmlgraphics;version='[2.9.0,2.9.1)';startlevel=3,\ + org.bndtools.headless.build.manager;version=snapshot;startlevel=3,\ + org.bndtools.headless.build.plugin.ant;version=snapshot;startlevel=3,\ + org.bndtools.headless.build.plugin.gradle;version=snapshot;startlevel=3,\ + org.bndtools.remoteinstall;version=snapshot;startlevel=3,\ + org.bndtools.templates.template;version=snapshot;startlevel=3,\ + org.bndtools.templating;version=snapshot;startlevel=3,\ + org.bndtools.templating.gitrepo;version=snapshot;startlevel=3,\ + org.bndtools.versioncontrol.ignores.manager;version=snapshot;startlevel=3,\ + org.bndtools.versioncontrol.ignores.plugin.git;version=snapshot;startlevel=3,\ + org.eclipse.ant.core;version='[3.7.200,3.7.201)';startlevel=3,\ + org.eclipse.ant.launching;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.ant.ui;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.compare;version='[3.9.300,3.9.301)';startlevel=3,\ + org.eclipse.compare.core;version='[3.8.300,3.8.301)';startlevel=3,\ + org.eclipse.compare.win32;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.core.commands;version='[3.11.200,3.11.201)';startlevel=3,\ + org.eclipse.core.contenttype;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.core.databinding;version='[1.13.100,1.13.101)';startlevel=3,\ + org.eclipse.core.databinding.beans;version='[1.10.100,1.10.101)';startlevel=3,\ + org.eclipse.core.databinding.observable;version='[1.13.100,1.13.101)';startlevel=3,\ + org.eclipse.core.databinding.property;version='[1.10.100,1.10.101)';startlevel=3,\ + org.eclipse.core.expressions;version='[3.9.200,3.9.201)';startlevel=3,\ + org.eclipse.core.externaltools;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.core.filebuffers;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.core.filesystem;version='[1.10.200,1.10.201)';startlevel=3,\ + org.eclipse.core.filesystem.win32.x86_64;version='[1.4.300,1.4.301)';startlevel=3,\ + org.eclipse.core.jobs;version='[3.15.100,3.15.101)';startlevel=3,\ + org.eclipse.core.net;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.core.net.win32.x86_64;version='[1.2.100,1.2.101)';startlevel=3,\ + org.eclipse.core.resources;version='[3.20.0,3.20.1)';startlevel=3,\ + org.eclipse.core.resources.win32.x86_64;version='[3.5.500,3.5.501)';startlevel=3,\ + org.eclipse.core.runtime;version='[3.30.0,3.30.1)';startlevel=3,\ + org.eclipse.core.variables;version='[3.6.200,3.6.201)';startlevel=3,\ + org.eclipse.debug.core;version='[3.21.200,3.21.201)';startlevel=3,\ + org.eclipse.debug.ui;version='[3.18.200,3.18.201)';startlevel=3,\ + org.eclipse.e4.core.commands;version='[1.1.200,1.1.201)';startlevel=3,\ + org.eclipse.e4.core.contexts;version='[1.12.400,1.12.401)';startlevel=3,\ + org.eclipse.e4.core.di;version='[1.9.200,1.9.201)';startlevel=3,\ + org.eclipse.e4.core.di.annotations;version='[1.8.200,1.8.201)';startlevel=3,\ + org.eclipse.e4.core.di.extensions;version='[0.18.100,0.18.101)';startlevel=3,\ + org.eclipse.e4.core.di.extensions.supplier;version='[0.17.300,0.17.301)';startlevel=3,\ + org.eclipse.e4.core.services;version='[2.4.200,2.4.201)';startlevel=1,\ + org.eclipse.e4.emf.xpath;version='[0.4.100,0.4.101)';startlevel=3,\ + org.eclipse.e4.tools.services;version='[4.10.300,4.10.301)';startlevel=1,\ + org.eclipse.e4.ui.bindings;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.css.core;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.css.swt;version='[0.15.200,0.15.201)';startlevel=3,\ + org.eclipse.e4.ui.css.swt.theme;version='[0.14.200,0.14.201)';startlevel=3,\ + org.eclipse.e4.ui.di;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.e4.ui.dialogs;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.e4.ui.ide;version='[3.17.100,3.17.101)';startlevel=3,\ + org.eclipse.e4.ui.model.workbench;version='[2.4.100,2.4.101)';startlevel=3,\ + org.eclipse.e4.ui.progress;version='[0.4.200,0.4.201)';startlevel=3,\ + org.eclipse.e4.ui.services;version='[1.6.200,1.6.201)';startlevel=1,\ + org.eclipse.e4.ui.swt.win32;version='[1.2.100,1.2.101)';startlevel=3,\ + org.eclipse.e4.ui.widgets;version='[1.4.0,1.4.1)';startlevel=3,\ + org.eclipse.e4.ui.workbench;version='[1.15.200,1.15.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.addons.swt;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.renderers.swt;version='[0.16.200,0.16.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench.swt;version='[0.17.200,0.17.201)';startlevel=3,\ + org.eclipse.e4.ui.workbench3;version='[0.17.200,0.17.201)';startlevel=3,\ + org.eclipse.ecf;version='[3.11.0,3.11.1)';startlevel=3,\ + org.eclipse.ecf.filetransfer;version='[5.1.103,5.1.104)';startlevel=3,\ + org.eclipse.ecf.identity;version='[3.10.0,3.10.1)';startlevel=3,\ + org.eclipse.ecf.provider.filetransfer;version='[3.3.0,3.3.1)';startlevel=3,\ + org.eclipse.egit.core;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.egit.ui;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.emf.ant;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.emf.codegen;version='[2.23.0,2.23.1)';startlevel=3,\ + org.eclipse.emf.codegen.ecore;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.codegen.ecore.ui;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.codegen.ui;version='[2.24.0,2.24.1)';startlevel=3,\ + org.eclipse.emf.common;version='[2.29.0,2.29.1)';startlevel=3,\ + org.eclipse.emf.common.ui;version='[2.22.0,2.22.1)';startlevel=3,\ + org.eclipse.emf.converter;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.emf.databinding;version='[1.7.0,1.7.1)';startlevel=3,\ + org.eclipse.emf.databinding.edit;version='[1.8.0,1.8.1)';startlevel=3,\ + org.eclipse.emf.ecore;version='[2.35.0,2.35.1)';startlevel=3,\ + org.eclipse.emf.ecore.change;version='[2.15.0,2.15.1)';startlevel=3,\ + org.eclipse.emf.ecore.edit;version='[2.14.0,2.14.1)';startlevel=3,\ + org.eclipse.emf.ecore.xmi;version='[2.36.0,2.36.1)';startlevel=3,\ + org.eclipse.emf.edit;version='[2.20.0,2.20.1)';startlevel=3,\ + org.eclipse.emf.edit.ui;version='[2.23.0,2.23.1)';startlevel=3,\ + org.eclipse.emf.exporter;version='[2.11.0,2.11.1)';startlevel=3,\ + org.eclipse.emf.importer;version='[2.13.0,2.13.1)';startlevel=3,\ + org.eclipse.emf.importer.ecore;version='[2.11.0,2.11.1)';startlevel=3,\ + org.eclipse.emf.importer.java;version='[2.12.0,2.12.1)';startlevel=3,\ + org.eclipse.equinox.app;version='[1.6.400,1.6.401)';startlevel=2,\ + org.eclipse.equinox.bidi;version='[1.4.400,1.4.401)';startlevel=3,\ + org.eclipse.equinox.common;version='[3.18.200,3.18.201)';startlevel=3,\ + org.eclipse.equinox.concurrent;version='[1.2.200,1.2.201)';startlevel=3,\ + org.eclipse.equinox.event;version='[1.6.300,1.6.301)';startlevel=3,\ + org.eclipse.equinox.frameworkadmin;version='[2.3.0,2.3.1)';startlevel=3,\ + org.eclipse.equinox.frameworkadmin.equinox;version='[1.3.0,1.3.1)';startlevel=3,\ + org.eclipse.equinox.p2.artifact.repository;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.core;version='[2.10.200,2.10.201)';startlevel=3,\ + org.eclipse.equinox.p2.director;version='[2.6.200,2.6.201)';startlevel=3,\ + org.eclipse.equinox.p2.directorywatcher;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.equinox.p2.discovery;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.discovery.compatibility;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.engine;version='[2.9.0,2.9.1)';startlevel=3,\ + org.eclipse.equinox.p2.extensionlocation;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.garbagecollector;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.p2.jarprocessor;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.equinox.p2.metadata;version='[2.8.0,2.8.1)';startlevel=3,\ + org.eclipse.equinox.p2.metadata.repository;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.operations;version='[2.7.200,2.7.201)';startlevel=3,\ + org.eclipse.equinox.p2.publisher;version='[1.9.0,1.9.1)';startlevel=3,\ + org.eclipse.equinox.p2.publisher.eclipse;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.equinox.p2.repository;version='[2.8.0,2.8.1)';startlevel=3,\ + org.eclipse.equinox.p2.repository.tools;version='[2.4.200,2.4.201)';startlevel=3,\ + org.eclipse.equinox.p2.touchpoint.eclipse;version='[2.4.100,2.4.101)';startlevel=3,\ + org.eclipse.equinox.p2.transport.ecf;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.p2.ui;version='[2.8.200,2.8.201)';startlevel=3,\ + org.eclipse.equinox.p2.ui.discovery;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.equinox.preferences;version='[3.10.400,3.10.401)';startlevel=3,\ + org.eclipse.equinox.registry;version='[3.11.400,3.11.401)';startlevel=3,\ + org.eclipse.equinox.security;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.security.ui;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.equinox.security.win32.x86_64;version='[1.2.100,1.2.101)';startlevel=3,\ + org.eclipse.equinox.simpleconfigurator;version='[1.5.0,1.5.1)';startlevel=3,\ + org.eclipse.equinox.simpleconfigurator.manipulator;version='[2.3.0,2.3.1)';startlevel=3,\ + org.eclipse.help;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.help.base;version='[4.4.200,4.4.201)';startlevel=3,\ + org.eclipse.help.ui;version='[4.6.100,4.6.101)';startlevel=3,\ + org.eclipse.jdt.annotation;version='[2.2.800,2.2.801)';startlevel=3,\ + org.eclipse.jdt.core;version='[3.36.0,3.36.1)';startlevel=3,\ + org.eclipse.jdt.core.compiler.batch;version='[3.36.0,3.36.1)';startlevel=3,\ + org.eclipse.jdt.core.manipulation;version='[1.20.0,1.20.1)';startlevel=3,\ + org.eclipse.jdt.debug;version='[3.21.200,3.21.201)';startlevel=3,\ + org.eclipse.jdt.debug.ui;version='[3.13.200,3.13.201)';startlevel=3,\ + org.eclipse.jdt.junit;version='[3.16.200,3.16.201)';startlevel=3,\ + org.eclipse.jdt.junit.core;version='[3.13.0,3.13.1)';startlevel=3,\ + org.eclipse.jdt.junit.runtime;version='[3.7.300,3.7.301)';startlevel=3,\ + org.eclipse.jdt.launching;version='[3.21.0,3.21.1)';startlevel=3,\ + org.eclipse.jdt.ui;version='[3.31.0,3.31.1)';startlevel=3,\ + org.eclipse.jface;version='[3.32.0,3.32.1)';startlevel=3,\ + org.eclipse.jface.databinding;version='[1.15.100,1.15.101)';startlevel=3,\ + org.eclipse.jface.text;version='[3.24.200,3.24.201)';startlevel=3,\ + org.eclipse.jgit;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.gpg.bc;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.http.apache;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.jgit.ssh.apache;version='[6.8.0,6.8.1)';startlevel=3,\ + org.eclipse.ltk.core.refactoring;version='[3.14.200,3.14.201)';startlevel=3,\ + org.eclipse.ltk.ui.refactoring;version='[3.13.200,3.13.201)';startlevel=3,\ + org.eclipse.m2e.archetype.catalog;version='[3.2.1,3.2.2)';startlevel=3,\ + org.eclipse.m2e.archetype.common;version='[3.2.104,3.2.105)';startlevel=3,\ + org.eclipse.m2e.archetype.descriptor;version='[3.2.1,3.2.2)';startlevel=3,\ + org.eclipse.m2e.archetype.maven-artifact-transfer;version='[0.13.1,0.13.2)';startlevel=3,\ + org.eclipse.m2e.binaryproject;version='[2.1.202,2.1.203)';startlevel=3,\ + org.eclipse.m2e.binaryproject.ui;version='[2.0.200,2.0.201)';startlevel=3,\ + org.eclipse.m2e.core;version='[2.4.1,2.4.2)';startlevel=3,\ + org.eclipse.m2e.core.ui;version='[2.0.7,2.0.8)';startlevel=3,\ + org.eclipse.m2e.discovery;version='[2.0.201,2.0.202)';startlevel=3,\ + org.eclipse.m2e.editor;version='[2.0.302,2.0.303)';startlevel=3,\ + org.eclipse.m2e.jdt;version='[2.3.3,2.3.4)';startlevel=3,\ + org.eclipse.m2e.jdt.ui;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.maven.runtime;version='[3.9.500,3.9.501)';startlevel=3,\ + org.eclipse.m2e.model.edit;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.profiles.core;version='[2.1.201,2.1.202)';startlevel=3,\ + org.eclipse.m2e.profiles.ui;version='[2.0.301,2.0.302)';startlevel=3,\ + org.eclipse.m2e.refactoring;version='[2.0.302,2.0.303)';startlevel=3,\ + org.eclipse.m2e.scm;version='[2.0.201,2.0.202)';startlevel=3,\ + org.eclipse.m2e.sourcelookup;version='[2.0.301,2.0.302)';startlevel=3,\ + org.eclipse.m2e.sourcelookup.ui;version='[2.0.400,2.0.401)';startlevel=3,\ + org.eclipse.m2e.workspace.cli;version='[0.3.1,0.3.2)';startlevel=3,\ + org.eclipse.orbit.xml-apis-ext;version='[1.0.0,1.0.1)';startlevel=3,\ + org.eclipse.osgi.compatibility.state;version='[1.2.900,1.2.901)';startlevel=3,\ + org.eclipse.osgi.services;version='[3.11.200,3.11.201)';startlevel=1,\ + org.eclipse.osgi.util;version='[3.7.300,3.7.301)';startlevel=3,\ + org.eclipse.pde.build;version='[3.12.200,3.12.201)';startlevel=3,\ + org.eclipse.pde.core;version='[3.17.200,3.17.201)';startlevel=3,\ + org.eclipse.pde.genericeditor.extension;version='[1.2.200,1.2.201)';startlevel=3,\ + org.eclipse.pde.launching;version='[3.11.200,3.11.201)';startlevel=3,\ + org.eclipse.pde.runtime;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.pde.ua.core;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.pde.ua.ui;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.pde.ui;version='[3.15.0,3.15.1)';startlevel=3,\ + org.eclipse.platform;version='[4.30.0,4.30.1)';startlevel=3,\ + org.eclipse.sdk;version='[4.30.0,4.30.1)';startlevel=3,\ + org.eclipse.search;version='[3.16.0,3.16.1)';startlevel=3,\ + org.eclipse.search.core;version='[3.16.0,3.16.1)';startlevel=3,\ + org.eclipse.swt;version='[3.126.0,3.126.1)';startlevel=3,\ + org.eclipse.swt.win32.win32.x86_64;version='[3.124.200,3.124.201)';startlevel=3,\ + org.eclipse.swtbot.eclipse.finder;version='[4.1.0,4.1.1)';startlevel=3,\ + org.eclipse.swtbot.swt.finder;version='[4.1.0,4.1.1)';startlevel=3,\ + org.eclipse.team.core;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.team.ui;version='[3.10.200,3.10.201)';startlevel=3,\ + org.eclipse.text;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui;version='[3.205.0,3.205.1)';startlevel=3,\ + org.eclipse.ui.browser;version='[3.8.100,3.8.101)';startlevel=3,\ + org.eclipse.ui.cheatsheets;version='[3.8.200,3.8.201)';startlevel=3,\ + org.eclipse.ui.console;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui.editors;version='[3.17.100,3.17.101)';startlevel=3,\ + org.eclipse.ui.externaltools;version='[3.6.200,3.6.201)';startlevel=3,\ + org.eclipse.ui.forms;version='[3.13.100,3.13.101)';startlevel=3,\ + org.eclipse.ui.genericeditor;version='[1.3.200,1.3.201)';startlevel=3,\ + org.eclipse.ui.ide;version='[3.22.0,3.22.1)';startlevel=3,\ + org.eclipse.ui.ide.application;version='[1.5.200,1.5.201)';startlevel=3,\ + org.eclipse.ui.intro;version='[3.7.200,3.7.201)';startlevel=3,\ + org.eclipse.ui.navigator;version='[3.12.200,3.12.201)';startlevel=3,\ + org.eclipse.ui.navigator.resources;version='[3.9.100,3.9.101)';startlevel=3,\ + org.eclipse.ui.views;version='[3.12.100,3.12.101)';startlevel=3,\ + org.eclipse.ui.views.log;version='[1.4.200,1.4.201)';startlevel=3,\ + org.eclipse.ui.views.properties.tabbed;version='[3.10.100,3.10.101)';startlevel=3,\ + org.eclipse.ui.win32;version='[3.5.100,3.5.101)';startlevel=3,\ + org.eclipse.ui.workbench;version='[3.131.0,3.131.1)';startlevel=3,\ + org.eclipse.ui.workbench.texteditor;version='[3.17.200,3.17.201)';startlevel=3,\ + org.eclipse.urischeme;version='[1.3.100,1.3.101)';startlevel=3,\ + org.eclipse.wst.common.core;version='[1.4.100,1.4.101)';startlevel=3,\ + org.eclipse.wst.common.emf;version='[1.2.700,1.2.701)';startlevel=3,\ + org.eclipse.wst.common.environment;version='[1.1.0,1.1.1)';startlevel=3,\ + org.eclipse.wst.common.frameworks;version='[1.3.0,1.3.1)';startlevel=3,\ + org.eclipse.wst.common.uriresolver;version='[1.4.0,1.4.1)';startlevel=3,\ + org.eclipse.wst.sse.core;version='[1.2.1200,1.2.1201)';startlevel=3,\ + org.eclipse.wst.xml.core;version='[1.2.800,1.2.801)';startlevel=3,\ + org.hamcrest;version='[2.2.0,2.2.1)';startlevel=3,\ + org.hamcrest.core;version='[2.2.0,2.2.1)';startlevel=3,\ + org.hamcrest.library;version='[1.3.0,1.3.1)';startlevel=3,\ + org.jdom2;version='[2.0.6,2.0.7)';startlevel=3,\ + org.junit;version='[4.13.2,4.13.3)';startlevel=3,\ + org.opentest4j;version='[1.3.0,1.3.1)';startlevel=3,\ + org.osgi.service.cm;version='[1.6.1,1.6.2)';startlevel=1,\ + org.osgi.service.component;version='[1.5.1,1.5.2)';startlevel=1,\ + org.osgi.service.coordinator;version='[1.0.2,1.0.3)';startlevel=1,\ + org.osgi.service.device;version='[1.1.1,1.1.2)';startlevel=1,\ + org.osgi.service.event;version='[1.4.1,1.4.2)';startlevel=1,\ + org.osgi.service.metatype;version='[1.4.1,1.4.2)';startlevel=1,\ + org.osgi.service.prefs;version='[1.1.2,1.1.3)';startlevel=1,\ + org.osgi.service.provisioning;version='[1.2.0,1.2.1)';startlevel=1,\ + org.osgi.service.repository;version='[1.1.0,1.1.1)';startlevel=1,\ + org.osgi.service.upnp;version='[1.2.1,1.2.2)';startlevel=1,\ + org.osgi.service.useradmin;version='[1.1.1,1.1.2)';startlevel=1,\ + org.osgi.service.wireadmin;version='[1.0.2,1.0.3)';startlevel=1,\ + org.osgi.util.function;version='[1.2.0,1.2.1)';startlevel=3,\ + org.osgi.util.measurement;version='[1.0.2,1.0.3)';startlevel=3,\ + org.osgi.util.position;version='[1.0.1,1.0.2)';startlevel=3,\ + org.osgi.util.promise;version='[1.3.0,1.3.1)';startlevel=3,\ + org.osgi.util.xml;version='[1.0.2,1.0.3)';startlevel=3,\ + org.sat4j.core;version='[2.3.6,2.3.7)';startlevel=3,\ + org.sat4j.pb;version='[2.3.6,2.3.7)';startlevel=3,\ + org.tukaani.xz;version='[1.9.0,1.9.1)';startlevel=3 \ No newline at end of file diff --git a/bndtools.core.test/tests.bnd b/bndtools.core.test/tests.bnd index a2d28fad48..2aadd3fad0 100644 --- a/bndtools.core.test/tests.bnd +++ b/bndtools.core.test/tests.bnd @@ -1,6 +1,6 @@ Test-Cases: ${classes;CONCRETE;HIERARCHY_INDIRECTLY_ANNOTATED;org.junit.platform.commons.annotation.Testable} --privatepackage: !bndtools.core.test.launch,bndtools.core.test* +-privatepackage: !bndtools.core.test.launch*,!bndtools.core.test.ui.swtbot*,bndtools.core.test* Bundle-Activator: bndtools.core.test.Activator Bundle-ActivationPolicy: lazy diff --git a/bndtools.core/src/bndtools/editor/project/RepositoryBundleSelectionPart.java b/bndtools.core/src/bndtools/editor/project/RepositoryBundleSelectionPart.java index a86ab03a08..7476811897 100644 --- a/bndtools.core/src/bndtools/editor/project/RepositoryBundleSelectionPart.java +++ b/bndtools.core/src/bndtools/editor/project/RepositoryBundleSelectionPart.java @@ -9,6 +9,7 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Objects; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IStatus; @@ -49,11 +50,13 @@ import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.part.ResourceTransfer; +import org.osgi.framework.namespace.IdentityNamespace; import aQute.bnd.build.model.BndEditModel; import aQute.bnd.build.model.clauses.VersionedClause; import aQute.bnd.header.Attrs; import aQute.bnd.osgi.Constants; +import aQute.bnd.osgi.resource.ResourceUtils; import bndtools.Plugin; import bndtools.editor.common.BndEditorPart; import bndtools.model.clauses.VersionedClauseLabelProvider; @@ -297,19 +300,9 @@ private boolean handleSelectionDrop() { adding.add(newClause); } else if (item instanceof RepositoryFeature) { RepositoryFeature feature = (RepositoryFeature) item; - // Create VersionedClause with "feature:id" BSN and feature=true attribute - VersionedClause newClause = new VersionedClause("feature:" + feature.getFeature() - .getId(), new Attrs()); - // Set version if available - if (feature.getFeature() - .getVersion() != null) { - newClause.setVersionRange(feature.getFeature() - .getVersion()); - } - // Add feature=true attribute for resolver identification - newClause.getAttribs() - .put("feature", "true"); - adding.add(newClause); + // Create a clause in the canonical feature syntax: + // id;version='V';type=org.eclipse.update.feature + adding.add(RepositoryBundleUtils.convertRepoFeature(feature)); } else if (item instanceof IncludedBundleItem) { IncludedBundleItem bundleItem = (IncludedBundleItem) item; VersionedClause newClause = new VersionedClause(bundleItem.getPlugin().id, new Attrs()); @@ -336,7 +329,11 @@ private void handleAdd(Collection newClauses) { for (ListIterator iter = bundles.listIterator(); iter.hasNext();) { VersionedClause existing = iter.next(); if (newClause.getName() - .equals(existing.getName())) { + .equals(existing.getName()) + && Objects.equals(newClause.getAttribs() + .get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE), + existing.getAttribs() + .get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE))) { int index = iter.previousIndex(); iter.set(newClause); viewer.replace(newClause, index); diff --git a/bndtools.core/src/bndtools/model/clauses/VersionedClauseLabelProvider.java b/bndtools.core/src/bndtools/model/clauses/VersionedClauseLabelProvider.java index e10939ffaf..24e7846819 100644 --- a/bndtools.core/src/bndtools/model/clauses/VersionedClauseLabelProvider.java +++ b/bndtools.core/src/bndtools/model/clauses/VersionedClauseLabelProvider.java @@ -5,10 +5,14 @@ import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.graphics.Image; +import org.osgi.framework.namespace.IdentityNamespace; + +import aQute.bnd.osgi.resource.ResourceUtils; public class VersionedClauseLabelProvider extends StyledCellLabelProvider { - final static Image bundleImg = Icons.image("bundle"); + final static Image bundleImg = Icons.image("bundle"); + final static Image featureImg = Icons.image("feature"); @Override public void update(ViewerCell cell) { @@ -21,7 +25,9 @@ public void update(ViewerCell cell) { } cell.setText(label.getString()); cell.setStyleRanges(label.getStyleRanges()); - cell.setImage(bundleImg); + boolean isFeature = ResourceUtils.TYPE_ECLIPSE_FEATURE.equals(clause.getAttribs() + .get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE)); + cell.setImage(isFeature ? featureImg : bundleImg); } } diff --git a/bndtools.core/src/bndtools/model/repo/RepositoryBundleUtils.java b/bndtools.core/src/bndtools/model/repo/RepositoryBundleUtils.java index 32be671869..f881565547 100644 --- a/bndtools.core/src/bndtools/model/repo/RepositoryBundleUtils.java +++ b/bndtools.core/src/bndtools/model/repo/RepositoryBundleUtils.java @@ -1,10 +1,12 @@ package bndtools.model.repo; import org.bndtools.utils.repos.RepoUtils; +import org.osgi.framework.namespace.IdentityNamespace; import aQute.bnd.build.model.clauses.VersionedClause; import aQute.bnd.header.Attrs; import aQute.bnd.osgi.Constants; +import aQute.bnd.osgi.resource.ResourceUtils; import aQute.bnd.version.Version; import aQute.bnd.version.VersionRange; @@ -125,6 +127,28 @@ public static VersionedClause convertRepoBundleVersion(RepositoryBundleVersion b .getBsn(), attribs); } + /** + * Converts a RepositoryFeature into a versioned clause using the + * canonical Eclipse feature syntax: + * {@code id;version='V';type=org.eclipse.update.feature}. The type + * attribute distinguishes the feature from a bundle with the same name + * since Eclipse features and bundles may share id and version. + * + * @param feature the repository feature + * @return the versioned clause identifying the feature + */ + public static VersionedClause convertRepoFeature(RepositoryFeature feature) { + Attrs attribs = new Attrs(); + String version = feature.getFeature() + .getVersion(); + if (version != null && !version.isBlank() && !version.equals("0.0.0")) { + attribs.put(Constants.VERSION_ATTRIBUTE, version); + } + attribs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, ResourceUtils.TYPE_ECLIPSE_FEATURE); + return new VersionedClause(feature.getFeature() + .getId(), attribs); + } + /** * Creates VersionRange from the given version up to the next micro-version * e.g. 1.2.3 results in [1.2.3,1.2.4). diff --git a/bndtools.core/src/bndtools/utils/SelectionDragAdapter.java b/bndtools.core/src/bndtools/utils/SelectionDragAdapter.java index 5e24b66615..0422a9b171 100644 --- a/bndtools.core/src/bndtools/utils/SelectionDragAdapter.java +++ b/bndtools.core/src/bndtools/utils/SelectionDragAdapter.java @@ -58,10 +58,18 @@ public void dragSetData(DragSourceEvent event) { break; } else if (item instanceof RepositoryFeature) { RepositoryFeature rf = (RepositoryFeature) item; - // Create drag data as "feature:id:version" string - event.data = "feature:" + rf.getFeature() - .getId() + ":" + rf.getFeature() - .getVersion(); + // Create drag data in the canonical feature clause syntax + String version = rf.getFeature() + .getVersion(); + StringBuilder clause = new StringBuilder(rf.getFeature() + .getId()); + if (version != null) { + clause.append(";version='") + .append(version) + .append('\''); + } + clause.append(";type=org.eclipse.update.feature"); + event.data = clause.toString(); break; } } diff --git a/bndtools.core/src/bndtools/wizards/repo/RepoBundleSelectionWizardPage.java b/bndtools.core/src/bndtools/wizards/repo/RepoBundleSelectionWizardPage.java index d0caf55a33..19244a2944 100644 --- a/bndtools.core/src/bndtools/wizards/repo/RepoBundleSelectionWizardPage.java +++ b/bndtools.core/src/bndtools/wizards/repo/RepoBundleSelectionWizardPage.java @@ -33,6 +33,7 @@ import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; +import org.osgi.framework.namespace.IdentityNamespace; import aQute.bnd.build.Workspace; import aQute.bnd.build.model.clauses.VersionedClause; @@ -43,10 +44,12 @@ import bndtools.central.RepositoryUtils; import bndtools.model.clauses.VersionedClauseLabelProvider; import bndtools.model.repo.DependencyPhase; +import bndtools.model.repo.FeatureVersionNode; import bndtools.model.repo.ProjectBundle; import bndtools.model.repo.RepositoryBundle; import bndtools.model.repo.RepositoryBundleUtils; import bndtools.model.repo.RepositoryBundleVersion; +import bndtools.model.repo.RepositoryFeature; import bndtools.model.repo.RepositoryTreeLabelProvider; import bndtools.model.repo.SearchableRepositoryTreeContentProvider; @@ -103,7 +106,7 @@ protected RepoBundleSelectionWizardPage(DependencyPhase phase) { public void setSelectedBundles(Collection selectedBundles) { for (VersionedClause clause : selectedBundles) { - this.selectedBundles.put(clause.getName(), clause); + this.selectedBundles.put(key(clause), clause); } } @@ -111,6 +114,16 @@ public List getSelectedBundles() { return new ArrayList<>(selectedBundles.values()); } + /** + * Key for the selected map. Eclipse features may share their name with a + * bundle, so the identity type is part of the key. + */ + private static String key(VersionedClause clause) { + String type = clause.getAttribs() + .get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE); + return type == null ? clause.getName() : clause.getName() + ";" + type; + } + Control createAvailableBundlesPanel(Composite parent) { Composite panel = new Composite(parent, SWT.NONE); new Label(panel, SWT.NONE).setText("Available Bundles:"); @@ -272,8 +285,8 @@ void doAdd() { List adding = getVersionClauses(selection); if (!adding.isEmpty()) { for (VersionedClause clause : adding) { - if (!selectedBundles.containsKey(clause.getName())) { - selectedBundles.put(clause.getName(), clause); + if (!selectedBundles.containsKey(key(clause))) { + selectedBundles.put(key(clause), clause); selectedViewer.add(clause); } } @@ -291,6 +304,10 @@ private List getVersionClauses(IStructuredSelection selection) adding.add(RepositoryBundleUtils.convertRepoBundle((RepositoryBundle) item, phase)); } else if (item instanceof RepositoryBundleVersion) { adding.add(RepositoryBundleUtils.convertRepoBundleVersion((RepositoryBundleVersion) item, phase)); + } else if (item instanceof RepositoryFeature) { + adding.add(RepositoryBundleUtils.convertRepoFeature((RepositoryFeature) item)); + } else if (item instanceof FeatureVersionNode) { + adding.add(RepositoryBundleUtils.convertRepoFeature(((FeatureVersionNode) item).getParent())); } else if (item instanceof ProjectBundle) { String bsn = ((ProjectBundle) item).getBsn(); Attrs attribs = new Attrs(); @@ -304,7 +321,7 @@ private List getVersionClauses(IStructuredSelection selection) void doRemove() { IStructuredSelection selection = (IStructuredSelection) selectedViewer.getSelection(); for (Object clause : selection.toList()) { - selectedBundles.remove(((VersionedClause) clause).getName()); + selectedBundles.remove(key((VersionedClause) clause)); } selectedViewer.remove(selection.toArray()); availableViewer.refresh(); diff --git a/docs/_instructions/_ext/buildpath.md b/docs/_instructions/_ext/buildpath.md index 7de9062b2f..121fbf4a0b 100644 --- a/docs/_instructions/_ext/buildpath.md +++ b/docs/_instructions/_ext/buildpath.md @@ -16,6 +16,17 @@ An example of the `-buildpath` could be the following, where three dependencies osgi.core;version=4.3.1,\ osgi.cmpn;version=4.3.1 +## Eclipse Features + +An entry can reference an Eclipse feature from a [p2 repository](/plugins/p2.html) by adding the `type=org.eclipse.update.feature` attribute. The `type` attribute is required because a feature can share its name and version with a bundle; without it the entry is resolved as a bundle. + + -buildpath: \ + org.eclipse.e4.rcp;version='4.40.0.v20260516-1214';type=org.eclipse.update.feature + +A feature is a container of bundles: on the build path it expands to its member bundles, i.e. the `` references of its `feature.xml` and, recursively, the members of its `` referenced features. `` imports are dependencies, not members, and are not expanded. Members whose `os`/`ws`/`arch` attributes do not match the running platform are skipped. The versions pinned in the feature are resolved exactly; when a pinned version is not present in the repositories, the highest available version is used and a warning is issued. + +Feature entries are supported on all container paths, i.e. also on `-testpath`, `-runpath` and `-runbundles`. + ## See Also * [-testpath] for setting the test path.