diff --git a/biz.aQute.bndlib.tests/test/test/baseline/BaselineTest.java b/biz.aQute.bndlib.tests/test/test/baseline/BaselineTest.java index 6de8264a61..f181d1ae3a 100644 --- a/biz.aQute.bndlib.tests/test/test/baseline/BaselineTest.java +++ b/biz.aQute.bndlib.tests/test/test/baseline/BaselineTest.java @@ -22,7 +22,6 @@ import java.util.TreeSet; import java.util.regex.Pattern; -import aQute.bnd.osgi.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -33,6 +32,13 @@ import aQute.bnd.differ.Baseline.BundleInfo; import aQute.bnd.differ.Baseline.Info; import aQute.bnd.differ.DiffPluginImpl; +import aQute.bnd.osgi.Analyzer; +import aQute.bnd.osgi.Builder; +import aQute.bnd.osgi.Constants; +import aQute.bnd.osgi.Instructions; +import aQute.bnd.osgi.Jar; +import aQute.bnd.osgi.Processor; +import aQute.bnd.osgi.Verifier; import aQute.bnd.service.RepositoryPlugin; import aQute.bnd.service.diff.Delta; import aQute.bnd.service.diff.Diff; @@ -263,6 +269,65 @@ public void testNoMismatchForZeroMajor() throws Exception { } } + @Test + public void testMismatchForZeroMajorWhenIncludeZeroMajorEnabled() throws Exception { + try (Builder older = new Builder(); Builder newer = new Builder();) { + older.addClasspath(IO.getFile("java8/older/bin")); + older.setExportPackage("*;version=0.1"); + // Enable baselineincludezeromajor via global property + older.setProperty(Constants.BASELINEINCLUDEZEROMAJOR, "true"); + newer.addClasspath(IO.getFile("java8/newer/bin")); + newer.setExportPackage("*;version=0.1"); + try (Jar o = older.build(); Jar n = newer.build();) { + assertTrue(older.check()); + assertTrue(newer.check()); + + DiffPluginImpl differ = new DiffPluginImpl(); + Baseline baseline = new Baseline(older, differ); + + Set infoSet = baseline.baseline(n, o, null); + assertEquals(1, infoSet.size()); + for (Info info : infoSet) { + // With -baselineincludezeromajor enabled, mismatch should + // be true for 0.x versions + assertTrue(info.mismatch, "Expected mismatch for 0.x version when baselineincludezeromajor is enabled"); + assertEquals(new Version(0, 2, 0), info.suggestedVersion); + assertEquals(info.packageName, "api_default_methods"); + } + } + } + } + + @Test + public void testNoMismatchForZeroZeroVersionsEvenWithIncludeZeroMajor() throws Exception { + try (Builder older = new Builder(); Builder newer = new Builder();) { + older.addClasspath(IO.getFile("java8/older/bin")); + older.setExportPackage("*;version=0.0.1"); + // Enable baselineincludezeromajor via global property + older.setProperty(Constants.BASELINEINCLUDEZEROMAJOR, "true"); + newer.addClasspath(IO.getFile("java8/newer/bin")); + newer.setExportPackage("*;version=0.0.1"); + try (Jar o = older.build(); Jar n = newer.build();) { + assertTrue(older.check()); + assertTrue(newer.check()); + + DiffPluginImpl differ = new DiffPluginImpl(); + Baseline baseline = new Baseline(older, differ); + + Set infoSet = baseline.baseline(n, o, null); + assertEquals(1, infoSet.size()); + for (Info info : infoSet) { + // Even with baselineincludezeromajor enabled, 0.0.x versions should not report mismatch + assertFalse(info.mismatch, "Expected no mismatch for 0.0.x version even with baselineincludezeromajor"); + // Note: The suggested version is 0.1.0 because there's a MINOR change in the test data + // The important thing is that mismatch is false, so no error is reported + assertEquals(new Version(0, 1, 0), info.suggestedVersion); + assertEquals(info.packageName, "api_default_methods"); + } + } + } + } + /** * Check if we can ignore resources in the baseline. First build two jars * that are identical except for the b/b resource. Then do baseline on them. diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/ProjectBuilder.java b/biz.aQute.bndlib/src/aQute/bnd/build/ProjectBuilder.java index b1ae76dee8..6042b97ce1 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/ProjectBuilder.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/ProjectBuilder.java @@ -558,9 +558,18 @@ public Jar getBaselineJar() throws Exception { if (versions.isEmpty()) { // We have a repo - // Baselining 0.x is uninteresting + // Baselining 0.x is uninteresting (unless baselineincludezeromajor is enabled) // x.0.0 is a new major version so maybe there is no baseline - if ((version.getMajor() > 0) && ((version.getMinor() > 0) || (version.getMicro() > 0))) { + + // Check if baselineincludezeromajor is enabled in diffpackages + boolean includeZeroMajor = isIncludeZeroMajorEnabled(); + + boolean shouldWarn = (version.getMajor() > 0) && ((version.getMinor() > 0) || (version.getMicro() > 0)); + if (!shouldWarn && includeZeroMajor && version.getMajor() == 0 && (version.getMinor() > 0 || version.getMicro() > 0)) { + shouldWarn = true; + } + + if (shouldWarn) { warning( "There is no baseline for %s in the baseline repo %s. The build is for version %s, which is higher than %s which suggests that there should be a prior version.", getBsn(), repo, version.getWithoutQualifier(), new Version(version.getMajor())); @@ -649,6 +658,10 @@ public Jar getBaselineJar() throws Exception { return null; } + private boolean isIncludeZeroMajorEnabled() { + return project.is(Constants.BASELINEINCLUDEZEROMAJOR); + } + /** * Remove any staging versions that have a variant with a higher qualifier. * diff --git a/biz.aQute.bndlib/src/aQute/bnd/differ/Baseline.java b/biz.aQute.bndlib/src/aQute/bnd/differ/Baseline.java index f9e23bd261..ece12e1f52 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/differ/Baseline.java +++ b/biz.aQute.bndlib/src/aQute/bnd/differ/Baseline.java @@ -77,10 +77,20 @@ public static class BundleInfo { Version olderVersion; Version suggestedVersion; String releaseRepository; + final boolean includeZeroMajor; public Baseline(Reporter bnd, Differ differ) throws IOException { this.differ = differ; this.bnd = bnd; + + // Read includeZeroMajor from global property + // The Reporter interface doesn't have getProperty, but the actual + // instance is always a Processor + if (bnd instanceof Processor proc) { + includeZeroMajor = proc.is(Constants.BASELINEINCLUDEZEROMAJOR); + } else { + includeZeroMajor = false; + } } /** @@ -319,10 +329,19 @@ private Delta getThreshold(Instructions packageFilters, Instruction matcher) { /** * "Major version zero (0.y.z) is for initial development. Anything may * change at any time. The public API should not be considered stable." + *

+ * This method returns {@code true} if baselining should report mismatches + * for the given versions. By default, it returns {@code false} for versions + * with major version 0 (unless {@code includeZeroMajor} is enabled). * * @see SemVer */ private boolean mismatch(Version older, Version newer) { + if (includeZeroMajor) { + // When includeZeroMajor is enabled, only exclude 0.0.x versions + return !(newer.getMajor() == 0 && newer.getMinor() == 0); + } + // Default behavior: exclude all versions with major version 0 return older.getMajor() > 0 && newer.getMajor() > 0; } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java index 6e701cb8ed..b174af197e 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java @@ -93,6 +93,7 @@ public interface Constants { REQUIRE_CAPABILITY, SERVICE_COMPONENT, PRIVATE_PACKAGE, IGNORE_PACKAGE, TESTCASES); String BASELINE = "-baseline"; + String BASELINEINCLUDEZEROMAJOR = "-baselineincludezeromajor"; String BASELINEREPO = "-baselinerepo"; String BNDDRIVER = "-bnd-driver"; @@ -332,7 +333,7 @@ public interface Constants { Set options = Sets.of(BASELINE, BUILDPATH, BUMPPOLICY, CONDUIT, CLASSPATH, COMPRESSION, CONSUMER_POLICY, DEPENDSON, DONOTCOPY, EXPORT_CONTENTS, FAIL_OK, INCLUDE, - INCLUDERESOURCE, MAKE, MANIFEST, NOEXTRAHEADERS, NOUSES, NOBUNDLES, PEDANTIC, PLUGIN, POM, PROVIDER_POLICY, + BASELINEINCLUDEZEROMAJOR, INCLUDERESOURCE, MAKE, MANIFEST, NOEXTRAHEADERS, NOUSES, NOBUNDLES, PEDANTIC, PLUGIN, POM, PROVIDER_POLICY, REMOVEHEADERS, RESOURCEONLY, SOURCES, SOURCEPATH, SUB, RUNBUNDLES, RUNPATH, RUNSYSTEMPACKAGES, RUNSYSTEMCAPABILITIES, RUNPROPERTIES, REPORTNEWER, UNDERTEST, TESTPATH, TESTPACKAGES, NOMANIFEST, DEPLOYREPO, RELEASEREPO, SAVEMANIFEST, RUNVM, RUNPROGRAMARGS, WAB, WABLIB, RUNFRAMEWORK, RUNFW, RUNKEEP, RUNTRACE, diff --git a/docs/_chapters/180-baselining.md b/docs/_chapters/180-baselining.md index 4077f56baf..05b5b7df64 100644 --- a/docs/_chapters/180-baselining.md +++ b/docs/_chapters/180-baselining.md @@ -10,7 +10,13 @@ APIs are compared for backward compatibility using the semantic versioning rules ## Setting Up a Project for Baselining -During the build, bnd will check the [`-baseline` instruction](../instructions/baseline.html) at the end of the build when the JAR is ready. This instruction is a selector on the symbolic name of the building bundle. If it matches, the baselining is started with one exception: the bundle/package version must be 1.0.0 or above. If the version is less (i.e. major version being `0`) no baselining is possible, the purpose is to allow the [primordial baseline to be established without errors](https://semver.org/#spec-item-4). +During the build, bnd will check the [`-baseline` instruction](../instructions/baseline.html) at the end of the build when the JAR is ready. This instruction is a selector on the symbolic name of the building bundle. If it matches, the baselining is started with one exception: by default, the bundle/package version must be 1.0.0 or above. If the version is less (i.e. major version being `0`) no baselining errors are reported, the purpose is to allow the [primordial baseline to be established without errors](https://semver.org/#spec-item-4). + +To enable baselining for versions in the range `[0.1.0, 1.0.0)`, use the [`-baselineincludezeromajor` instruction](../instructions/baselineincludezeromajor.html): + + -baselineincludezeromajor: true + +This will enable baseline error reporting for packages with major version `0` (except for `0.0.x` versions which are still excluded). By default the baseline is a bundle from one of the repositories with the same symbolic name as the building bundle and the highest possible version. However, it is possible to specify the version or to baseline against a file. diff --git a/docs/_instructions/_ext/baselineincludezeromajor.md b/docs/_instructions/_ext/baselineincludezeromajor.md new file mode 100644 index 0000000000..7c3b382151 --- /dev/null +++ b/docs/_instructions/_ext/baselineincludezeromajor.md @@ -0,0 +1,25 @@ +--- +layout: default +class: Project +title: -baselineincludezeromajor BOOLEAN +summary: Enable baselining for packages with major version 0. +--- + +# -baselineincludezeromajor + +The `-baselineincludezeromajor` instruction enables baseline error reporting for packages with major version `0` (i.e., versions in the range `[0.1.0, 1.0.0)`). + +By default, bnd does not report baselining errors for packages with major version `0`, following the [semantic versioning spec](https://semver.org/#spec-item-4) which states that "Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable." + +When set to `true`, baseline error reporting is enabled for packages with version `0.1.0` or higher. Packages with version `0.0.x` are still excluded from baselining. + +Example: + +``` +-baseline: * +-baselineincludezeromajor: true +``` + +This is useful when you want to enforce semantic versioning discipline during the initial development phase (0.x versions) while still excluding the very early `0.0.x` versions. + +Default value is `false`. diff --git a/docs/_instructions/_ext/diffpackages.md b/docs/_instructions/_ext/diffpackages.md index 75d0a16891..5e9d65d2f4 100644 --- a/docs/_instructions/_ext/diffpackages.md +++ b/docs/_instructions/_ext/diffpackages.md @@ -8,6 +8,26 @@ summary: The names of exported packages to baseline. You can use the `-diffpackages` instruction to specify the names of exported packages to be baseline compared. The default is all exported packages. -## Example +## Attributes + +The following attributes can be specified on each package selector: + +### threshold + +Specifies the minimum change level that should be reported. Valid values are `MICRO`, `MINOR`, or `MAJOR`. Changes below this threshold will be ignored during baselining. + +Example: + + -diffpackages: *;threshold=MAJOR + +This will only report MAJOR changes and ignore MINOR and MICRO changes. + +## Examples + +Exclude internal packages from baselining: -diffpackages: !*.internal.*, * + +Set threshold for changes: + + -diffpackages: *;threshold=MINOR