Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[bp} Create tycho-baseline:check-dependencies mojo to validate versions
Browse files Browse the repository at this point in the history
If version ranges on packages or bundles are used it is currently quite
hard to ensure these are actually work for the provided ranges
especially if they evolve over a long time.

This now adds a new tycho-baseline:check-dependencies mojo that can help
in this task by inspecting the byte-code of the project and the
dependencies if there is any inconsistency in any of the dependencies
matching the version range.
laeubi committed Jan 17, 2025
1 parent db64bb9 commit 2b72bea
Showing 18 changed files with 1,844 additions and 568 deletions.
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ This is now fixed, but might result in build previously working now fail due to

backports:
- Support for implicit dependencies in target definitions
- Add tycho-baseline:check-dependencies mojo

## 4.0.10

14 changes: 14 additions & 0 deletions tycho-baseline-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -53,13 +53,27 @@
<artifactId>asciitable</artifactId>
<version>0.3.2</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7.1</version>
</dependency>
<dependency>
<groupId>org.eclipse.emf</groupId>
<artifactId>org.eclipse.emf.ecore</artifactId>
<version>2.38.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
</plugin>
<plugin>
<groupId>org.eclipse.sisu</groupId>
<artifactId>sisu-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.osgi.container.ModuleRevisionBuilder;
import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory;
import org.eclipse.osgi.internal.framework.FilterImpl;
import org.eclipse.tycho.DependencyArtifacts;
import org.eclipse.tycho.PackagingType;
import org.eclipse.tycho.artifacts.ArtifactVersion;
import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
import org.eclipse.tycho.baseline.analyze.ClassCollection;
import org.eclipse.tycho.baseline.analyze.ClassMethods;
import org.eclipse.tycho.baseline.analyze.ClassUsage;
import org.eclipse.tycho.baseline.analyze.DependencyAnalyzer;
import org.eclipse.tycho.baseline.analyze.JrtClasses;
import org.eclipse.tycho.baseline.analyze.MethodSignature;
import org.eclipse.tycho.core.TychoProjectManager;
import org.eclipse.tycho.core.maven.OSGiJavaToolchain;
import org.eclipse.tycho.core.maven.ToolchainProvider;
import org.eclipse.tycho.core.osgitools.BundleReader;
import org.eclipse.tycho.core.osgitools.OsgiManifest;
import org.eclipse.tycho.core.resolver.target.ArtifactMatcher;
import org.eclipse.tycho.model.manifest.MutableBundleManifest;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.osgi.framework.VersionRange;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.resource.Namespace;

/**
* This mojos performs deep inspections of dependencies to find out if a version
* range is actually valid. For this the following steps are performed:
* <ol>
* <li>The current project artifact is analyzed for method signatures it
* calls</li>
* <li>Then it is checked what of these match to a given dependency</li>
* <li>All dependency versions matching the range are fetched and inspected
* using {@link ArtifactVersionProvider}s</li>
* <li>Then it checks if there are any missing signatures or inconsistencies and
* possibly failing the build</li>
* </ol>
*/
@Mojo(defaultPhase = LifecyclePhase.VERIFY, name = "check-dependencies", threadSafe = true, requiresProject = true)
public class DependencyCheckMojo extends AbstractMojo {

@Parameter(property = "project", readonly = true)
private MavenProject project;

@Parameter(property = "session", readonly = true)
private MavenSession session;

@Parameter(defaultValue = "${project.build.directory}/versionProblems.txt", property = "tycho.dependency.check.report")
private File reportFileName;

@Parameter(defaultValue = "${project.basedir}/META-INF/MANIFEST.MF", property = "tycho.dependency.check.manifest")
private File manifestFile;

@Parameter(defaultValue = "false", property = "tycho.dependency.check.apply")
private boolean applySuggestions;

@Component
private TychoProjectManager projectManager;

@Component
private List<ArtifactVersionProvider> versionProvider;

@Component
private BundleReader bundleReader;

@Component
ToolchainProvider toolchainProvider;

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if (!"jar".equals(project.getPackaging())
&& !PackagingType.TYPE_ECLIPSE_PLUGIN.equals(project.getPackaging())) {
return;
}
DependencyArtifacts artifacts = projectManager.getDependencyArtifacts(project).orElse(null);
File file = project.getArtifact().getFile();
if (file == null || !file.isFile()) {
throw new MojoFailureException("Project artifact is not a valid file");
}
JrtClasses jrtClassResolver = getJRTClassResolver();
List<ClassUsage> usages = DependencyAnalyzer.analyzeUsage(file, jrtClassResolver);
if (usages.isEmpty()) {
return;
}
Collection<IInstallableUnit> units = artifacts.getInstallableUnits();
ModuleRevisionBuilder builder = readOSGiInfo(file);
List<GenericInfo> requirements = builder.getRequirements();
List<DependencyVersionProblem> dependencyProblems = new ArrayList<>();
Map<Path, ClassCollection> analyzeCache = new HashMap<>();
Log log = getLog();
Map<String, Version> lowestPackageVersion = new HashMap<>();
Map<String, Set<Version>> allPackageVersion = new HashMap<>();
Set<String> packageWithError = new HashSet<>();
Function<String, Optional<ClassMethods>> classResolver = DependencyAnalyzer
.createDependencyClassResolver(jrtClassResolver, artifacts);
for (GenericInfo genericInfo : requirements) {
if (PackageNamespace.PACKAGE_NAMESPACE.equals(genericInfo.getNamespace())) {
Map<String, String> pkgInfo = getVersionInfo(genericInfo,
PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE);
String packageVersion = pkgInfo.getOrDefault(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, "0.0.0");
String packageName = pkgInfo.get(PackageNamespace.PACKAGE_NAMESPACE);
Optional<IInstallableUnit> packageProvidingUnit = ArtifactMatcher.findPackage(packageName, units);
if (packageProvidingUnit.isEmpty()) {
continue;
}
if (packageName.contains(".internal.")) {
// TODO configurable, but also internal packages should be properly versioned!
continue;
}
IInstallableUnit unit = packageProvidingUnit.get();
Optional<org.eclipse.equinox.p2.metadata.Version> matchedPackageVersion = ArtifactMatcher
.getPackageVersion(unit, packageName);
matchedPackageVersion.filter(v -> v.isOSGiCompatible())
.ifPresent(v -> {
Version current = new Version(v.toString());
allPackageVersion.computeIfAbsent(packageName, nil -> new TreeSet<>()).add(current);
lowestPackageVersion.put(packageName, current);
});
VersionRange versionRange = VersionRange.valueOf(packageVersion);
List<ArtifactVersion> list = versionProvider.stream()
.flatMap(avp -> avp.getPackageVersions(unit, packageName, versionRange, project)).toList();
if (log.isDebugEnabled()) {
log.debug("== " + packageName + " " + packageVersion + " is provided by " + unit
+ " with version range " + versionRange + ", matching versions: " + list.stream()
.map(av -> av.getVersion()).map(String::valueOf).collect(Collectors.joining(", ")));
}
Set<MethodSignature> packageMethods = new TreeSet<>();
Map<MethodSignature, Collection<String>> references = new HashMap<>();
for (ClassUsage usage : usages) {
usage.signatures().filter(ms -> packageName.equals(ms.packageName())).forEach(sig -> {
packageMethods.add(sig);
references.computeIfAbsent(sig, nil -> new TreeSet<>()).addAll(usage.classRef(sig));
});
}
if (packageMethods.isEmpty()) {
// it could be that actually no methods referenced (e.g. interface is only
// referencing a type)
// TODO we need to check that the types used are present in all versions as
// otherwise we will get CNF exception!
// TODO a class can also reference fields!
continue;
}
if (log.isDebugEnabled()) {
for (MethodSignature signature : packageMethods) {
log.debug("Referenced: " + signature.id());
}
}
// now we need to inspect all jars
for (ArtifactVersion v : list) {
Version version = v.getVersion();
if (version == null) {
continue;
}
if (!allPackageVersion.computeIfAbsent(packageName, nil -> new TreeSet<>()).add(version)) {
// already checked!
continue;
}
Path artifact = v.getArtifact();
log.debug(v + "=" + artifact);
if (artifact == null) {
// Retrieval of artifacts might be lazy and we can't get this one --> error?
continue;
}
ClassCollection collection = analyzeCache.get(artifact);
if (collection == null) {
collection = DependencyAnalyzer.analyzeProvides(artifact.toFile(), classResolver, null);
analyzeCache.put(artifact, collection);
}
boolean ok = true;
Set<MethodSignature> set = collection.provides().collect(Collectors.toSet());
for (MethodSignature mthd : packageMethods) {
if (!set.contains(mthd)) {
List<MethodSignature> provided = collection.get(mthd.className());
if (provided != null) {
provided = provided.stream().filter(ms -> packageName.equals(ms.packageName()))
.toList();
}
if (log.isDebugEnabled()) {
log.debug("Not found: " + mthd);
if (provided != null) {
for (MethodSignature s : provided) {
log.debug("Provided: " + s);
}
}
}
dependencyProblems.add(new DependencyVersionProblem(String.format(
"Import-Package '%s %s (compiled against %s %s / %s) includes %s (provided by %s) but this version is missing the method %s",
packageName, packageVersion, unit.getId(), unit.getVersion(),
matchedPackageVersion.orElse(org.eclipse.equinox.p2.metadata.Version.emptyVersion)
.toString(),
v.getVersion(), v.getProvider(), mthd.id()), references.get(mthd), provided));
ok = false;
packageWithError.add(packageName);
}
}
if (ok) {
lowestPackageVersion.merge(packageName, version, (v1, v2) -> {
if (v1.compareTo(v2) > 0) {
return v2;
}
return v1;
});
}
}
// TODO we should emit a warning if the lower bound is not part of the
// discovered versions (or even fail?)

}
}
if (dependencyProblems.isEmpty()) {
return;
}
List<String> results = new ArrayList<>();
for (DependencyVersionProblem problem : dependencyProblems) {
Collection<String> references = problem.references();
String message;
if (references == null || references.isEmpty()) {
message = problem.message();
} else {
message = String.format("%s, referenced by:%s%s", problem.message(), System.lineSeparator(),
problem.references().stream().collect(Collectors.joining(System.lineSeparator())));
}
log.error(message);
results.add(message);
List<MethodSignature> provided = problem.provided();
if (provided != null && !provided.isEmpty()) {
results.add("Provided Methods in this version are:");
for (MethodSignature sig : provided) {
results.add("\t" + sig);
}
}
}
for (String pkg : packageWithError) {
String suggestion = "Suggested lower version for package " + pkg + " is " + lowestPackageVersion.get(pkg);
Set<Version> all = allPackageVersion.get(pkg);
if (all != null && !all.isEmpty()) {
suggestion += " out of " + all;
}
log.info(suggestion);
results.add(suggestion);
}
if (results.isEmpty()) {
return;
}
try {
Files.writeString(reportFileName.toPath(),
results.stream().collect(Collectors.joining(System.lineSeparator())));
if (applySuggestions) {
applyLowerBounds(packageWithError, lowestPackageVersion);
}
} catch (IOException e) {
throw new MojoFailureException(e);
}
}

private void applyLowerBounds(Set<String> packageWithError, Map<String, Version> lowestPackageVersion)
throws IOException {
MutableBundleManifest manifest = MutableBundleManifest.read(manifestFile);
Map<String, String> exportedPackagesVersion = manifest.getExportedPackagesVersion();
Map<String, String> updates = new HashMap<>();
for (String packageName : packageWithError) {
Version lowestVersion = lowestPackageVersion.getOrDefault(packageName, Version.emptyVersion);
String current = exportedPackagesVersion.get(packageName);
if (current == null) {
updates.put(packageName, String.format("[%s,%d)", lowestVersion, (lowestVersion.getMajor() + 1)));
} else {
VersionRange range = VersionRange.valueOf(current);
Version right = range.getRight();
updates.put(packageName, String.format("[%s,%s%c", lowestVersion, right, range.getRightType()));
}
}
manifest.updateImportedPackageVersions(updates);
MutableBundleManifest.write(manifest, manifestFile);
}

private Map<String, String> getVersionInfo(GenericInfo genericInfo, String versionAttribute) {
Map<String, String> directives = new HashMap<>(genericInfo.getDirectives());
String filter = directives.remove(Namespace.REQUIREMENT_FILTER_DIRECTIVE);
FilterImpl filterImpl;
try {
filterImpl = FilterImpl.newInstance(filter);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter directive", e); //$NON-NLS-1$
}
return filterImpl.getStandardOSGiAttributes(versionAttribute);
}

private ModuleRevisionBuilder readOSGiInfo(File file) throws MojoFailureException {
OsgiManifest manifest = bundleReader.loadManifest(file);
ModuleRevisionBuilder builder;
try {
builder = OSGiManifestBuilderFactory.createBuilder(manifest.getHeaders());
} catch (BundleException e) {
throw new MojoFailureException(e);
}
return builder;
}

private JrtClasses getJRTClassResolver() {
String profileName = projectManager.getExecutionEnvironments(project, session).findFirst()
.map(ee -> ee.getProfileName()).orElse(null);
if (profileName != null) {
OSGiJavaToolchain osgiToolchain = toolchainProvider.getToolchain(profileName).orElse(null);
if (osgiToolchain != null) {
return new JrtClasses(osgiToolchain.getJavaHome());
}
}
// use running jvm
return new JrtClasses(null);
}

private static record DependencyVersionProblem(String message, Collection<String> references,
List<MethodSignature> provided) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;

public class ClassCollection implements Function<String, Optional<ClassMethods>>, Consumer<ClassMethods> {

private Map<String, ClassMethods> classLookupMap = new HashMap<>();

@Override
public Optional<ClassMethods> apply(String className) {
return Optional.ofNullable(classLookupMap.get(className));
}

public Stream<MethodSignature> provides() {

return classLookupMap.values().stream().distinct().flatMap(cm -> cm.provides());
}

public List<MethodSignature> get(String className) {
return apply(className).stream().flatMap(cm -> cm.provides()).toList();
}

public Function<String, Optional<ClassMethods>> chain(Function<String, Optional<ClassMethods>> chained) {
return cls -> {
return apply(cls).or(() -> chained.apply(cls));
};
}

@Override
public void accept(ClassMethods methods) {
methods.definitions().forEach(def -> {
classLookupMap.put(def.name(), methods);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

record ClassDef(int access, String name, String signature, String superName, String[] interfaces) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

record ClassMethodSignature(ClassDef clazz, int access, String name, String descriptor, String signature) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
* Analyze a class about methods it possibly provides to callers
*/
public class ClassMethods {

private List<ClassDef> classDefs = new ArrayList<>();
private List<ClassMethodSignature> signatures = new ArrayList<>();
private Function<String, Optional<ClassMethods>> supplier;
private Set<MethodSignature> collect;

public ClassMethods(byte[] classbytes, Function<String, Optional<ClassMethods>> supplier) {
this.supplier = supplier;
ClassReader reader = new ClassReader(classbytes);
reader.accept(new ClassVisitor(DependencyAnalyzer.ASM_API) {

private ClassDef classDef;

@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
if ((access & Opcodes.ACC_PRIVATE) != 0) {
// private methods can not be called
return;
}
classDef = new ClassDef(access, name, signature, superName, interfaces);
classDefs.add(classDef);
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
signatures.add(new ClassMethodSignature(classDef, access, name, descriptor, signature));
return null;
}
}, ClassReader.SKIP_FRAMES);
}

Stream<ClassDef> definitions() {
return classDefs.stream();
}

/**
* @return a stream of all method signatures this class can provide
*/
Stream<MethodSignature> provides() {
// all methods declared by our class are provided
Stream<MethodSignature> declared = signatures.stream().map(cms -> {
return new MethodSignature(cms.clazz().name(), cms.name(), cms.descriptor());
});
// and from the super class, transformed with our class as the classname
Stream<MethodSignature> supermethods = classDefs.stream().flatMap(cd -> {
return Optional.ofNullable(cd.superName()).flatMap(cn -> findRef(cn)).stream().flatMap(cm -> cm.provides())
// constructors are not inheritable
.filter(ms -> !ms.isContructor()).map(ms -> inherit(cd, ms));
});
// and possibly from interfaces
Stream<MethodSignature> interfaces = classDefs.stream().flatMap(cd -> {
return Optional.ofNullable(cd.interfaces()).stream().flatMap(Arrays::stream)
.flatMap(cn -> findRef(cn).stream()).flatMap(cm -> cm.provides())
// constructors are not inheritable
.filter(ms -> !ms.isContructor()).map(ms -> inherit(cd, ms));
});
Stream<MethodSignature> inherited = Stream.concat(supermethods, interfaces);
return Stream.concat(declared, inherited).distinct();
}

private MethodSignature inherit(ClassDef cd, MethodSignature ms) {
return new MethodSignature(cd.name(), ms.methodName(), ms.signature());
}

private Optional<ClassMethods> findRef(String cn) {
return supplier.apply(cn);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
* Analyze code that is used by the class itself
*/
public class ClassUsage {

private Set<MethodSignature> usedMethodSignatures = new HashSet<>();
private Map<MethodSignature, Collection<String>> classRef = new HashMap<>();

public ClassUsage(byte[] classbytes, JrtClasses jrt) {
ClassReader reader = new ClassReader(classbytes);
reader.accept(new ClassVisitor(Opcodes.ASM9) {

private String className;

@Override
public void visit(int version, int access, String name, String signature, String superName,
String[] interfaces) {
this.className = name.replace('/', '.');
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
return new MethodVisitor(Opcodes.ASM9) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor,
boolean isInterface) {
if (jrt.apply(owner).isPresent()) {
// ignore references to java provided classes
return;
}
MethodSignature sig = new MethodSignature(owner, name, descriptor);
classRef.computeIfAbsent(sig, nil -> new TreeSet<>()).add(className);
usedMethodSignatures.add(sig);
}
};
}
}, ClassReader.SKIP_FRAMES);
}

public Stream<MethodSignature> signatures() {
return usedMethodSignatures.stream();
}

public Collection<String> classRef(MethodSignature mthd) {
return classRef.getOrDefault(mthd, List.of());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.maven.plugin.MojoFailureException;
import org.eclipse.tycho.ArtifactDescriptor;
import org.eclipse.tycho.ArtifactType;
import org.eclipse.tycho.DependencyArtifacts;
import org.objectweb.asm.Opcodes;

public class DependencyAnalyzer {

static final String CLASS_SUFFIX = ".class";
static final int ASM_API = Opcodes.ASM9;

public static String getPackageName(String className) {
className = className.replace('/', '.');
int idx = className.lastIndexOf('.');
if (idx > 0) {
return className.substring(0, idx);
}
return className;
}

public static Function<String, Optional<ClassMethods>> createDependencyClassResolver(JrtClasses jrtClassResolver,
DependencyArtifacts artifacts) throws MojoFailureException {
ClassCollection allClassMethods = new ClassCollection();
Function<String, Optional<ClassMethods>> function = allClassMethods.chain(jrtClassResolver);
List<ArtifactDescriptor> list = artifacts.getArtifacts(ArtifactType.TYPE_ECLIPSE_PLUGIN);
for (ArtifactDescriptor descriptor : list) {
File file = descriptor.fetchArtifact().join();
analyzeProvides(file, function, allClassMethods);
}
return function;
}

public static List<ClassUsage> analyzeUsage(File file, JrtClasses jre) throws MojoFailureException {
List<ClassUsage> usages = new ArrayList<>();
try {
try (JarFile jar = new JarFile(file)) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
if (name.endsWith(CLASS_SUFFIX)) {
InputStream stream = jar.getInputStream(jarEntry);
usages.add(new ClassUsage(stream.readAllBytes(), jre));
}
}
}
return usages;
} catch (IOException e) {
throw new MojoFailureException(e);
}
}

public static ClassCollection analyzeProvides(File file, Function<String, Optional<ClassMethods>> classResolver,
Consumer<ClassMethods> consumer) throws MojoFailureException {
try {
ClassCollection local = new ClassCollection();
Function<String, Optional<ClassMethods>> resolver = local.chain(classResolver);
try (JarFile jar = new JarFile(file)) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String name = jarEntry.getName();
if (name.endsWith(CLASS_SUFFIX)) {
InputStream stream = jar.getInputStream(jarEntry);
ClassMethods methods = new ClassMethods(stream.readAllBytes(), resolver);
if (consumer != null) {
consumer.accept(methods);
}
local.accept(methods);
}
}
}
return local;
} catch (IOException e) {
throw new MojoFailureException(e);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Stream;


/**
* Lookup of all classes provided by the java runtime
*/
public class JrtClasses implements Function<String, Optional<ClassMethods>> {

private Path rootPath;
private Map<String, Optional<ClassMethods>> cache = new ConcurrentHashMap<>();

public JrtClasses(String javaHome) {
try {
Map<String, String> map;
if (javaHome != null) {
map = Map.of("java.home", javaHome);
} else {
map = Map.of();
}
FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), map);
rootPath = fs.getPath("/packages");
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public Optional<ClassMethods> apply(String className) {
if (rootPath == null) {
return Optional.empty();
}
return cache.computeIfAbsent(className.replace('.', '/'), path -> lookupJreClass(className));
}

private Optional<ClassMethods> lookupJreClass(String classPath) {
// Paths in the "jrt:/" NIO filesystem are of this form:
//
// /modules/$MODULE/$PATH
// /packages/$PACKAGE/$MODULE
//
// where $PACKAGE is a package name (e.g., "java.lang"). A path of the
// second form names a symbolic link which, in turn, points to the
// directory under /modules that contains a module that defines that
// package. Example:
//
// /packages/java.lang/java.base -> /modules/java.base
//
// To find java/sql/Array.class without knowing its module you look up
// /packages/java.sql, which is a directory, and enumerate its entries.
// In this case there will be just one entry, a symbolic link named
// "java.sql", which will point to /modules/java.sql, which will contain
// java/sql/Array.class.
//
String packageName = DependencyAnalyzer.getPackageName(classPath);
Path modulesPath = rootPath.resolve(packageName);
if (Files.isDirectory(modulesPath)) {
try (Stream<Path> module = Files.list(modulesPath)) {
Iterator<Path> iterator = module.iterator();
while (iterator.hasNext()) {
Path modulePath = iterator.next();
Path classFile = modulePath.resolve(classPath + ".class");
if (Files.isRegularFile(classFile)) {
return Optional.of(new ClassMethods(Files.readAllBytes(classFile), JrtClasses.this));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return Optional.empty();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.analyze;

public record MethodSignature(String className, String methodName, String signature)
implements Comparable<MethodSignature> {

public String packageName() {
String cn = className();
return DependencyAnalyzer.getPackageName(cn);
}

public String id() {
return className() + "#" + methodName() + signature();
}

@Override
public int compareTo(MethodSignature o) {
return id().compareTo(o.id());
}

public boolean isContructor() {
return "<clinit>".equals(methodName) || "<init>".equals(methodName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.provider;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.logging.Logger;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.spi.p2.publisher.PublisherHelper;
import org.eclipse.tycho.artifacts.ArtifactVersion;
import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
import org.eclipse.tycho.copyfrom.oomph.P2Index;
import org.eclipse.tycho.copyfrom.oomph.P2Index.Repository;
import org.eclipse.tycho.copyfrom.oomph.P2IndexImpl;
import org.eclipse.tycho.core.resolver.target.ArtifactMatcher;
import org.eclipse.tycho.p2maven.repository.P2RepositoryManager;
import org.eclipse.tycho.p2maven.transport.TransportCacheConfig;
import org.osgi.framework.VersionRange;

/**
* {@link ArtifactVersionProvider} using eclipse index
*/
@Named
public class EclipseIndexArtifactVersionProvider implements ArtifactVersionProvider {

private P2Index p2Index;
private P2RepositoryManager repositoryManager;
private Logger logger;

@Inject
public EclipseIndexArtifactVersionProvider(TransportCacheConfig cacheConfig, P2RepositoryManager repositoryManager,
Logger logger) {
this.repositoryManager = repositoryManager;
this.logger = logger;
p2Index = new P2IndexImpl(new File(cacheConfig.getCacheLocation(), "index"));
}

@Override
public Stream<ArtifactVersion> getPackageVersions(IInstallableUnit unit, String packageName,
VersionRange versionRange, MavenProject mavenProject) {
Map<Repository, Set<Version>> map = p2Index.lookupCapabilities(PublisherHelper.CAPABILITY_NS_JAVA_PACKAGE,
packageName);
Map<Version, List<Repository>> found = new HashMap<>();
map.entrySet().forEach(entry -> {
entry.getValue().stream().filter(v -> v.isOSGiCompatible()).forEach(v -> {
found.computeIfAbsent(v, x -> new ArrayList<>()).add(entry.getKey());
});
});
String id = unit.getId();
return found.entrySet().stream().map(entry -> {
return new EclipseIndexArtifactVersion(entry.getValue(), id, packageName, entry.getKey(), logger);
}).filter(eia -> versionRange.includes(eia.getVersion()))
.sorted(Comparator.comparing(EclipseIndexArtifactVersion::getVersion).reversed())
.map(ArtifactVersion.class::cast);
}

private class EclipseIndexArtifactVersion implements ArtifactVersion {

private Version version;
private String packageName;
private Path tempFile;
private String unitId;
private List<Repository> repositories;
private org.osgi.framework.Version osgiVersion;
private Optional<IInstallableUnit> unit;
private Repository unitRepo;

public EclipseIndexArtifactVersion(List<Repository> repositories, String unitId, String packageName,
Version version, Logger logger) {
osgiVersion = org.osgi.framework.Version.parseVersion(version.getOriginal());
this.repositories = repositories;

this.unitId = unitId;
this.packageName = packageName;
this.version = version;
}

@Override
public Path getArtifact() {
if (tempFile == null) {
IInstallableUnit unit = getUnit().orElse(null);
if (unit != null) {
Path file;
try {
file = Files.createTempFile(unit.getId(), ".jar");
} catch (IOException e) {
return null;
}
file.toFile().deleteOnExit();
List<Repository> list = new ArrayList<>(repositories);
if (unitRepo != null) {
list.remove(unitRepo);
list.add(0, unitRepo);
}
for (Repository repository : list) {
try {
org.apache.maven.model.Repository r = new org.apache.maven.model.Repository();
r.setUrl(repository.getLocation().toString());
try (OutputStream stream = Files.newOutputStream(file)) {
repositoryManager.downloadArtifact(unit, repositoryManager.getArtifactRepository(r),
stream);
return tempFile = file;
}
} catch (Exception e) {
logger.error("Fetch artifact for unit " + unit.getId() + " from " + repository.getLocation()
+ " failed: " + e);
}
}
file.toFile().delete();
}
}
return tempFile;
}

private Optional<IInstallableUnit> getUnit() {
if (unit == null) {
for (Repository repository : repositories) {
try {
org.apache.maven.model.Repository r = new org.apache.maven.model.Repository();
r.setUrl(repository.getLocation().toString());
IMetadataRepository metadataRepository = repositoryManager.getMetadataRepository(r);
Optional<IInstallableUnit> optional = ArtifactMatcher.findPackage(packageName,
metadataRepository.query(QueryUtil.createIUQuery(unitId), null), version);
if (optional.isPresent()) {
this.unitRepo = repository;
return unit = optional;
} else {
// if we have a package exported from many bundles it might be the case that the
// actual unit we look for is not found, so we need to try on
logger.debug(
"Package " + packageName + " not found in metadata of " + repository.getLocation());
}
} catch (Exception e) {
logger.error("Fetch metadata from " + repository.getLocation() + ":: " + e);
}
}
unit = Optional.empty();
}
return Objects.requireNonNullElse(unit, Optional.empty());
}

@Override
public org.osgi.framework.Version getVersion() {
return osgiVersion;
}

@Override
public String toString() {
if (unit != null && unit.isPresent()) {
return getVersion() + " (" + unit.get() + ")";
}
return getVersion().toString();
}

@Override
public String getProvider() {
return getUnit().map(unit -> unit.getId() + " " + unit.getVersion()).orElse(null);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.baseline.provider;

import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import javax.inject.Inject;
import javax.inject.Named;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.SessionScoped;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.version.Version;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.osgi.container.ModuleRevisionBuilder;
import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo;
import org.eclipse.osgi.container.builders.OSGiManifestBuilderFactory;
import org.eclipse.tycho.TychoConstants;
import org.eclipse.tycho.artifacts.ArtifactVersion;
import org.eclipse.tycho.artifacts.ArtifactVersionProvider;
import org.eclipse.tycho.core.osgitools.BundleReader;
import org.eclipse.tycho.core.osgitools.OsgiManifest;
import org.osgi.framework.BundleException;
import org.osgi.framework.VersionRange;
import org.osgi.framework.namespace.PackageNamespace;

/**
* A {@link ArtifactVersionProvider} that checks maven repository for possible
* candidates
*/
@Named
@SessionScoped
public class MavenArtifactVersionProvider implements ArtifactVersionProvider {

private MavenSession session;
private RepositorySystem repoSystem;
private BundleReader bundleReader;

@Inject
public MavenArtifactVersionProvider(MavenSession session, RepositorySystem repoSystem, BundleReader bundleReader) {
this.session = session;
this.repoSystem = repoSystem;
this.bundleReader = bundleReader;
}

@Override
public Stream<ArtifactVersion> getPackageVersions(IInstallableUnit unit, String packageName,
VersionRange versionRange, MavenProject mavenProject) {
String groupId = unit.getProperty(TychoConstants.PROP_GROUP_ID);
String artifactId = unit.getProperty(TychoConstants.PROP_ARTIFACT_ID);
String classifier = unit.getProperty(TychoConstants.PROP_CLASSIFIER);
if (groupId != null && artifactId != null && !"sources".equals(classifier)) {
List<RemoteRepository> repositories = RepositoryUtils.toRepos(mavenProject.getRemoteArtifactRepositories());
DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId, classifier, "jar", "[0,)");
// as we have no mean for a package version in maven we can only fetch all
// versions an check if the match
VersionRangeRequest rangeRequest = new VersionRangeRequest(artifact, repositories, "");
try {
VersionRangeResult range = repoSystem.resolveVersionRange(session.getRepositorySession(), rangeRequest);
// now we sort from highest > lowest version
List<Version> versions = range.getVersions().stream()
.sorted(Comparator.<Version>naturalOrder().reversed()).toList();
return versions.stream()
.map(v -> new MavenPackageArtifactVersion(artifact, v, packageName, repositories))
.filter(mav -> mav.getVersion() != null)
// and drop all until we find a matching version
.dropWhile(mav -> !versionRange.includes(mav.getVersion()))
// and stop when we find the first non matching version
.takeWhile(mav -> versionRange.includes(mav.getVersion()))
// cast to make compiler happy
.map(ArtifactVersion.class::cast);
} catch (VersionRangeResolutionException e) {
// can't provide any useful data then...
}
}
return Stream.empty();
}

private class MavenPackageArtifactVersion implements ArtifactVersion {

private Artifact artifact;
private List<RemoteRepository> repositories;
private Path path;
private String packageName;
private org.osgi.framework.Version packageVersion;

public MavenPackageArtifactVersion(Artifact artifact, Version version, String packageName,
List<RemoteRepository> repositories) {
this.artifact = new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(),
artifact.getExtension(), version.toString());
this.packageName = packageName;
this.repositories = repositories;
}

@Override
public Path getArtifact() {
try {
ArtifactRequest request = new ArtifactRequest(artifact, repositories, "");
ArtifactResult result = repoSystem.resolveArtifact(session.getRepositorySession(), request);
path = result.getArtifact().getFile().toPath();
} catch (ArtifactResolutionException e) {
}
return path;
}

@Override
public org.osgi.framework.Version getVersion() {
if (packageVersion == null) {
ModuleRevisionBuilder builder = readOSGiInfo(getArtifact());
if (builder != null) {
List<GenericInfo> capabilities = builder.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE);
for (GenericInfo info : capabilities) {
Map<String, Object> attributes = info.getAttributes();
if (packageName.equals(attributes.get(PackageNamespace.PACKAGE_NAMESPACE))) {
packageVersion = (org.osgi.framework.Version) attributes.getOrDefault(
PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE,
org.osgi.framework.Version.emptyVersion);
}
}
}
}
return packageVersion;
}

@Override
public String toString() {
return getVersion() + " (maven artifact " + artifact + ")";
}

@Override
public String getProvider() {
ModuleRevisionBuilder info = readOSGiInfo(getArtifact());
if (info != null) {
return info.getSymbolicName() + " " + info.getVersion();
}
return null;
}

}

private ModuleRevisionBuilder readOSGiInfo(Path path) {
if (path != null) {
OsgiManifest manifest = bundleReader.loadManifest(path.toFile());
try {
return OSGiManifestBuilderFactory.createBuilder(manifest.getHeaders());
} catch (BundleException e) {
}
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -8,15 +8,14 @@
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.oomph.p2.internal.core;

import org.eclipse.emf.common.util.URI;

import org.eclipse.equinox.p2.metadata.Version;
package org.eclipse.tycho.copyfrom.oomph;

import java.util.Map;
import java.util.Set;

import org.eclipse.emf.common.util.URI;
import org.eclipse.equinox.p2.metadata.Version;

/**
* @author Eike Stepper
*/
@@ -26,8 +25,6 @@ public interface P2Index

public static final int COMPOSED_REPOSITORY = 1;

public static final P2Index INSTANCE = P2IndexImpl.INSTANCE;

public Repository[] getRepositories();

public Map<String, Set<String>> getCapabilities();

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -14,11 +14,13 @@
package org.eclipse.tycho.core.resolver.target;

import java.util.AbstractMap.SimpleEntry;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.IProvidedCapability;
@@ -27,6 +29,7 @@
import org.eclipse.equinox.p2.publisher.eclipse.Feature;
import org.eclipse.equinox.p2.publisher.eclipse.FeatureEntry;
import org.eclipse.equinox.p2.publisher.eclipse.FeaturesAction;
import org.eclipse.equinox.p2.query.CollectionResult;
import org.eclipse.equinox.p2.query.IQuery;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
@@ -53,16 +56,31 @@ public static IInstallableUnit resolveReference(String type, String id, VersionR
return ius.iterator().next();
}
if (PublisherHelper.CAPABILITY_NS_JAVA_PACKAGE.equals(type)) {
return ius.stream().flatMap(iu -> getPackageVersion(iu, id).map(v -> new SimpleEntry<>(iu, v)).stream())
.max((o1, o2) -> {
return o1.getValue().compareTo(o2.getValue());
}).map(Entry::getKey).orElse(null);
return findPackage(id, ius).orElse(null);
} else {
return ius.iterator().next();
}
}

private static Optional<Version> getPackageVersion(IInstallableUnit unit, String packageName) {
public static Optional<IInstallableUnit> findPackage(String packageName, Collection<IInstallableUnit> ius) {
return findPackage(packageName, new CollectionResult<>(ius), null);

}

public static Optional<IInstallableUnit> findPackage(String packageName, IQueryResult<IInstallableUnit> query,
Version version) {
Stream<SimpleEntry<IInstallableUnit, Version>> stream = query.stream()
.flatMap(iu -> getPackageVersion(iu, packageName).map(v -> new SimpleEntry<>(iu, v)).stream());
if (version == null) {
return stream.max((o1, o2) -> {
return o1.getValue().compareTo(o2.getValue());
}).map(Entry::getKey);
} else {
return stream.filter(e -> version.equals(e.getValue())).map(Entry::getKey).findFirst();
}
}

public static Optional<Version> getPackageVersion(IInstallableUnit unit, String packageName) {

return unit.getProvidedCapabilities().stream()
.filter(capability -> PublisherHelper.CAPABILITY_NS_JAVA_PACKAGE.equals(capability.getNamespace()))
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.artifacts;

import java.nio.file.Path;

import org.osgi.framework.Version;

public interface ArtifactVersion {

Path getArtifact();

Version getVersion();

String getProvider();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christoph Läubrich - initial API and implementation
*******************************************************************************/
package org.eclipse.tycho.artifacts;

import java.util.stream.Stream;

import org.apache.maven.project.MavenProject;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.osgi.framework.VersionRange;

public interface ArtifactVersionProvider {

Stream<ArtifactVersion> getPackageVersions(IInstallableUnit unit, String packageName, VersionRange versionRange,
MavenProject mavenProject);

}

0 comments on commit 2b72bea

Please sign in to comment.