Skip to content

Commit b178887

Browse files
committed
Let PDE compute required packages using a CompilationParticipant
There are some case where the JDT concept of classpath restrictions do not match well the OSGi runtime handling of imported packages these include: - inlined constants are never visible at runtime and therefore no import required and no restriction issue - indirect references through super types are handled by the classpath of the providing bundle and not the consumer - methods inherited from superclasses are also not a problem at all This is now a radical change in how PDE handles the case and detects problems: - instead of using access restrictions that try to emulate the runtime behavior, it computes a full set of transitive classpath items and add them unrestricted (except for internal packages to get warnings there) - then during compilation we dynamically compute the actual set of required imports at runtime - if any of these imports are missing in the manifest we create a problem and suggest suitable quickfix This has the benefit that we are in full control over what is detected, how it is reported and where it is reported. It also gives much more flexibility in improving user experience, for example one might even want to have some "autoimport" feature that is adding /removing packages on demand so do not bother the user with doing this daunting task manually.
1 parent e7b608f commit b178887

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

ui/org.eclipse.pde.core/plugin.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,4 +439,12 @@
439439
type="Repository">
440440
</targetLocation>
441441
</extension>
442+
<extension
443+
point="org.eclipse.jdt.core.compilationParticipant">
444+
<compilationParticipant
445+
class="org.eclipse.pde.internal.core.builders.PDECompilationParticipant"
446+
id="org.eclipse.pde.core.compilationParticipant"
447+
modifiesEnvironment="false">
448+
</compilationParticipant>
449+
</extension>
442450
</plugin>
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Christoph Läubrich and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Christoph Läubrich - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.pde.internal.core.builders;
15+
16+
import java.io.ByteArrayInputStream;
17+
import java.util.HashSet;
18+
import java.util.Optional;
19+
import java.util.Set;
20+
import java.util.stream.Collectors;
21+
22+
import org.eclipse.core.runtime.CoreException;
23+
import org.eclipse.core.runtime.IPath;
24+
import org.eclipse.core.runtime.NullProgressMonitor;
25+
import org.eclipse.jdt.core.IJavaProject;
26+
import org.eclipse.jdt.core.compiler.BuildContext;
27+
import org.eclipse.jdt.core.compiler.CompilationParticipant;
28+
import org.eclipse.jdt.core.search.IJavaSearchConstants;
29+
import org.eclipse.jdt.core.search.IJavaSearchScope;
30+
import org.eclipse.jdt.core.search.SearchEngine;
31+
import org.eclipse.jdt.core.search.SearchMatch;
32+
import org.eclipse.jdt.core.search.SearchParticipant;
33+
import org.eclipse.jdt.core.search.SearchPattern;
34+
import org.eclipse.jdt.core.search.SearchRequestor;
35+
import org.eclipse.pde.internal.core.bnd.PdeProjectAnalyzer;
36+
import org.eclipse.pde.internal.core.natures.PluginProject;
37+
import org.eclipse.pde.internal.core.search.PluginJavaSearchUtil;
38+
39+
import aQute.bnd.osgi.Analyzer;
40+
import aQute.bnd.osgi.Descriptors.PackageRef;
41+
import aQute.bnd.osgi.EmbeddedResource;
42+
import aQute.bnd.osgi.Packages;
43+
44+
public class PDECompilationParticipant extends CompilationParticipant {
45+
46+
private ThreadLocal<IJavaProject> project = new ThreadLocal<>();
47+
private ThreadLocal<Analyzer> analyzer = new ThreadLocal<>();
48+
49+
@Override
50+
public int aboutToBuild(IJavaProject project) {
51+
this.project.set(project);
52+
System.out.println(
53+
String.format("---- PDECompilationParticipant.aboutToBuild(%s)", project.getProject().getName())); //$NON-NLS-1$
54+
return READY_FOR_BUILD;
55+
}
56+
57+
@Override
58+
public void buildFinished(IJavaProject project) {
59+
System.out.println(
60+
String.format("---- PDECompilationParticipant.buildFinished(%s)", project.getProject().getName())); //$NON-NLS-1$
61+
this.project.set(null);
62+
try (Analyzer analyzer = this.analyzer.get()) {
63+
if (analyzer != null) {
64+
analyzer.setImportPackage("*"); //$NON-NLS-1$
65+
analyzer.calcManifest();
66+
Packages imports = analyzer.getImports();
67+
if (imports == null) {
68+
System.out.println("No packages computed!");
69+
} else {
70+
checkImportedPackages(imports, project);
71+
}
72+
}
73+
} catch (Exception e) {
74+
e.printStackTrace();
75+
}
76+
this.analyzer.set(null);
77+
}
78+
79+
protected void checkImportedPackages(Packages imports, IJavaProject javaProject) throws CoreException {
80+
SearchEngine engine = new SearchEngine();
81+
IJavaSearchScope searchScope = PluginJavaSearchUtil.createSeachScope(javaProject);
82+
Set<String> computedPackages = imports.keySet().stream().map(PackageRef::getFQN)
83+
.collect(Collectors.toSet());
84+
ReferencesSearch referencesSearch = new ReferencesSearch(engine, searchScope);
85+
for (String pkg : computedPackages) {
86+
// TODO here we now want to check if the package is already properly
87+
// imported by the manifest
88+
System.out.println(" - " + pkg);
89+
// if not we need to create an error marker on places where this is
90+
// used is the given compilation units
91+
Set<String> search = referencesSearch.search(pkg);
92+
if (search.isEmpty()) {
93+
System.out.println(" -> Nothing found in source?!?");
94+
}
95+
for (String used : search) {
96+
System.out.println(" -> " + used);
97+
}
98+
}
99+
}
100+
101+
@Override
102+
public void buildStarting(BuildContext[] files, boolean isBatch) {
103+
IJavaProject javaProject = project.get();
104+
System.out.println(
105+
String.format("PDECompilationParticipant.buildStarting(%s)", javaProject.getProject().getName())); //$NON-NLS-1$
106+
try {
107+
this.analyzer.set(new PdeProjectAnalyzer(javaProject.getProject(), true));
108+
} catch (Exception e) {
109+
e.printStackTrace();
110+
}
111+
}
112+
113+
@Override
114+
public void cleanStarting(IJavaProject project) {
115+
System.out.println(
116+
String.format("---- PDECompilationParticipant.cleanStarting(%s)", project.getProject().getName())); //$NON-NLS-1$
117+
}
118+
119+
@Override
120+
public boolean isActive(IJavaProject project) {
121+
System.out.println(String.format("PDECompilationParticipant.isActive(%s)", project.getProject().getName())); //$NON-NLS-1$
122+
return PluginProject.isPluginProject(project.getProject());
123+
}
124+
125+
@Override
126+
public boolean isPostProcessor() {
127+
return true;
128+
}
129+
130+
@Override
131+
public Optional<byte[]> postProcess(BuildContext file, ByteArrayInputStream bytes) {
132+
IJavaProject javaProject = project.get();
133+
Analyzer projectAnalyzer = analyzer.get();
134+
IPath projectRelativePath = file.getFile().getProjectRelativePath();
135+
String relativePath = projectRelativePath.toString();
136+
String base = relativePath.substring(javaProject.getProject().getProjectRelativePath().toString().length());
137+
System.out.println(String.format("PDECompilationParticipant.postProcess(%s)", file.getFile())); //$NON-NLS-1$
138+
if (base.startsWith("src/")) {
139+
// TODO build context should supply the binary name
140+
base = base.substring("src/".length());
141+
}
142+
String name = base.replace(".java", ".class");
143+
System.out.println(base);
144+
projectAnalyzer.getJar().putResource(name,
145+
new EmbeddedResource(bytes.readAllBytes(), System.currentTimeMillis()));
146+
return super.postProcess(file, bytes);
147+
}
148+
149+
private static class ReferencesSearch extends SearchRequestor {
150+
private final SearchEngine engine;
151+
private final IJavaSearchScope searchScope;
152+
private final Set<String> found = new HashSet<>();
153+
154+
public ReferencesSearch(SearchEngine engine, IJavaSearchScope searchScope) {
155+
this.engine = engine;
156+
this.searchScope = searchScope;
157+
}
158+
159+
public Set<String> search(String packageName) throws CoreException {
160+
found.clear();
161+
SearchPattern pattern = SearchPattern.createPattern(packageName, IJavaSearchConstants.PACKAGE,
162+
IJavaSearchConstants.REFERENCES, SearchPattern.R_EXACT_MATCH);
163+
engine.search(pattern, new SearchParticipant[] {
164+
SearchEngine.getDefaultSearchParticipant() },
165+
//TODO it seems there is no way to cancel a compilation participant!
166+
searchScope, this, new NullProgressMonitor());
167+
return Set.copyOf(found);
168+
}
169+
170+
@Override
171+
public void acceptSearchMatch(SearchMatch match) {
172+
// TODO we must gather more information than a string, is it safe to
173+
// store the match?
174+
found.add(String.valueOf(match.getElement()));
175+
}
176+
177+
}
178+
179+
}

0 commit comments

Comments
 (0)