Skip to content

Commit

Permalink
Add multi-release processing support
Browse files Browse the repository at this point in the history
Currently bnd is not a capable of processing multi-release jars, this
adds support of multi-release processing by a new directive -release
where one can select what release version should be processed by bnd.

Fixes bndtools#5346

Signed-off-by: Christoph Läubrich <laeubi@laeubi-soft.de>
  • Loading branch information
laeubi committed Aug 23, 2022
1 parent b545b94 commit 2f8635f
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 33 deletions.
93 changes: 67 additions & 26 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Analyzer.java
Expand Up @@ -33,6 +33,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
Expand Down Expand Up @@ -200,12 +201,13 @@ public static Properties getManifest(File dirOrJar) throws Exception {
*/
public void analyze() throws Exception {
if (!analyzed) {
int release = getRelease();
analyzed = true;
analyzeContent();
analyzeContent(release);

// Execute any plugins
// TODO handle better reanalyze
doPlugins();
doPlugins(release);

//
// calculate class versions in use
Expand Down Expand Up @@ -293,7 +295,7 @@ public void analyze() throws Exception {
getHostPackages().ifPresent(hostPackages -> referredAndExported.keySet()
.removeAll(hostPackages));

getRequireBundlePackages().ifPresent(hostPackages -> referredAndExported.keySet()
getRequireBundlePackages(release).ifPresent(hostPackages -> referredAndExported.keySet()
.removeAll(hostPackages));

String h = getProperty(IMPORT_PACKAGE);
Expand Down Expand Up @@ -423,6 +425,19 @@ public void analyze() throws Exception {
}
}

private int getRelease() {
String property = getProperty(JAVA_RELEASE);
if (property != null) {
try {
return Integer.parseInt(property.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
JAVA_RELEASE + " must be a valid integer but was " + property + " (" + e.getMessage() + ")", e);
}
}
return -1;
}

private void reset() {
contained.clear();
classspace.clear();
Expand All @@ -436,10 +451,10 @@ private void reset() {
bcpTypes.clear();
}

private void analyzeContent() throws Exception {
private void analyzeContent(int release) throws Exception {
// Parse all the classes in the
// the jar according to the OSGi Bundle-ClassPath
analyzeBundleClasspath();
analyzeBundleClasspath(release);

//
// Get exported packages from the
Expand All @@ -448,9 +463,9 @@ private void analyzeContent() throws Exception {
//

for (Jar current : getClasspath()) {
getManifestInfoFromClasspath(current, classpathExports, contracts);
getManifestInfoFromClasspath(current, classpathExports, contracts, release);

Manifest m = current.getManifest();
Manifest m = current.getManifest(release);
if (m == null) {
for (String dir : current.getDirectories()
.keySet()) {
Expand All @@ -472,7 +487,7 @@ private void analyzeContent() throws Exception {

// Conditional packages

doConditionalPackages();
doConditionalPackages(release);
}

/**
Expand Down Expand Up @@ -504,7 +519,12 @@ public Optional<Set<PackageRef>> getHostPackages() {
* @return the packages from the required bundles, with no Require-Bundle
* return an empty Optional
*/

public Optional<Set<PackageRef>> getRequireBundlePackages() {
return getRequireBundlePackages(Jar.MULTI_RELEASE_DEFAULT_VERSION);
}

public Optional<Set<PackageRef>> getRequireBundlePackages(int release) {

Parameters required = getRequireBundle();
if (required.isEmpty())
Expand All @@ -514,7 +534,7 @@ public Optional<Set<PackageRef>> getRequireBundlePackages() {
.stream()
.map(this::toJar)
.filter(Objects::nonNull)
.map(asFunctionOrElse(Jar::getManifest, null))
.map(asFunctionOrElse(jar -> jar.getManifest(release), null))
.filter(Objects::nonNull)
.flatMap(manifest -> {
Domain domain = Domain.domain(manifest);
Expand Down Expand Up @@ -625,7 +645,7 @@ public Clazz getPackageInfo(PackageRef packageRef) {
}
}

private void doConditionalPackages() throws Exception {
private void doConditionalPackages(int release) throws Exception {
//
// We need to find out the contained packages
// again ... so we need to clear any visited
Expand All @@ -636,7 +656,7 @@ private void doConditionalPackages() throws Exception {

for (Jar extra; (extra = getExtra()) != null;) {
dot.addAll(extra);
analyzeJar(extra, "", true, null);
analyzeJar(extra, "", true, null, release);
}
}

Expand Down Expand Up @@ -980,8 +1000,10 @@ protected Jar getExtra() throws Exception {

/**
* Call AnalyzerPlugins to analyze the content.
*
* @param release the release flag for that content should be analyzed
*/
private void doPlugins() {
private void doPlugins(int release) {
List<AnalyzerPlugin> plugins = getPlugins(AnalyzerPlugin.class);
plugins.sort(Comparator.comparingInt(AnalyzerPlugin::ordering));
for (AnalyzerPlugin plugin : plugins) {
Expand All @@ -1000,7 +1022,7 @@ private void doPlugins() {
.filterValue(Objects::nonNull)
.collect(MapStream.toMap());
reset();
analyzeContent();
analyzeContent(release);
// Restore -internal-source information
// if the package still exists
sourceInformation.forEach((pkgRef, source) -> {
Expand Down Expand Up @@ -1913,10 +1935,10 @@ public boolean referred(PackageRef packageName) {
return result;
}

private void getManifestInfoFromClasspath(Jar jar, Packages classpathExports, Contracts contracts) {
private void getManifestInfoFromClasspath(Jar jar, Packages classpathExports, Contracts contracts, int release) {
logger.debug("get Manifest Info From Classpath for {}", jar);
try {
Manifest m = jar.getManifest();
Manifest m = jar.getManifest(release);
if (m != null) {
Domain domain = Domain.domain(m);
Parameters exported = domain.getExportPackage();
Expand Down Expand Up @@ -2577,11 +2599,11 @@ public Jar getTarget() {
return getJar();
}

private void analyzeBundleClasspath() throws Exception {
private void analyzeBundleClasspath(int release) throws Exception {
Parameters bcp = getBundleClasspath();

if (bcp.isEmpty()) {
analyzeJar(dot, "", true, null);
analyzeJar(dot, "", true, null, release);
} else {
// Cleanup entries
bcp = bcp.stream()
Expand All @@ -2593,7 +2615,7 @@ private void analyzeBundleClasspath() throws Exception {

for (String path : bcp.keySet()) {
if (path.equals(".")) {
analyzeJar(dot, "", okToIncludeDirs, null);
analyzeJar(dot, "", okToIncludeDirs, null, release);
continue;
}
//
Expand All @@ -2610,7 +2632,7 @@ private void analyzeBundleClasspath() throws Exception {
if (!(resource instanceof JarResource)) {
addClose(jar);
}
analyzeJar(jar, "", true, path);
analyzeJar(jar, "", true, path, release);
} catch (Exception e) {
warning("Invalid bundle classpath entry: %s: %s", path, e);
}
Expand All @@ -2623,7 +2645,7 @@ private void analyzeBundleClasspath() throws Exception {
warning(Constants.BUNDLE_CLASSPATH
+ " uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
path);
analyzeJar(dot, path.concat("/"), true, path);
analyzeJar(dot, path.concat("/"), true, path, release);
} else {
Attrs info = bcp.get(path);
if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
Expand All @@ -2639,17 +2661,37 @@ private void analyzeBundleClasspath() throws Exception {
* contained and referred set and uses. This method ignores the Bundle
* classpath.
*/
private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs, String bcpEntry) throws Exception {
private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs, String bcpEntry, int release)
throws Exception {
Map<String, Clazz> mismatched = new HashMap<>();

Parameters importPackage = Optional.ofNullable(jar.getManifest())
Parameters importPackage = Optional.ofNullable(jar.getManifest(release))
.map(Domain::domain)
.map(Domain::getImportPackage)
.orElseGet(() -> new Parameters());

next: for (String path : jar.getResources()
.keySet()) {
if (path.startsWith(prefix)) {
Map<String, NavigableMap<Integer, Resource>> resources = jar.getVersionedResources();
next: for (Entry<String, NavigableMap<Integer, Resource>> entry : resources.entrySet()) {
String path = entry.getKey();
NavigableMap<Integer, Resource> releaseMap = entry.getValue();
Resource resource;
if (release < 9) {
// we need to get the default version
resource = releaseMap.get(Jar.MULTI_RELEASE_DEFAULT_VERSION);
} else {
// we need to get the highest resource for this release
Entry<Integer, Resource> versionEntry = releaseMap.headMap(release, true)
.lastEntry();
if (versionEntry == null) {
// there is a resource, but it is not available for this
// release, e.g. we analyze for release=11 and the resource
// is only for release=17
resource = null;
} else {
resource = versionEntry.getValue();
}
}
if (resource != null && path.startsWith(prefix)) {

String relativePath = path.substring(prefix.length());

Expand All @@ -2665,7 +2707,6 @@ private boolean analyzeJar(Jar jar, String prefix, boolean okToIncludeDirs, Stri

// Check class resources, we need to analyze them
if (path.endsWith(".class")) {
Resource resource = jar.getResource(path);
Clazz clazz;

try {
Expand Down
3 changes: 2 additions & 1 deletion biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java
Expand Up @@ -293,6 +293,7 @@ public interface Constants {
String WORKINGSET = "-workingset";
String WORKINGSET_MEMBER = "member";
String REQUIRE_BND = "-require-bnd";
String JAVA_RELEASE = "-release";

// Deprecated
String CLASSPATH = "-classpath";
Expand All @@ -314,7 +315,7 @@ public interface Constants {
CONNECTION_SETTINGS, RUNPROVIDEDCAPABILITIES, WORKINGSET, RUNSTORAGE, REPRODUCIBLE, INCLUDEPACKAGE,
CDIANNOTATIONS, REMOTEWORKSPACE, MAVEN_DEPENDENCIES, BUILDERIGNORE, STALECHECK, MAVEN_SCOPE, RUNSTARTLEVEL,
RUNOPTIONS, NOCLASSFORNAME, EXPORT_APIGUARDIAN, RESOLVE, DEFINE_CONTRACT, GENERATE, RUNFRAMEWORKRESTART,
NOIMPORTJAVA, VERSIONDEFAULTS, LIBRARY);
NOIMPORTJAVA, VERSIONDEFAULTS, LIBRARY, JAVA_RELEASE);

// Ignore bundle specific headers. These headers do not make a lot of sense
// to inherit
Expand Down

0 comments on commit 2f8635f

Please sign in to comment.