Skip to content

Commit a3cd089

Browse files
committed
Modularize the DependencyCheckMojo for future improvements
1 parent 0a481ad commit a3cd089

File tree

11 files changed

+701
-235
lines changed

11 files changed

+701
-235
lines changed

tycho-baseline-plugin/src/main/java/org/eclipse/tycho/baseline/DependencyCheckMojo.java

Lines changed: 28 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@
1414

1515
import java.io.File;
1616
import java.io.IOException;
17-
import java.nio.file.Path;
18-
import java.util.ArrayList;
1917
import java.util.Collection;
2018
import java.util.HashMap;
2119
import java.util.HashSet;
2220
import java.util.List;
2321
import java.util.Map;
24-
import java.util.Optional;
2522
import java.util.Set;
26-
import java.util.TreeSet;
27-
import java.util.function.Function;
2823
import java.util.stream.Collectors;
2924

3025
import org.apache.maven.execution.MavenSession;
@@ -44,12 +39,12 @@
4439
import org.eclipse.osgi.internal.framework.FilterImpl;
4540
import org.eclipse.tycho.DependencyArtifacts;
4641
import org.eclipse.tycho.PackagingType;
47-
import org.eclipse.tycho.artifacts.ArtifactVersion;
4842
import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
49-
import org.eclipse.tycho.baseline.analyze.ClassCollection;
50-
import org.eclipse.tycho.baseline.analyze.ClassMethods;
43+
import org.eclipse.tycho.baseline.analyze.CheckContext;
5144
import org.eclipse.tycho.baseline.analyze.ClassUsage;
5245
import org.eclipse.tycho.baseline.analyze.DependencyAnalyzer;
46+
import org.eclipse.tycho.baseline.analyze.DependencyVersionProblem;
47+
import org.eclipse.tycho.baseline.analyze.ImportPackageChecker;
5348
import org.eclipse.tycho.baseline.analyze.JrtClasses;
5449
import org.eclipse.tycho.baseline.analyze.MethodSignature;
5550
import org.eclipse.tycho.core.MarkdownBuilder;
@@ -58,12 +53,9 @@
5853
import org.eclipse.tycho.core.maven.ToolchainProvider;
5954
import org.eclipse.tycho.core.osgitools.BundleReader;
6055
import org.eclipse.tycho.core.osgitools.OsgiManifest;
61-
import org.eclipse.tycho.core.resolver.target.ArtifactMatcher;
6256
import org.eclipse.tycho.model.manifest.MutableBundleManifest;
6357
import org.osgi.framework.BundleException;
6458
import org.osgi.framework.InvalidSyntaxException;
65-
import org.osgi.framework.Version;
66-
import org.osgi.framework.VersionRange;
6759
import org.osgi.framework.namespace.PackageNamespace;
6860
import org.osgi.resource.Namespace;
6961

@@ -137,144 +129,49 @@ public void execute() throws MojoExecutionException, MojoFailureException {
137129
throw new MojoFailureException("Project artifact is not a valid file");
138130
}
139131
JrtClasses jrtClassResolver = getJRTClassResolver();
140-
List<ClassUsage> usages = DependencyAnalyzer.analyzeUsage(file, jrtClassResolver);
132+
List<ClassUsage> usages;
133+
try {
134+
usages = DependencyAnalyzer.analyzeUsage(file, jrtClassResolver);
135+
} catch (IOException e) {
136+
throw new MojoExecutionException(e);
137+
}
141138
if (usages.isEmpty()) {
142139
return;
143140
}
144141
Collection<IInstallableUnit> units = artifacts.getInstallableUnits();
145142
ModuleRevisionBuilder builder = readOSGiInfo(file);
146143
List<GenericInfo> requirements = builder.getRequirements();
147-
List<DependencyVersionProblem> dependencyProblems = new ArrayList<>();
148-
Map<Path, ClassCollection> analyzeCache = new HashMap<>();
149144
Log log = getLog();
150-
Map<String, Version> lowestPackageVersion = new HashMap<>();
151-
Map<String, Set<Version>> allPackageVersion = new HashMap<>();
152-
Set<String> packageWithError = new HashSet<>();
153-
DependencyAnalyzer dependencyAnalyzer = new DependencyAnalyzer((m, e) -> getLog().error(m, e));
154-
Function<String, Optional<ClassMethods>> classResolver = dependencyAnalyzer
155-
.createDependencyClassResolver(jrtClassResolver, artifacts);
145+
DependencyAnalyzer dependencyAnalyzer = new DependencyAnalyzer(jrtClassResolver, (m, e) -> getLog().error(m, e));
146+
// Create the shared check context
147+
CheckContext context = new CheckContext(dependencyAnalyzer,artifacts,versionProvider, project, log, verbose);
148+
149+
// Create checkers that maintain their own state
150+
ImportPackageChecker importPackageChecker = new ImportPackageChecker(context, units, usages);
151+
156152
for (GenericInfo genericInfo : requirements) {
157153
if (PackageNamespace.PACKAGE_NAMESPACE.equals(genericInfo.getNamespace())) {
158154
Map<String, String> pkgInfo = getVersionInfo(genericInfo,
159155
PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
160156
String packageVersion = pkgInfo.getOrDefault(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, "0.0.0");
161157
String packageName = pkgInfo.get(PackageNamespace.PACKAGE_NAMESPACE);
162-
Optional<IInstallableUnit> packageProvidingUnit = ArtifactMatcher.findPackage(packageName, units);
163-
if (packageProvidingUnit.isEmpty()) {
164-
continue;
165-
}
166-
IInstallableUnit unit = packageProvidingUnit.get();
167-
Optional<org.eclipse.equinox.p2.metadata.Version> matchedPackageVersion = ArtifactMatcher
168-
.getPackageVersion(unit, packageName);
169-
if (matchedPackageVersion.isEmpty()
170-
|| matchedPackageVersion.get().equals(org.eclipse.equinox.p2.metadata.Version.emptyVersion)) {
171-
log.warn("Package " + packageName
172-
+ " has no version exported and can not be checked for compatibility");
173-
continue;
174-
}
175-
matchedPackageVersion.filter(v -> v.isOSGiCompatible()).ifPresent(v -> {
176-
Version current = new Version(v.toString());
177-
allPackageVersion.computeIfAbsent(packageName, nil -> new TreeSet<>()).add(current);
178-
lowestPackageVersion.put(packageName, current);
179-
});
180-
VersionRange versionRange = VersionRange.valueOf(packageVersion);
181-
List<ArtifactVersion> list = versionProvider.stream()
182-
.flatMap(avp -> avp.getPackageVersions(unit, packageName, versionRange, project)).toList();
183-
if (log.isDebugEnabled()) {
184-
log.debug("== " + packageName + " " + packageVersion + " is provided by " + unit
185-
+ " with version range " + versionRange + ", matching versions: " + list.stream()
186-
.map(av -> av.getVersion()).map(String::valueOf).collect(Collectors.joining(", ")));
187-
}
188-
Set<MethodSignature> packageMethods = new TreeSet<>();
189-
Map<MethodSignature, Collection<String>> references = new HashMap<>();
190-
for (ClassUsage usage : usages) {
191-
usage.signatures().filter(ms -> packageName.equals(ms.packageName())).forEach(sig -> {
192-
packageMethods.add(sig);
193-
references.computeIfAbsent(sig, nil -> new TreeSet<>()).addAll(usage.classRef(sig));
194-
});
195-
}
196-
if (packageMethods.isEmpty()) {
197-
// it could be that actually no methods referenced (e.g. interface is only
198-
// referencing a type)
199-
// TODO we need to check that the types used are present in all versions as
200-
// otherwise we will get CNF exception!
201-
// TODO a class can also reference fields!
202-
continue;
203-
}
204-
if (log.isDebugEnabled()) {
205-
for (MethodSignature signature : packageMethods) {
206-
log.debug("Referenced: " + signature.id());
207-
}
208-
}
209-
// now we need to inspect all jars
210-
for (ArtifactVersion v : list) {
211-
Version version = v.getVersion();
212-
if (version == null) {
213-
continue;
214-
}
215-
if (!allPackageVersion.computeIfAbsent(packageName, nil -> new TreeSet<>()).add(version)) {
216-
// already checked!
217-
continue;
218-
}
219-
Path artifact = v.getArtifact();
220-
log.debug(v + "=" + artifact);
221-
if (artifact == null) {
222-
// Retrieval of artifacts might be lazy and we can't get this one --> error?
223-
continue;
224-
}
225-
ClassCollection collection = analyzeCache.get(artifact);
226-
if (collection == null) {
227-
collection = dependencyAnalyzer.analyzeProvides(artifact.toFile(), classResolver);
228-
analyzeCache.put(artifact, collection);
229-
}
230-
boolean ok = true;
231-
Set<MethodSignature> set = collection.provides().collect(Collectors.toSet());
232-
for (MethodSignature mthd : packageMethods) {
233-
if (!set.contains(mthd)) {
234-
List<MethodSignature> provided = collection.get(mthd.className());
235-
if (provided != null) {
236-
provided = provided.stream().filter(ms -> packageName.equals(ms.packageName()))
237-
.toList();
238-
}
239-
if (log.isDebugEnabled()) {
240-
log.debug("Not found: " + mthd);
241-
if (provided != null) {
242-
for (MethodSignature s : provided) {
243-
log.debug("Provided: " + s);
244-
}
245-
}
246-
}
247-
dependencyProblems.add(new DependencyVersionProblem(packageName + "_" + version, String
248-
.format(
249-
"Import-Package `%s %s` (compiled against `%s` provided by `%s %s`) includes `%s` (provided by `%s`) but this version is missing the method `%s#%s`",
250-
packageName, packageVersion,
251-
matchedPackageVersion.orElse(org.eclipse.equinox.p2.metadata.Version.emptyVersion)
252-
.toString(),
253-
unit.getId(), unit.getVersion(), version, v.getProvider(), mthd.className(),
254-
getMethodRef(mthd)), references.get(mthd), provided));
255-
ok = false;
256-
packageWithError.add(packageName);
257-
}
258-
}
259-
if (ok) {
260-
lowestPackageVersion.merge(packageName, version, (v1, v2) -> {
261-
if (v1.compareTo(v2) > 0) {
262-
return v2;
263-
}
264-
return v1;
265-
});
266-
}
267-
}
268-
// TODO we should emit a warning if the lower bound is not part of the
269-
// discovered versions (or even fail?)
270-
158+
importPackageChecker.check(packageName, packageVersion);
271159
}
272160
}
161+
List<DependencyVersionProblem> dependencyProblems = context.getProblems();
273162
if (dependencyProblems.isEmpty()) {
274163
return;
275164
}
276165
if (applySuggestions) {
277-
applyLowerBounds(packageWithError, lowestPackageVersion);
166+
try {
167+
MutableBundleManifest manifest = MutableBundleManifest.read(manifestFile);
168+
boolean changed = importPackageChecker.applySuggestions(manifest);
169+
if (changed) {
170+
MutableBundleManifest.write(manifest, manifestFile);
171+
}
172+
} catch (IOException ioe) {
173+
throw new MojoFailureException(ioe);
174+
}
278175
}
279176
MarkdownBuilder results = new MarkdownBuilder(reportFileName);
280177
Set<String> keys = new HashSet<>();
@@ -316,59 +213,10 @@ public void execute() throws MojoExecutionException, MojoFailureException {
316213
}
317214
results.add("");
318215
}
319-
if (!packageWithError.isEmpty()) {
320-
results.add("");
321-
for (String pkg : packageWithError) {
322-
String suggestion = String.format("Suggested lower version for package `%s` is `%s`", pkg,
323-
lowestPackageVersion.get(pkg));
324-
Set<Version> all = allPackageVersion.get(pkg);
325-
if (all != null && !all.isEmpty()) {
326-
suggestion += " out of " + all.stream().map(v -> String.format("`%s`", v))
327-
.collect(Collectors.joining(", ", "[", "]"));
328-
}
329-
log.info(suggestion);
330-
results.add(suggestion);
331-
}
332-
333-
}
216+
importPackageChecker.reportSuggestions(results, log);
334217
results.write();
335218
}
336219

337-
private String getMethodRef(MethodSignature mthd) {
338-
if (verbose) {
339-
return String.format("%s %s", mthd.methodName(), mthd.signature());
340-
}
341-
return mthd.methodName();
342-
}
343-
344-
private void applyLowerBounds(Set<String> packageWithError, Map<String, Version> lowestPackageVersion)
345-
throws MojoFailureException {
346-
if (packageWithError.isEmpty()) {
347-
return;
348-
}
349-
try {
350-
MutableBundleManifest manifest = MutableBundleManifest.read(manifestFile);
351-
Map<String, String> exportedPackagesVersion = manifest.getExportedPackagesVersion();
352-
Map<String, String> updates = new HashMap<>();
353-
for (String packageName : packageWithError) {
354-
Version lowestVersion = lowestPackageVersion.getOrDefault(packageName, Version.emptyVersion);
355-
String current = exportedPackagesVersion.get(packageName);
356-
if (current == null) {
357-
updates.put(packageName, String.format("[%s,%d)", lowestVersion, (lowestVersion.getMajor() + 1)));
358-
} else {
359-
VersionRange range = VersionRange.valueOf(current);
360-
Version right = range.getRight();
361-
updates.put(packageName, String.format("[%s,%s%c", lowestVersion, right, range.getRightType()));
362-
}
363-
}
364-
manifest.updateImportedPackageVersions(updates);
365-
366-
MutableBundleManifest.write(manifest, manifestFile);
367-
} catch (IOException e) {
368-
throw new MojoFailureException(e);
369-
}
370-
}
371-
372220
private Map<String, String> getVersionInfo(GenericInfo genericInfo, String versionAttribute) {
373221
Map<String, String> directives = new HashMap<>(genericInfo.getDirectives());
374222
String filter = directives.remove(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
@@ -404,9 +252,4 @@ private JrtClasses getJRTClassResolver() {
404252
// use running jvm
405253
return new JrtClasses(null);
406254
}
407-
408-
private static record DependencyVersionProblem(String key, String message, Collection<String> references,
409-
List<MethodSignature> provided) {
410-
411-
}
412255
}

0 commit comments

Comments
 (0)