From 140bc01cabd693012772bf0160a54a99153dcfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Fri, 17 May 2024 08:32:59 +0200 Subject: [PATCH] Synthesize a (bnd) clazz from the JDT model to support export annotation Export annotations are already parsed and the package is given to the analyzer, but if no export instruction is present the bnd analyzer does throw them away as it does not know about it in a later stage. This now synthesize a (bnd) class to emulate what the analyzer would see for a real class file and inject that instance into the analyzer so the package is finally considdered. --- .../core/bnd/PdeInstallableUnitProvider.java | 34 ++++++++++-- .../core/bnd/SourceCodeAnalyzerPlugin.java | 54 ++++++++++++++++--- .../tycho/packaging/BndManifestProcessor.java | 8 ++- 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/PdeInstallableUnitProvider.java b/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/PdeInstallableUnitProvider.java index 842bdb815c..d968b5dbd9 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/PdeInstallableUnitProvider.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/PdeInstallableUnitProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Christoph Läubrich and others. + * Copyright (c) 2023, 2024 Christoph Läubrich and others. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -49,7 +49,10 @@ import org.eclipse.tycho.resolver.InstallableUnitProvider; import aQute.bnd.osgi.Builder; +import aQute.bnd.osgi.Clazz; import aQute.bnd.osgi.Constants; +import aQute.bnd.osgi.Descriptors.PackageRef; +import aQute.bnd.osgi.Descriptors.TypeRef; import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Processor; import aQute.bnd.version.MavenVersion; @@ -183,7 +186,25 @@ private static Map collectInitial(MavenProject project, Map< private Collection generateWithProcessor(MavenProject project, Processor processor, Collection artifacts) throws Exception { - try (Builder analyzer = new Builder(processor)) { + SourceCodeAnalyzerPlugin plugin = new SourceCodeAnalyzerPlugin( + project.getCompileSourceRoots().stream().map(Path::of).toList()); + try (Builder analyzer = new Builder(processor) { + @Override + public Clazz getPackageInfo(PackageRef packageRef) { + Clazz info = super.getPackageInfo(packageRef); + if (info == null) { + return plugin.getPackageInfo(packageRef); + } + return info; + } + + @Override + public Clazz findClass(TypeRef typeRef) throws Exception { + //TODO instead of override the getPackageInfo(...) we can also use this but + //in that case we probably need to implement more in the JDTClazz as it is called from different places + return super.findClass(typeRef); + }; + }) { analyzer.setBase(project.getBasedir()); Jar jar = new Jar(project.getArtifactId()); analyzer.setJar(jar); @@ -197,8 +218,7 @@ private Collection generateWithProcessor(MavenProject project, } } } - analyzer.addBasicPlugin( - new SourceCodeAnalyzerPlugin(project.getCompileSourceRoots().stream().map(Path::of).toList())); + analyzer.addBasicPlugin(plugin); analyzer.setProperty(Constants.NOEXTRAHEADERS, "true"); analyzer.build(); Manifest manifest = jar.getManifest(); @@ -210,6 +230,12 @@ private Collection generateWithProcessor(MavenProject project, ManifestUtil.write(manifest, outputStream); String str = new String(outputStream.toByteArray(), StandardCharsets.UTF_8); logger.debug("Generated preliminary manifest for " + project.getId() + ":\r\n" + str); + for (String error : analyzer.getErrors()) { + logger.debug("ERROR: " + error); + } + for (String warn : analyzer.getWarnings()) { + logger.debug("WARN: " + warn); + } } return installableUnitGenerator.getInstallableUnits(manifest); } diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/SourceCodeAnalyzerPlugin.java b/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/SourceCodeAnalyzerPlugin.java index 3c9988ff73..7c05f69deb 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/SourceCodeAnalyzerPlugin.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/bnd/SourceCodeAnalyzerPlugin.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Christoph Läubrich and others. + * Copyright (c) 2023, 2024 Christoph Läubrich and others. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -18,8 +18,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.eclipse.jdt.core.dom.AST; @@ -35,8 +37,11 @@ import aQute.bnd.header.Attrs; import aQute.bnd.osgi.Analyzer; -import aQute.bnd.osgi.Descriptors; +import aQute.bnd.osgi.Clazz; import aQute.bnd.osgi.Descriptors.PackageRef; +import aQute.bnd.osgi.Descriptors.TypeRef; +import aQute.bnd.osgi.FileResource; +import aQute.bnd.osgi.Resource; import aQute.bnd.service.AnalyzerPlugin; /** @@ -44,10 +49,13 @@ */ class SourceCodeAnalyzerPlugin implements AnalyzerPlugin { + private static final String PACKAGE_INFO = "package-info"; private static final String ANNOTATION_VERSION = "org.osgi.annotation.versioning.Version"; private static final String ANNOTATION_EXPORT = "org.osgi.annotation.bundle.Export"; - private static final String PACKAGE_INFO_JAVA = "package-info.java"; + private static final String PACKAGE_INFO_JAVA = PACKAGE_INFO + ".java"; + private static final String PACKAGE_INFO_CLASS = PACKAGE_INFO + ".class"; private List sourcePaths; + private Map packageInfoMap = new HashMap<>(); public SourceCodeAnalyzerPlugin(List sourcePaths) { this.sourcePaths = sourcePaths; @@ -56,7 +64,6 @@ public SourceCodeAnalyzerPlugin(List sourcePaths) { @Override public boolean analyzeJar(Analyzer analyzer) throws Exception { ASTParser parser = ASTParser.newParser(AST.getJLSLatest()); - Descriptors descriptors = new Descriptors(); Set seenPackages = new HashSet<>(); Set analyzedPath = new HashSet<>(); for (Path sourcePath : sourcePaths) { @@ -80,12 +87,16 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO PackageDeclaration packageDecl = cu.getPackage(); if (packageDecl != null) { String packageFqdn = packageDecl.getName().getFullyQualifiedName(); + PackageRef packageRef = analyzer.getPackageRef(packageFqdn); if (seenPackages.add(packageFqdn)) { //make the package available to bnd analyzer - PackageRef packageRef = descriptors.getPackageRef(packageFqdn); analyzer.getContained().put(packageRef); } if (packageInfo) { + JDTClazz clazz = new JDTClazz(analyzer, + packageRef.getBinary() + "/" + PACKAGE_INFO_CLASS, + new FileResource(file), + analyzer.getTypeRef(packageRef.getBinary() + "/" + PACKAGE_INFO)); //check for export annotations boolean export = false; String version = null; @@ -94,6 +105,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO String annotationFqdn = annot.getTypeName().getFullyQualifiedName(); if (ANNOTATION_EXPORT.equals(annotationFqdn)) { export = true; + clazz.addAnnotation( + analyzer.getTypeRef(ANNOTATION_EXPORT.replace('.', '/'))); } else if (ANNOTATION_VERSION.equals(annotationFqdn)) { if (annot instanceof NormalAnnotation normal) { for (Object vp : normal.values()) { @@ -112,7 +125,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } } if (export) { - PackageRef packageRef = descriptors.getPackageRef(packageFqdn); + packageInfoMap.put(packageRef, clazz); if (version == null) { analyzer.getContained().put(packageRef); } else { @@ -142,4 +155,33 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx return false; } + Clazz getPackageInfo(PackageRef packageRef) { + return packageInfoMap.get(packageRef); + } + + private static final class JDTClazz extends Clazz { + private Set annotations = new HashSet<>(); + private TypeRef className; + + public JDTClazz(Analyzer analyzer, String path, Resource resource, TypeRef className) { + super(analyzer, path, resource); + this.className = className; + } + + @Override + public TypeRef getClassName() { + return className; + } + + public void addAnnotation(TypeRef typeRef) { + annotations.add(typeRef); + } + + @Override + public Set annotations() { + return annotations; + } + + } + } diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/BndManifestProcessor.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/BndManifestProcessor.java index 2cc8e6ad7b..6a3f04dace 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/BndManifestProcessor.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/BndManifestProcessor.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Christoph Läubrich and others. + * Copyright (c) 2023, 2024 Christoph Läubrich and others. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -31,6 +31,7 @@ import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.logging.Logger; import org.eclipse.tycho.ClasspathEntry; +import org.eclipse.tycho.TychoConstants; import org.eclipse.tycho.classpath.ClasspathContributor; import org.eclipse.tycho.helper.PluginConfigurationHelper; import org.eclipse.tycho.helper.PluginRealmHelper; @@ -74,7 +75,10 @@ public BndManifestProcessor(MavenSession mavenSession) { @Override public void processManifest(MavenProject mavenProject, Manifest manifest) { - + if (new File(mavenProject.getBasedir(), TychoConstants.PDE_BND).exists()) { + // we don't want to process manifests already generated by BND! + return; + } if (configurationHelper.getConfiguration().getBoolean("deriveHeaderFromSource") // don't be confused here, we use FALSE as default because it means no such // configuration option defined in the mojo (probably called from different