From 644c52e30cf8d94cf88758b02428f8b4597f8937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Thu, 15 Feb 2024 18:12:20 +0100 Subject: [PATCH] Add a mojo that generates a target platform from the current reactor If one currently uses p2 repositories in Tycho this is quite convenient as Tycho just fetches what ever is required by the project. As PDE has no such feature yet, it is on the other hand not very usable in the IDE itself or one needs to manage a second target platform for development. This now adds a new tycho-dependency-tools-plugin:generate-target mojo that collects all dependencies of the reactor, maps them to update sites whenever possible and reduces the set then to the root units that are at the top of the dependency chain. Finally these units are tried to be matched to features of the site to finally select a set of units that are suitable for target resolution with planner. --- .../repository/P2RepositoryManager.java | 2 +- .../org/eclipse/tycho/core/TychoProject.java | 2 + .../tycho/core/TychoProjectManager.java | 7 +- .../core/osgitools/AbstractTychoProject.java | 5 + .../core/osgitools/BaselineServiceImpl.java | 2 +- .../tycho/extras/pde/GenerateTargetMojo.java | 287 ++++++++++++++++++ 6 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 tycho-extras/tycho-dependency-tools-plugin/src/main/java/org/eclipse/tycho/extras/pde/GenerateTargetMojo.java diff --git a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java index 95b7295d5a..18f7cdfcb4 100644 --- a/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java +++ b/p2-maven-plugin/src/main/java/org/eclipse/tycho/p2maven/repository/P2RepositoryManager.java @@ -131,7 +131,7 @@ public IQueryable getCompositeMetadataRepository(Collection getTargetEnvironments(MavenProject project) { TychoProject tychoProject = projectTypes.get(project.getPackaging()); if (tychoProject != null) { - //these will already be filtered at reading the target configuration + //these will already be filtered at reading the target configuration return getTargetPlatformConfiguration(project).getEnvironments(); } //if no tycho project, just assume the default running environment @@ -199,6 +200,10 @@ public Optional getTychoProject(MavenProject project) { return Optional.ofNullable(projectTypes.get(project.getPackaging())); } + public Optional getDependencyArtifacts(MavenProject project) { + return getTychoProject(project).map(tp -> tp.getDependencyArtifacts(project)); + } + public Optional getTychoProject(ReactorProject project) { if (project == null) { return Optional.empty(); diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/AbstractTychoProject.java b/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/AbstractTychoProject.java index 8a4a0cf2ae..26ce8bb6b2 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/AbstractTychoProject.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/AbstractTychoProject.java @@ -72,6 +72,11 @@ public abstract class AbstractTychoProject extends AbstractLogEnabled implements @Requirement(hint = "p2") protected DependencyResolver dependencyResolver; + @Override + public DependencyArtifacts getDependencyArtifacts(MavenProject project) { + return getDependencyArtifacts(DefaultReactorProject.adapt(project)); + } + @Override public DependencyArtifacts getDependencyArtifacts(ReactorProject reactorProject) { return reactorProject.computeContextValue(TychoConstants.CTX_DEPENDENCY_ARTIFACTS, () -> { diff --git a/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/BaselineServiceImpl.java b/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/BaselineServiceImpl.java index 2ebd4931f4..6f3ce55b45 100644 --- a/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/BaselineServiceImpl.java +++ b/tycho-core/src/main/java/org/eclipse/tycho/core/osgitools/BaselineServiceImpl.java @@ -75,7 +75,7 @@ public Map getProjectBaseline(Collection projects = mavenSession.getProjects(); + List reactorDependencies = projects.stream().parallel().unordered().flatMap(project -> { + MavenSession old = legacySupport.getSession(); + try { + MavenSession clone = mavenSession.clone(); + clone.setCurrentProject(project); + legacySupport.setSession(clone); + return projectManager.getDependencyArtifacts(project).map(DependencyArtifacts::getNonReactorUnits) + .stream().flatMap(Collection::stream).filter(iu -> { + if (iu.getId().endsWith(".source")) { + return false; + } + if (iu.getId().endsWith(".feature.jar")) { + return false; + } + return true; + }); + } finally { + legacySupport.setSession(old); + } + }).distinct().toList(); + List repoList = projects.stream().flatMap(project -> { + Stream pomRepos = project.getRepositories().stream().filter(repo -> "p2".equals(repo.getLayout())) + .map(repo -> repo.getUrl()); + return pomRepos; + }).distinct().sorted().toList(); + log.info("Found " + reactorDependencies.size() + " dependencies and " + repoList.size() + + " possible repositories: "); + Map> repo2unitMap = new LinkedHashMap<>(); + Set notFound = new HashSet<>(reactorDependencies); + for (String repository : repoList) { + log.info("\tScanning " + repository + "..."); + try { + IMetadataRepository metadataRepository = repositoryManager + .getMetadataRepository(new MavenRepositoryLocation(null, URI.create(repository))); + Set units = new HashSet<>(); + for (IInstallableUnit unit : reactorDependencies) { + if (metadataRepository.contains(unit)) { + units.add(unit); + notFound.remove(unit); + } + } + if (units.size() > 0) { + repo2unitMap.put(metadataRepository, units); + log.info("\tFound: " + units.size() + " dependencies in this repository!"); + } + } catch (ProvisionException e) { + throw new MojoFailureException("can't load repository " + repository, e); + } + } + if (notFound.size() > 0) { + log.info(notFound.size() + " dependencies where not mapped to a repository:"); + for (IInstallableUnit unit : notFound) { + log.info("\t" + unit); + } + } + log.info("Generate Target ..."); + StringBuilder builder = new StringBuilder("\n" + + "\n\n\t\n"); + for (Entry> entry : repo2unitMap.entrySet()) { + builder.append("\t\t\n"); + Set requiredUnits = entry.getValue(); + //First step is to remove everything that is already required by something... + Set alreayTransitiveRequired = computeRequired(requiredUnits); + requiredUnits.removeAll(alreayTransitiveRequired); + IMetadataRepository repository = entry.getKey(); + if (!hasOnlyFeatures(requiredUnits)) { + Set strict = new HashSet<>(requiredUnits); + //Next is we try to find all features that do require something we still want to have + IQueryResult features = repository.query(QueryUtil.createIUGroupQuery(), null); + Set providingFeatures = computeFeatureRequires(features, requiredUnits); + requiredUnits.addAll(providingFeatures); + //Now a final reduction step + Set transitiveRequiredFinal = computeRequired(requiredUnits); + requiredUnits.removeAll(transitiveRequiredFinal); + if (!strict.equals(requiredUnits)) { + builder.append("\t\t\t\n"); + for (IInstallableUnit unit : strict) { + builder.append("\t\t\t\n"); + } + builder.append( + "\n\t\t\t\n"); + } + } + for (IInstallableUnit unit : requiredUnits) { + builder.append("\t\t\t"); + addUnit(unit, builder); + builder.append("\n"); + } + builder.append("\t\t\t\n"); + builder.append("\t\t\n"); + } + if (notFound.size() > 0) { + List osgiMaven = new ArrayList<>(); + List wrappedMaven = new ArrayList<>(); + for (IInstallableUnit mavenUnit : notFound) { + String osgi = getMavenDependency(mavenUnit, builder, TychoConstants.PROP_GROUP_ID, + TychoConstants.PROP_ARTIFACT_ID, TychoConstants.PROP_VERSION); + if (osgi == null) { + String wrapped = getMavenDependency(mavenUnit, builder, TychoConstants.PROP_WRAPPED_GROUP_ID, + TychoConstants.PROP_WRAPPED_ARTIFACT_ID, TychoConstants.PROP_WRAPPED_VERSION); + if (wrapped != null) { + wrappedMaven.add(wrapped); + } else { + log.warn(mavenUnit + " can not be represented in the target at all!"); + } + } else { + osgiMaven.add(osgi); + } + } + createMavenLocation(osgiMaven, "error", builder); + createMavenLocation(wrappedMaven, "generate", builder); + } + builder.append("\t\n"); + try { + targetFile.getParentFile().mkdirs(); + Files.writeString(targetFile.toPath(), builder, StandardCharsets.UTF_8); + log.info("Target is written to " + targetFile.getAbsolutePath()); + } catch (IOException e) { + throw new MojoFailureException("writing target file failed", e); + } + } + + private void createMavenLocation(List artifacts, String mm, StringBuilder builder) { + builder.append("\t\t\n"); + for (String artifact : artifacts) { + builder.append("\t\t\t"); + builder.append(artifact); + builder.append("\n"); + } + builder.append("\t\t\n"); + } + + private String getMavenDependency(IInstallableUnit mavenUnit, StringBuilder builder, String groupProperty, + String artifactProperty, String versionProperty) { + String groupId = mavenUnit.getProperty(groupProperty); + String artifactId = mavenUnit.getProperty(artifactProperty); + String version = mavenUnit.getProperty(versionProperty); + if (groupId != null && artifactId != null && version != null) { + return String.format( + "\n\t\t\t\t%s\n\t\t\t\t%s\n\t\t\t\t%s\n\t\t\t", + groupId, artifactId, version); + } + return null; + } + + private boolean hasOnlyFeatures(Set requiredUnits) { + for (IInstallableUnit unit : requiredUnits) { + if (!Boolean.TRUE.toString().equals(unit.getProperty(QueryUtil.PROP_TYPE_GROUP))) { + return false; + } + } + return true; + } + + private void addUnit(IInstallableUnit unit, StringBuilder builder) { + builder.append(""); + } + + private Set computeFeatureRequires(IQueryResult features, + Set units) { + HashSet result = new HashSet<>(); + outer: for (IInstallableUnit feature : features) { + if (units.contains(feature)) { + continue; + } + if (Boolean.TRUE.toString() + .equals(feature.getProperty(MetadataFactory.InstallableUnitDescription.PROP_TYPE_PRODUCT))) { + continue; + } + for (IRequirement featureRequirement : feature.getRequirements()) { + for (IInstallableUnit unit : units) { + if (unit.satisfies(featureRequirement)) { + result.add(feature); + continue outer; + } + } + } + } + return result; + } + + private Set computeRequired(Set units) { + Set required = new HashSet<>(); + for (IInstallableUnit base : units) { + for (IRequirement requirement : base.getRequirements()) { + for (IInstallableUnit other : units) { + if (other == base) { + continue; + } + if (other.satisfies(requirement)) { + required.add(other); + } + } + } + } + return required; + } + +}