Skip to content

Repository Tagging 2.0 - Brainstorming #7131

@chrisrueger

Description

@chrisrueger

Summary

Introduce a phase: namespace prefix within the existing tags property of repository plugins to cleanly separate bnd lifecycle roles (phases) from user-defined categorization tags. This is a planned breaking-change improvement targeted at bnd 8.0.0.

Related draft PR (earlier approach, now superseded by this design): #6612
Related forum discussion: https://bnd.discourse.group/t/baseline-repo-exclude-from-the-build-path/391/2


Problem

The current tags property on repository plugins (introduced in bnd 7.1.0) serves two conflicting purposes:

  1. Lifecycle control — the reserved resolve tag controls which repos participate in OSGi resolution (-runrepos, BndrunResolveContext). There is currently no equivalent tag for controlling which repos contribute to -buildpath, -testpath, -baseline, or -releaserepo.
  2. User categorization — users can add arbitrary tags (e.g. team-a, qa, snapshots) for their own filtering needs.

This conflation causes several problems:

  • Missing lifecycle axes: there is no way to express "this repo is for baseline checks only" or "this repo should be on the compile classpath but not in the OSGi resolver" using tags today.
  • Opt-in silent cliff: once any repo in the workspace gets a resolve tag, all untagged repos silently drop out of OSGi resolution — a confusing and hard-to-diagnose behavior.
  • Hack workarounds: users resort to tags = "<<EMPTY>>" or assigning meaningless tags just to exclude a repo from resolution.
  • Project.getRepositories() gap: there is currently no tag-based way to exclude a repo from -buildpath / the Eclipse classpath (this is what PR allow excluding repositories from Project -buildpath via tags #6612 attempted to solve).
  • -runrepos is name-based only: filtering run repositories requires listing fragile repo names; there is no way to filter by semantic intent or category.

Proposed Solution: phase: Namespace Prefix in tags

Keep the single tags property, but introduce a phase: prefix convention to distinguish bnd-owned lifecycle roles from user-defined tags.

Syntax

tags = "phase:resolve, phase:compile, team-a, qa"
  • Tags starting with phase: are reserved by bnd and control lifecycle participation.
  • All other tags are user-defined and have no built-in bnd semantics.

Reserved Phase Tags

Tag Meaning
phase:resolve Repo participates in OSGi resolution (-runrepos / BndrunResolveContext)
phase:compile Repo contributes to -buildpath / Eclipse classpath (Project.getRepositories())
phase:test Repo contributes to -testpath
phase:baseline Repo is considered for -baseline version checks
phase:release Repo is a target for -releaserepo
phase:none Explicit sentinel: repo participates in no bnd lifecycle phase

Backward Compatibility Rules

The new method Tags.includesPhase(String phase) replaces the current Tags.includesAny(String... tags) for all internal phase checks, with the following logic:

Repo tags value includesPhase("compile") Rationale
(not set / empty) true Untagged repos participate in all phases — unchanged behavior
"team-a, qa" (user tags only, no phase:) true Backward compat: user-only tags don't restrict phases
"resolve" (legacy, no prefix) true Treated as a plain user tag — warn that "resolve" no longer has lifecycle meaning
"phase:compile, phase:resolve" true Explicit phase opt-in
"phase:resolve" false Only in resolver, not on compile classpath
"phase:none, research" false Explicitly excluded from all lifecycle phases

This means zero migration is required for existing workspaces that do not use phase: prefixed tags. The new behavior only activates when a repo has at least one phase: tag.


Implementation Sketch

Tags.java

Add to aQute.bnd.service.tags.Tags:

private static final String PHASE_PREFIX = "phase:";

/**
 * Phase-aware inclusion check (bnd 8.0.0).
 * Rules:
 *  1. Empty tags → true (untagged = all phases, backward compat)
 *  2. No phase: tags present → true (user-only tags = all phases, backward compat)
 *  3. "phase:none" present → false (explicit exclusion)
 *  4. Otherwise → check if "phase:<phase>" is in the set
 */
public boolean includesPhase(String phase) {
    if (isEmpty()) return true;
    boolean hasAnyPhaseTag = internalSet.stream().anyMatch(t -> t.startsWith(PHASE_PREFIX));
    if (!hasAnyPhaseTag) return true;
    if (internalSet.contains(PHASE_PREFIX + "none")) return false;
    return internalSet.contains(PHASE_PREFIX + phase);
}

/** Returns only the phase: tags */
public Tags phases() {
    return new Tags(internalSet.stream()
        .filter(t -> t.startsWith(PHASE_PREFIX))
        .collect(Collectors.toList()));
}

/** Returns only the user-defined tags (non-phase:) */
public Tags userTags() {
    return new Tags(internalSet.stream()
        .filter(t -> !t.startsWith(PHASE_PREFIX))
        .collect(Collectors.toList()));
}

Constants.java

String REPOTAG_PHASE_RESOLVE  = "phase:resolve";
String REPOTAG_PHASE_COMPILE  = "phase:compile";
String REPOTAG_PHASE_TEST     = "phase:test";
String REPOTAG_PHASE_BASELINE = "phase:baseline";
String REPOTAG_PHASE_RELEASE  = "phase:release";
String REPOTAG_PHASE_NONE     = "phase:none";

Project.java

public List<RepositoryPlugin> getRepositories() {
    return workspace.getRepositories()
        .stream()
        .filter(repo -> repo.getTags().includesPhase("compile"))
        .collect(Collectors.toList());
}

BndrunResolveContext.java

Change getPlugins(Repository.class, Constants.REPOTAGS_RESOLVE) to use includesPhase("resolve") consistently.

-runrepos tag-based filtering (optional / future)

Extend -runrepos to support filtering by user tags in addition to repo names:

-runrepos.tag: qa, snapshots

This allows .bndrun files to select repos by semantic intent rather than fragile names.


Usage Examples

Research/browsing repo — excluded from all lifecycle

-plugin.Research: \
    aQute.bnd.repository.p2.provider.P2Repository; \
    url = https://download.eclipse.org/eclipse/updates/4.36-I-builds/...; \
    name = "Eclipse-4_36"; \
    tags = "phase:none, research, eclipse"

Standard workspace repo

-plugin.Main: \
    aQute.bnd.repository.maven.provider.MavenBndRepository; \
    name = "Main"; \
    tags = "phase:compile, phase:resolve, phase:test, internal"

Baseline-only repo

-plugin.Baseline: \
    aQute.bnd.repository.maven.provider.MavenBndRepository; \
    name = "Nexus-Baseline"; \
    tags = "phase:baseline, nexus"

QA snapshot repo — filtered in bndrun by user tag

-plugin.QA: \
    aQute.bnd.repository.maven.provider.MavenBndRepository; \
    name = "QA-Snapshots"; \
    tags = "phase:compile, phase:test, qa, snapshots"
# in integration-test.bndrun
-runrepos.tag: qa

Acceptance Criteria

  • Tags.includesPhase(String phase) method added with the backward-compatible rules described above
  • Tags.phases() and Tags.userTags() helper methods added
  • Phase constants added to Constants.java (REPOTAG_PHASE_RESOLVE, REPOTAG_PHASE_COMPILE, etc.)
  • Project.getRepositories() filters by phase:compile
  • BndrunResolveContext uses phase:resolve / includesPhase()
  • Existing behavior is fully preserved when no phase: tags are present
  • A deprecation warning is emitted in 7.x when tags = "resolve" (without prefix) is used, guiding migration
  • Documentation updated: 870-plugins.md, -runrepos instruction page
  • Tests added covering: untagged repo, user-only tags, phase:none, mixed phase+user tags, per-phase filtering

Disclaimer: Above is the result and conclusion of a Copilot session which I found fits quite good as a base for discussion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions