diff --git a/independent-projects/tools/devtools-common/pom.xml b/independent-projects/tools/devtools-common/pom.xml index 2e5832fddb6aff..d9cbf09b675e9d 100644 --- a/independent-projects/tools/devtools-common/pom.xml +++ b/independent-projects/tools/devtools-common/pom.xml @@ -24,8 +24,59 @@ + + io.quarkus quarkus-devtools-registry-client diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredArtifact.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredArtifact.java new file mode 100644 index 00000000000000..fc68b85366b47f --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredArtifact.java @@ -0,0 +1,153 @@ +package io.quarkus.devtools.project.state; + +import java.util.Objects; + +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; + +public class ConfiguredArtifact { + + public static ConfiguredArtifact jar(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue version) { + return jar(groupId, artifactId, version, false); + } + + public static ConfiguredArtifact jar(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue version, + boolean local) { + return of(groupId, artifactId, ConfiguredValue.of(ArtifactCoords.DEFAULT_CLASSIFIER), + ConfiguredValue.of(ArtifactCoords.TYPE_JAR), version, local); + } + + public static ConfiguredArtifact pom(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue version) { + return pom(groupId, artifactId, version, false); + } + + public static ConfiguredArtifact pom(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue version, + boolean local) { + return of(groupId, artifactId, ConfiguredValue.of(ArtifactCoords.DEFAULT_CLASSIFIER), + ConfiguredValue.of(ArtifactCoords.TYPE_POM), version, local); + } + + public static ConfiguredArtifact of(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue classifier, + ConfiguredValue type, ConfiguredValue version) { + return of(groupId, artifactId, classifier, type, version, false); + } + + public static ConfiguredArtifact of(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue classifier, + ConfiguredValue type, ConfiguredValue version, boolean local) { + return new ConfiguredArtifact(groupId, artifactId, classifier, type, version, local); + } + + private final ConfiguredValue groupId; + private final ConfiguredValue artifactId; + private final ConfiguredValue classifier; + private final ConfiguredValue type; + private final ConfiguredValue version; + private final boolean local; + private ArtifactKey key; + + private ConfiguredArtifact(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue classifier, + ConfiguredValue type, ConfiguredValue version, boolean local) { + super(); + this.groupId = groupId; + this.artifactId = artifactId; + this.classifier = classifier; + this.type = type; + this.version = version; + this.local = local; + } + + public ConfiguredValue getGroupId() { + return groupId; + } + + public ConfiguredValue getArtifactId() { + return artifactId; + } + + public ConfiguredValue getClassifier() { + return classifier; + } + + public ConfiguredValue getType() { + return type; + } + + public ConfiguredValue getVersion() { + return version; + } + + public boolean isLocal() { + return local; + } + + public boolean isManagedVersion() { + return version.getRawValue() == null || version.getRawValue().isEmpty(); + } + + public ArtifactCoords getRawCoords() { + return ArtifactCoords.of(groupId.getRawValue(), artifactId.getRawValue(), classifier.getRawValue(), + type.getRawValue(), version.getRawValue()); + } + + public ArtifactCoords getEffectiveCoords() { + return ArtifactCoords.of(groupId.getEffectiveValue(), artifactId.getEffectiveValue(), classifier.getEffectiveValue(), + type.getEffectiveValue(), version.getEffectiveValue()); + } + + public ArtifactKey getKey() { + return key == null + ? key = ArtifactKey.of(groupId.getEffectiveValue(), artifactId.getEffectiveValue(), + classifier.getEffectiveValue(), type.getEffectiveValue()) + : key; + } + + @Override + public int hashCode() { + return Objects.hash(artifactId, classifier, groupId, key, local, type, version); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ConfiguredArtifact other = (ConfiguredArtifact) obj; + return Objects.equals(artifactId, other.artifactId) && Objects.equals(classifier, other.classifier) + && Objects.equals(groupId, other.groupId) && Objects.equals(key, other.key) && local == other.local + && Objects.equals(type, other.type) && Objects.equals(version, other.version); + } + + public String toCompactString() { + final StringBuilder sb = new StringBuilder() + .append(groupId).append(':') + .append(artifactId).append(':'); + if (!classifier.isEffectivelyNull() && !classifier.getEffectiveValue().isEmpty()) { + sb.append(classifier).append(':'); + } + if (type.isEffectivelyNull() && !ArtifactCoords.TYPE_JAR.equals(type.getEffectiveValue())) { + sb.append(type).append(':'); + } + sb.append(version); + if (local) { + sb.append("[local]"); + } + return sb.toString(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder() + .append(groupId).append(':') + .append(artifactId).append(':') + .append(classifier).append(':') + .append(type).append(':') + .append(version); + if (local) { + sb.append("[local]"); + } + return sb.toString(); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredBom.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredBom.java new file mode 100644 index 00000000000000..5b2016706f79eb --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredBom.java @@ -0,0 +1,59 @@ +package io.quarkus.devtools.project.state; + +import java.util.Objects; + +public class ConfiguredBom { + + public static ConfiguredBom enforced(ConfiguredArtifact pom) { + return new ConfiguredBom(pom, false); + } + + public static ConfiguredBom enforced(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue version, + boolean local) { + return new ConfiguredBom(ConfiguredArtifact.pom(groupId, artifactId, version, local), false); + } + + public static ConfiguredBom imported(ConfiguredValue groupId, ConfiguredValue artifactId, ConfiguredValue version, + boolean local) { + return new ConfiguredBom(ConfiguredArtifact.pom(groupId, artifactId, version, local), true); + } + + private final ConfiguredArtifact bom; + private final boolean imported; + + private ConfiguredBom(ConfiguredArtifact bom, boolean imported) { + super(); + this.bom = bom; + this.imported = imported; + } + + public ConfiguredArtifact getArtifact() { + return bom; + } + + public boolean isImported() { + return imported; + } + + @Override + public int hashCode() { + return Objects.hash(bom, imported); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ConfiguredBom other = (ConfiguredBom) obj; + return Objects.equals(bom, other.bom) && imported == other.imported; + } + + @Override + public String toString() { + return (imported ? "imported " : "enforced ") + bom.toCompactString(); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredModule.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredModule.java new file mode 100644 index 00000000000000..246bc2055758c1 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredModule.java @@ -0,0 +1,97 @@ +package io.quarkus.devtools.project.state; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; +import io.quarkus.maven.dependency.ArtifactCoords; + +public class ConfiguredModule { + + public static ConfiguredModule of(WorkspaceModuleId id, Path buildFile) { + return new ConfiguredModule(id, buildFile); + } + + private final WorkspaceModuleId id; + private final Path buildFile; + private final Map platformBoms = new LinkedHashMap<>(); + private final Map managedExtensions = new LinkedHashMap<>(); + private final List directExtensionDeps = new ArrayList<>(); + private final List topTransitiveExtensionDeps = new ArrayList<>(0); + private ConfiguredArtifact quarkusPlugin; + private ConfiguredArtifact managedQuarkusPlugin; + private boolean quarkusApp; + + private ConfiguredModule(WorkspaceModuleId id, Path buildFile) { + this.id = id; + this.buildFile = buildFile; + } + + public WorkspaceModuleId getId() { + return id; + } + + public Path getBuildFile() { + return buildFile; + } + + public void addPlatformBom(ConfiguredBom bom) { + platformBoms.put(bom.getArtifact().getEffectiveCoords(), bom); + } + + public Collection getPlatformBoms() { + return platformBoms.values(); + } + + public void addManagedExtension(ConfiguredArtifact directExtensionDep) { + managedExtensions.put(directExtensionDep.getEffectiveCoords(), directExtensionDep); + } + + public Collection getManagedExtensions() { + return managedExtensions.values(); + } + + public void addDirectExtensionDep(ConfiguredArtifact directExtensionDep) { + directExtensionDeps.add(directExtensionDep); + } + + public List getDirectExtensionDeps() { + return directExtensionDeps; + } + + public void addTopTransitiveExtensionDep(ConfiguredArtifact extensionDep) { + topTransitiveExtensionDeps.add(extensionDep); + } + + public List getTopTransitiveExtensionDeps() { + return topTransitiveExtensionDeps; + } + + public void setQuarkusPlugin(ConfiguredArtifact plugin) { + this.quarkusPlugin = plugin; + } + + public ConfiguredArtifact getQuarkusPlugin() { + return quarkusPlugin; + } + + public void setManagedQuarkusPlugin(ConfiguredArtifact plugin) { + this.managedQuarkusPlugin = plugin; + } + + public ConfiguredArtifact getManagedQuarkusPlugin() { + return managedQuarkusPlugin; + } + + public void setQuarkusApplication(boolean quarkusApp) { + this.quarkusApp = quarkusApp; + } + + public boolean isQuarkusApplication() { + return quarkusApp; + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredProject.java new file mode 100644 index 00000000000000..e424e59842f3a4 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredProject.java @@ -0,0 +1,28 @@ +package io.quarkus.devtools.project.state; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class ConfiguredProject { + + private final Path projectDir; + private final List modules = new ArrayList<>(); + + public ConfiguredProject(Path projectDir) { + this.projectDir = projectDir; + } + + public Path getProjectDir() { + return projectDir; + } + + public void addModule(ConfiguredModule module) { + modules.add(module); + } + + public Collection getModules() { + return modules; + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredValue.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredValue.java new file mode 100644 index 00000000000000..7e3a3afa01a709 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ConfiguredValue.java @@ -0,0 +1,97 @@ +package io.quarkus.devtools.project.state; + +import java.util.Objects; + +public class ConfiguredValue { + + public static ConfiguredValue of(String effectiveValue) { + return new ConfiguredValue(effectiveValue, ResolvedValue.of(effectiveValue)); + } + + public static ConfiguredValue of(String rawValue, String effectiveValue) { + return new ConfiguredValue(rawValue, ResolvedValue.of(effectiveValue)); + } + + public static ConfiguredValue of(String rawValue, ResolvedValue resolvedValue) { + return new ConfiguredValue(rawValue, resolvedValue); + } + + public static boolean isPropertyExpression(final String value) { + return value != null && !value.isBlank() && value.startsWith("${") && value.endsWith("}"); + } + + public static String getPropertyName(String propertyExpr) { + return propertyExpr.substring(2, propertyExpr.length() - 1); + } + + private final ResolvedValue resolvedValue; + private final String rawValue; + + private ConfiguredValue(String rawValue, ResolvedValue resolvedValue) { + this.resolvedValue = resolvedValue; + this.rawValue = rawValue; + } + + public ResolvedValue getResolvedValue() { + return resolvedValue; + } + + public String getEffectiveValue() { + return resolvedValue == null ? null : resolvedValue.getValue(); + } + + public boolean isEffectivelyNull() { + return resolvedValue == null || resolvedValue.getValue() == null; + } + + public boolean isProperty() { + return isPropertyExpression(rawValue); + } + + public String getPropertyName() { + if (isProperty()) { + return getPropertyName(rawValue); + } + return null; + } + + public String getRawValue() { + return rawValue; + } + + public boolean isRawEffective() { + return Objects.equals(rawValue, getEffectiveValue()); + } + + @Override + public int hashCode() { + return Objects.hash(resolvedValue, rawValue); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ConfiguredValue other = (ConfiguredValue) obj; + return Objects.equals(resolvedValue, other.resolvedValue) && Objects.equals(rawValue, other.rawValue); + } + + @Override + public String toString() { + if (resolvedValue == null) { + return "null"; + } + if (isRawEffective()) { + return rawValue; + } + final StringBuilder sb = new StringBuilder(); + if (rawValue != null) { + sb.append(rawValue); + } + return sb.append("(").append(resolvedValue).append(")").toString(); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ExternalValueSource.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ExternalValueSource.java new file mode 100644 index 00000000000000..9c32f481bab027 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ExternalValueSource.java @@ -0,0 +1,71 @@ +package io.quarkus.devtools.project.state; + +import java.nio.file.Path; +import java.util.Objects; + +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; + +class ExternalValueSource implements ValueSource { + + private final WorkspaceModuleId module; + private final String propertyName; + private final Path p; + + ExternalValueSource(WorkspaceModuleId module, Path p) { + this.module = module; + this.propertyName = null; + this.p = p; + } + + ExternalValueSource(WorkspaceModuleId module, String propertyName, Path p) { + this.module = module; + this.propertyName = propertyName; + this.p = p; + } + + @Override + public WorkspaceModuleId getModule() { + return module; + } + + @Override + public boolean isExternal() { + return true; + } + + @Override + public Path getPath() { + return p; + } + + @Override + public String getProperty() { + return propertyName; + } + + @Override + public int hashCode() { + return Objects.hash(module, p, propertyName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ExternalValueSource other = (ExternalValueSource) obj; + return Objects.equals(module, other.module) && Objects.equals(p, other.p) + && Objects.equals(propertyName, other.propertyName); + } + + @Override + public String toString() { + if (propertyName == null) { + return p == null ? String.valueOf(module) : p.toString(); + } + return "${" + propertyName + "}@" + p.toString(); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/LocalValueSource.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/LocalValueSource.java new file mode 100644 index 00000000000000..604f9b06d4035a --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/LocalValueSource.java @@ -0,0 +1,71 @@ +package io.quarkus.devtools.project.state; + +import java.nio.file.Path; +import java.util.Objects; + +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; + +class LocalValueSource implements ValueSource { + + private final WorkspaceModuleId module; + private final String propertyName; + private final Path p; + + LocalValueSource(WorkspaceModuleId module, Path p) { + this.module = module; + this.propertyName = null; + this.p = p; + } + + LocalValueSource(WorkspaceModuleId module, String propertyName, Path p) { + this.module = module; + this.propertyName = propertyName; + this.p = p; + } + + @Override + public WorkspaceModuleId getModule() { + return module; + } + + @Override + public boolean isExternal() { + return false; + } + + @Override + public Path getPath() { + return p; + } + + @Override + public String getProperty() { + return propertyName; + } + + @Override + public int hashCode() { + return Objects.hash(module, p, propertyName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LocalValueSource other = (LocalValueSource) obj; + return Objects.equals(module, other.module) && Objects.equals(p, other.p) + && Objects.equals(propertyName, other.propertyName); + } + + @Override + public String toString() { + if (propertyName == null) { + return p.toString(); + } + return "${" + propertyName + "}@" + p.toString(); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/MavenProjectConfigurationLoader.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/MavenProjectConfigurationLoader.java new file mode 100644 index 00000000000000..f5ffb85177f0bb --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/MavenProjectConfigurationLoader.java @@ -0,0 +1,980 @@ +package io.quarkus.devtools.project.state; + +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.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.apache.maven.model.DependencyManagement; +import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.model.PluginManagement; +import org.apache.maven.model.Profile; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.RepositoryPolicy; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResult; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; +import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; +import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; +import io.quarkus.devtools.messagewriter.MessageWriter; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.paths.PathTree; +import io.quarkus.registry.util.PlatformArtifacts; + +public class MavenProjectConfigurationLoader { + + public static ConfiguredProject load(Path projectDir) throws Exception { + return load(projectDir, new BootstrapMavenContext( + BootstrapMavenContext.config() + .setCurrentProject(projectDir.toString()) + .setEffectiveModelBuilder(true) + .setPreferPomsFromWorkspace(true)), + MessageWriter.info()); + } + + public static ConfiguredProject load(Path projectDir, BootstrapMavenContext mavenCtx) throws Exception { + return load(projectDir, mavenCtx, MessageWriter.info()); + } + + public static ConfiguredProject load(Path projectDir, BootstrapMavenContext mavenCtx, MessageWriter log) throws Exception { + return new MavenProjectConfigurationLoader(mavenCtx, log).loadInternal(projectDir); + } + + private final Map modules = new HashMap<>(); + private final Map> externalBoms = new HashMap<>(); + private final BootstrapMavenContext mavenContext; + private final MessageWriter log; + + private MavenProjectConfigurationLoader(BootstrapMavenContext mavenContext, MessageWriter log) { + this.mavenContext = mavenContext; + this.log = log; + } + + private ConfiguredProject loadInternal(Path projectDir) throws Exception { + + log.debug("Loading project configuration for %s", projectDir); + final long start = System.currentTimeMillis(); + + final List createdDirs = new ArrayList<>(); + final LocalWorkspace ws = mavenContext.getWorkspace(); + ws.getProjects().values().forEach(p -> ensureResolvable(p, createdDirs)); + try { + for (LocalProject project : ws.getProjects().values()) { + final ModuleWrapper moduleWrapper = getModuleWrapper(project); + if (!moduleWrapper.effectiveDirectDeps.isEmpty()) { + final Map rawDeps = new HashMap<>( + moduleWrapper.getRawDirectDeps().size()); + for (org.apache.maven.model.Dependency d : moduleWrapper.getRawDirectDeps()) { + final String type = d.getType().isEmpty() ? ArtifactCoords.TYPE_JAR : d.getType(); + if (isPotentialExtension(type, d.getClassifier())) { + var groupId = ConfiguredValue.of(d.getGroupId(), resolveValue(d.getGroupId(), moduleWrapper)); + var artifactId = ConfiguredValue.of(d.getArtifactId(), + resolveValue(d.getArtifactId(), moduleWrapper)); + var version = ConfiguredValue.of(d.getVersion(), resolveValue(d.getVersion(), moduleWrapper)); + final ConfiguredArtifact c = ConfiguredArtifact.of(groupId, artifactId, + ConfiguredValue.of(d.getClassifier(), + resolveValue(d.getClassifier(), moduleWrapper)), + ConfiguredValue.of(d.getType(), resolveValue(type, moduleWrapper)), + version, + !version.isEffectivelyNull() + && isLocal(groupId.getEffectiveValue(), artifactId.getEffectiveValue(), + version.getEffectiveValue())); + rawDeps.put(c.getKey(), c); + } + } + + final List requests = new ArrayList<>(); + for (Dependency d : moduleWrapper.effectiveDirectDeps) { + final Artifact artifact = d.getArtifact(); + if (isPotentialExtension(artifact.getExtension(), artifact.getClassifier())) { + requests.add(new ArtifactRequest() + .setArtifact(artifact) + .setRepositories(mavenContext.getRepositorySystem().newResolutionRepositories( + mavenContext.getRepositorySystemSession(), moduleWrapper.effectiveRepos))); + } + } + final List results; + try { + results = mavenContext.getRepositorySystem() + .resolveArtifacts(mavenContext.getRepositorySystemSession(), requests); + } catch (Exception e) { + throw new RuntimeException("Failed to process " + project.getDir(), e); + } + for (ArtifactResult r : results) { + PathTree.ofDirectoryOrArchive(r.getArtifact().getFile().toPath()) + .accept(BootstrapConstants.DESCRIPTOR_PATH, visit -> { + if (visit == null) { + return; + } + final Artifact a = r.getArtifact(); + final ArtifactKey key = ArtifactKey.of(a.getGroupId(), a.getArtifactId(), a.getClassifier(), + a.getExtension()); + ConfiguredArtifact c = rawDeps.get(key); + if (c != null) { + if (c.getVersion().isEffectivelyNull()) { + ResolvedValue managedVersion = getManagedDependencyVersion(moduleWrapper, key); + if (managedVersion == null) { + managedVersion = ResolvedValue.of(a.getVersion()); + } + c = ConfiguredArtifact.of(c.getGroupId(), c.getArtifactId(), c.getClassifier(), + c.getType(), + ConfiguredValue.of(c.getVersion().getRawValue(), managedVersion), + isLocal(a.getGroupId(), a.getArtifactId(), a.getVersion())); + } + moduleWrapper.getConfiguredModule().addDirectExtensionDep(c); + } else { + c = ConfiguredArtifact.of(ConfiguredValue.of(a.getGroupId()), + ConfiguredValue.of(a.getArtifactId()), + ConfiguredValue.of(a.getClassifier()), + ConfiguredValue.of(a.getExtension()), + ConfiguredValue.of(null, getInheritedDependencyVersion(moduleWrapper, a)), + isLocal(a.getGroupId(), a.getArtifactId(), a.getVersion())); + moduleWrapper.getConfiguredModule().addTopTransitiveExtensionDep(c); + } + }); + } + } + + if (moduleWrapper.quarkusPlugin != null) { + var plugin = moduleWrapper.quarkusPlugin; + final Plugin effectivePlugin = moduleWrapper.getEffectivePluginConfig( + plugin.getGroupId().getEffectiveValue(), plugin.getArtifactId().getEffectiveValue()); + if (effectivePlugin != null) { + if (!ArtifactCoords.TYPE_POM.equals(moduleWrapper.getRawModel().getPackaging())) { + for (PluginExecution e : effectivePlugin.getExecutions()) { + if (e.getGoals().contains("build")) { + moduleWrapper.getConfiguredModule().setQuarkusApplication(true); + break; + } + } + } + } + if (plugin.getVersion().isEffectivelyNull()) { + ResolvedValue managedVersion = getManagedPluginVersion(moduleWrapper, ArtifactKey.ga( + plugin.getGroupId().getEffectiveValue(), plugin.getArtifactId().getEffectiveValue())); + if (managedVersion == null && effectivePlugin != null) { + managedVersion = ResolvedValue.of(effectivePlugin.getVersion()); + } + if (managedVersion != null) { + plugin = ConfiguredArtifact.of(plugin.getGroupId(), plugin.getArtifactId(), + plugin.getClassifier(), plugin.getType(), + ConfiguredValue.of(plugin.getVersion().getRawValue(), managedVersion)); + moduleWrapper.setQuarkusPlugin(plugin); + } + } + } + } + + final ConfiguredProject project = new ConfiguredProject(projectDir); + for (ModuleWrapper md : modules.values()) { + final ConfiguredModule module = md.module; + if (module != null && isLog(module) && md.getPomFile().startsWith(projectDir)) { + project.addModule(module); + log.info("Module " + module.getId()); + if (module.isQuarkusApplication()) { + log.info(" Quarkus application"); + } + for (ConfiguredBom bom : module.getPlatformBoms()) { + final StringBuilder sb = new StringBuilder(); + sb.append(" ").append(bom); + if (!bom.getArtifact().isLocal()) { + ResolvedValue value = bom.getArtifact().getArtifactId().getResolvedValue(); + ValueSource src = value == null ? null : value.getSource(); + if (src != null && src.getModule() != null) { + sb.append(" in ").append(src.getModule()); + } + } + log.info(sb.toString()); + } + if (module.getQuarkusPlugin() != null) { + log.info(" Quarkus plugin: " + module.getQuarkusPlugin().toCompactString()); + } + if (module.getManagedQuarkusPlugin() != null) { + log.info(" Managed Quarkus plugin: " + module.getManagedQuarkusPlugin().toCompactString()); + } + if (!module.getManagedExtensions().isEmpty()) { + log.info(" Extension version constraints:"); + module.getManagedExtensions().forEach(d -> log.info(" - " + d.toCompactString())); + } + if (!module.getDirectExtensionDeps().isEmpty()) { + log.info(" Extension dependencies:"); + module.getDirectExtensionDeps().forEach(d -> log.info(" - " + d.toCompactString())); + } + if (!module.getTopTransitiveExtensionDeps().isEmpty()) { + log.info(" Top transitive extension dependencies:"); + module.getTopTransitiveExtensionDeps().forEach(d -> log.info(" - " + d.toCompactString())); + } + } + } + return project; + } finally { + for (Path p : createdDirs) { + IoUtils.recursiveDelete(p); + } + log.info("Done in " + (System.currentTimeMillis() - start)); + } + } + + private static boolean isPotentialExtension(String type, String classifier) { + return (type == null + || type.isEmpty() + || type.equals(ArtifactCoords.TYPE_JAR)) + && (classifier == null + || classifier.isEmpty() + || !classifier.equals("sources") + && !classifier.equals("javadoc")); + } + + private static boolean isLog(ConfiguredModule module) { + if (module.isQuarkusApplication()) { + return true; + } + /* @formatter:off + for (ConfiguredArtifact a : module.getDirectExtensionDeps()) { + if (a.getVersion().getRawValue() != null) { + return true; + } + } + for (ConfiguredArtifact a : module.getManagedExtensions()) { + if (a.getVersion().getRawValue() != null) { + return true; + } + } + if (module.getManagedQuarkusPlugin() != null && module.getManagedQuarkusPlugin().getVersion().getRawValue() != null) { + return true; + } + if (module.getQuarkusPlugin() != null && module.getQuarkusPlugin().getVersion().getRawValue() != null) { + return true; + } + for (ConfiguredBom bom : module.getPlatformBoms()) { + if (bom.isImported()) { + return true; + } + } + @formatter:on */ + return false; + } + + private boolean isLocal(String groupId, String artifactId, String version) { + final LocalProject project = mavenContext.getWorkspace().getProject(groupId, artifactId); + return project != null && project.getVersion().equals(version); + } + + private ModuleWrapper getModuleWrapper(LocalProject project) { + ModuleWrapper module = this.modules.get(project.getKey()); + if (module == null) { + List effectiveManagedDeps = List.of(); + List effectiveDirectDeps = List.of(); + List effectiveRepos = List.of(); + if (project.getModelBuildingResult() == null) { + ArtifactDescriptorResult descriptor; + try { + descriptor = mavenContext.getRepositorySystem().readArtifactDescriptor( + mavenContext.getRepositorySystemSession(), + new ArtifactDescriptorRequest() + .setArtifact(new DefaultArtifact(project.getGroupId(), project.getArtifactId(), + ArtifactCoords.TYPE_POM, project.getVersion())) + .setRepositories(mavenContext.getRemoteRepositories())); + } catch (Exception e) { + throw new RuntimeException( + "Failed to resolve artifact descriptor for " + + ArtifactCoords.pom(project.getGroupId(), project.getArtifactId(), project.getVersion()), + e); + } + effectiveManagedDeps = descriptor.getManagedDependencies(); + effectiveDirectDeps = descriptor.getDependencies(); + effectiveRepos = descriptor.getRepositories(); + } else { + final Model em = project.getModelBuildingResult().getEffectiveModel(); + final DependencyManagement dm = em.getDependencyManagement(); + if (dm != null) { + effectiveManagedDeps = toAetherDeps(dm.getDependencies()); + } + effectiveDirectDeps = toAetherDeps(em.getDependencies()); + effectiveRepos = new ArrayList<>(em.getRepositories().size()); + for (org.apache.maven.model.Repository r : em.getRepositories()) { + final RemoteRepository.Builder rb = new RemoteRepository.Builder(r.getId(), r.getLayout(), r.getUrl()) + .setContentType(r.getLayout()); + var rp = r.getReleases(); + if (rp != null) { + rb.setReleasePolicy(new RepositoryPolicy(Boolean.parseBoolean(rp.getEnabled()), + rp.getUpdatePolicy() == null ? RepositoryPolicy.UPDATE_POLICY_NEVER : rp.getUpdatePolicy(), + rp.getChecksumPolicy() == null ? RepositoryPolicy.CHECKSUM_POLICY_WARN + : rp.getChecksumPolicy())); + } + rp = r.getSnapshots(); + if (rp != null) { + rb.setSnapshotPolicy(new RepositoryPolicy(Boolean.parseBoolean(rp.getEnabled()), + rp.getUpdatePolicy() == null ? RepositoryPolicy.UPDATE_POLICY_DAILY : rp.getUpdatePolicy(), + rp.getChecksumPolicy() == null ? RepositoryPolicy.CHECKSUM_POLICY_WARN + : rp.getChecksumPolicy())); + } + effectiveRepos.add(rb.build()); + } + } + module = new ModuleWrapper(project, effectiveManagedDeps, effectiveDirectDeps, effectiveRepos); + collectManagedDeps(module); + modules.put(project.getKey(), module); + } + return module; + } + + private List toAetherDeps(List deps) { + final List result = new ArrayList<>(deps.size()); + for (org.apache.maven.model.Dependency d : deps) { + result.add(new Dependency( + new DefaultArtifact(d.getGroupId(), d.getArtifactId(), d.getClassifier(), d.getType(), d.getVersion()), + d.getScope())); + } + return result; + } + + private ResolvedValue getInheritedDependencyVersion(ModuleWrapper module, Artifact a) { + for (org.apache.maven.model.Dependency d : module.getRawDirectDeps()) { + if (a.getArtifactId().equals(d.getArtifactId()) && a.getGroupId() + .equals(d.getGroupId().equals("${project.groupId}") ? module.getGroupId() : d.getGroupId()) + && a.getClassifier() + .equals(d.getClassifier() == null ? ArtifactCoords.DEFAULT_CLASSIFIER : d.getClassifier()) + && a.getExtension().equals(d.getType() == null ? ArtifactCoords.TYPE_JAR : d.getType())) { + return ResolvedValue.of(a.getVersion(), ValueSource.local(module.getId(), module.getPomFile())); + } + } + final LocalProject parent = module.project.getLocalParent(); + if (parent != null) { + return getInheritedDependencyVersion(getModuleWrapper(parent), a); + } + final Parent parentModel = module.project.getRawModel().getParent(); + if (parentModel != null) { + return ResolvedValue.of(a.getVersion(), ValueSource.external( + WorkspaceModuleId.of(parentModel.getGroupId(), parentModel.getArtifactId(), parentModel.getVersion()), + null)); + } + return ResolvedValue.of(a.getVersion()); + } + + private ResolvedValue getManagedDependencyVersion(ModuleWrapper moduleWrapper, ArtifactKey key) { + final ConfiguredArtifact configured = moduleWrapper.getExtensionVersionConstraint(key); + if (configured != null) { + return configured.getVersion().getResolvedValue(); + } + final LocalProject parent = moduleWrapper.project.getLocalParent(); + if (parent != null) { + final ResolvedValue v = getManagedDependencyVersion(getModuleWrapper(parent), key); + if (v != null) { + return v; + } + } + for (ArtifactCoords bom : moduleWrapper.bomImports.keySet()) { + final LocalProject bomProject = mavenContext.getWorkspace().getProject(bom.getGroupId(), bom.getArtifactId()); + if (bomProject != null) { + final ResolvedValue version = getManagedDependencyVersion(getModuleWrapper(bomProject), key); + if (version != null) { + return version; + } + } else { + final String version = getExternalBom(bom).get(key); + if (version != null) { + return ResolvedValue.of(version, + ValueSource.external(WorkspaceModuleId.of(bom.getGroupId(), bom.getArtifactId(), bom.getVersion()), + null)); // TODO + } + } + } + return null; + } + + private ConfiguredBom locateBomImport(ModuleWrapper moduleWrapper, ArtifactCoords bom) { + ConfiguredBom configured = moduleWrapper.bomImports.get(bom); + if (configured != null) { + return configured; + } + final LocalProject parent = moduleWrapper.project.getLocalParent(); + if (parent != null) { + configured = locateBomImport(getModuleWrapper(parent), bom); + if (configured != null) { + return configured; + } + } else if (moduleWrapper.project.getRawModel().getParent() != null) { + final Parent parentPom = moduleWrapper.project.getRawModel().getParent(); + final ArtifactCoords pom = ArtifactCoords.pom(parentPom.getGroupId(), parentPom.getArtifactId(), + parentPom.getVersion()); + if (importsPlatformBom(pom, bom)) { + final WorkspaceModuleId externalId = WorkspaceModuleId.of(pom.getGroupId(), pom.getArtifactId(), + pom.getVersion()); + return ConfiguredBom.enforced( + ConfiguredValue.of(bom.getGroupId(), + ResolvedValue.of(bom.getGroupId(), ValueSource.external(externalId, null))), + ConfiguredValue.of(bom.getArtifactId(), + ResolvedValue.of(bom.getArtifactId(), ValueSource.external(externalId, null))), + ConfiguredValue.of(bom.getVersion(), + ResolvedValue.of(bom.getVersion(), ValueSource.external(externalId, null))), + false); + } + } + for (Map.Entry imported : moduleWrapper.bomImports.entrySet()) { + if (imported.getKey().equals(bom)) { + return imported.getValue(); + } + if (imported.getValue().getArtifact().isLocal()) { + final LocalProject bomProject = mavenContext.getWorkspace().getProject(imported.getKey().getGroupId(), + imported.getKey().getArtifactId()); + if (bomProject != null) { + var located = locateBomImport(getModuleWrapper(bomProject), bom); + if (located != null) { + return located; + } + } + } else if (importsPlatformBom(imported.getKey(), bom)) { + final WorkspaceModuleId externalId = WorkspaceModuleId.of(imported.getKey().getGroupId(), + imported.getKey().getArtifactId(), imported.getKey().getVersion()); + return ConfiguredBom.enforced( + ConfiguredValue.of(bom.getGroupId(), + ResolvedValue.of(bom.getGroupId(), ValueSource.external(externalId, null))), + ConfiguredValue.of(bom.getArtifactId(), + ResolvedValue.of(bom.getArtifactId(), ValueSource.external(externalId, null))), + ConfiguredValue.of(bom.getVersion(), + ResolvedValue.of(bom.getVersion(), ValueSource.external(externalId, null))), + false); + } + } + return null; + } + + private boolean importsPlatformBom(ArtifactCoords pom, ArtifactCoords platformBom) { + //log("importsPlatformBom " + pom.toCompactCoords() + " " + platformBom.toCompactCoords()); + return getExternalBom(pom).containsKey(PlatformArtifacts.ensureCatalogArtifact(platformBom).getKey()); + } + + private Map getExternalBom(ArtifactCoords coords) { + Map managed = externalBoms.get(coords); + if (managed == null) { + final List deps; + try { + deps = mavenContext.getRepositorySystem().readArtifactDescriptor(mavenContext.getRepositorySystemSession(), + new ArtifactDescriptorRequest().setArtifact( + new DefaultArtifact(coords.getGroupId(), coords.getArtifactId(), ArtifactCoords.TYPE_POM, + coords.getVersion())) + .setRepositories(mavenContext.getRemoteRepositories())) + .getManagedDependencies(); + } catch (Exception e) { + throw new RuntimeException("Failed to resolve descriptor for " + coords.toCompactCoords(), e); + } + managed = new HashMap<>(deps.size()); + for (Dependency d : deps) { + final Artifact a = d.getArtifact(); + managed.put(ArtifactKey.of(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension()), + a.getVersion()); + } + externalBoms.put(coords, managed); + } + return managed; + } + + private ResolvedValue getManagedPluginVersion(ModuleWrapper moduleDeps, ArtifactKey pluginKey) throws Exception { + final ConfiguredArtifact configured = moduleDeps.getPluginVersionConstraint(pluginKey); + if (configured != null) { + return configured.getVersion().getResolvedValue(); + } + final LocalProject parent = moduleDeps.project.getLocalParent(); + if (parent != null) { + return getManagedPluginVersion(getModuleWrapper(parent), pluginKey); + } + return null; + } + + private void collectManagedDeps(ModuleWrapper moduleWrapper) { + + if (!moduleWrapper.getRawManagedDeps().isEmpty()) { + moduleWrapper.getRawManagedDeps().forEach(d -> { + final ConfiguredValue groupId = ConfiguredValue.of(d.getGroupId(), resolveValue(d.getGroupId(), moduleWrapper)); + final ConfiguredValue artifactId = ConfiguredValue.of(d.getArtifactId(), + resolveValue(d.getArtifactId(), moduleWrapper)); + final ConfiguredValue version = ConfiguredValue.of(d.getVersion(), resolveValue(d.getVersion(), moduleWrapper)); + if ("import".equals(d.getScope())) { + final ArtifactCoords effectiveCoords = ArtifactCoords.pom(groupId.getEffectiveValue(), + artifactId.getEffectiveValue(), version.getEffectiveValue()); + moduleWrapper.bomImports.put(effectiveCoords, + ConfiguredBom.imported(groupId, artifactId, version, + isLocal(groupId.getEffectiveValue(), artifactId.getEffectiveValue(), + version.getEffectiveValue()))); + } else { + final String classifier = d.getClassifier() == null ? ArtifactCoords.DEFAULT_CLASSIFIER : d.getClassifier(); + final ConfiguredArtifact artifact = ConfiguredArtifact.of( + groupId, artifactId, + ConfiguredValue.of(classifier, resolveValue(classifier, moduleWrapper)), + ConfiguredValue.of(d.getType(), resolveValue(d.getType(), moduleWrapper)), + version, + isLocal(groupId.getEffectiveValue(), artifactId.getEffectiveValue(), version.getEffectiveValue())); + moduleWrapper.managedDeps.put(artifact.getKey(), artifact); + } + }); + } + + final Collection enforcedPlatformBoms = getEnforcedPlatformBoms(moduleWrapper.effectiveManagedDeps); + for (ArtifactCoords enforced : enforcedPlatformBoms) { + ConfiguredBom platformBom = moduleWrapper.bomImports.get(enforced); + if (platformBom != null) { + moduleWrapper.getConfiguredModule().addPlatformBom(platformBom); + } else { + platformBom = locateBomImport(moduleWrapper, enforced); + if (platformBom == null) { + moduleWrapper.getConfiguredModule().addPlatformBom( + ConfiguredBom.enforced( + ConfiguredValue.of(enforced.getGroupId()), + ConfiguredValue.of(enforced.getArtifactId()), + ConfiguredValue.of(enforced.getVersion()), + isLocal(enforced.getGroupId(), enforced.getArtifactId(), enforced.getVersion()))); + } else { + moduleWrapper.getConfiguredModule().addPlatformBom(ConfiguredBom.enforced(platformBom.getArtifact())); + } + } + } + + for (Plugin rawPlugin : moduleWrapper.getRawPlugins()) { + final ResolvedValue artifactId = resolveValue(rawPlugin.getArtifactId(), moduleWrapper); + if ("quarkus-maven-plugin".equals(artifactId.getValue())) { + moduleWrapper.setQuarkusPlugin(ConfiguredArtifact.jar( + ConfiguredValue.of(rawPlugin.getGroupId(), resolveValue(rawPlugin.getGroupId(), moduleWrapper)), + ConfiguredValue.of(rawPlugin.getArtifactId(), artifactId), + ConfiguredValue.of(rawPlugin.getVersion(), resolveValue(rawPlugin.getVersion(), moduleWrapper)))); + break; + } + } + for (Plugin plugin : moduleWrapper.getRawManagedPlugins()) { + if (plugin.getVersion() != null) { + moduleWrapper.managedPlugins.put( + ArtifactKey.ga(resolveValue(plugin.getGroupId(), moduleWrapper).getValue(), + resolveValue(plugin.getArtifactId(), moduleWrapper).getValue()), + plugin); + } + } + } + + private ResolvedValue resolveValue(String expr, PomWrapper rawModel) { + return resolveValue(expr, null, rawModel); + } + + private ResolvedValue resolveValue(String expr, String wrappingExpr, PomWrapper rawModel) { + + if (!ConfiguredValue.isPropertyExpression(expr)) { + final LocalProject project = mavenContext.getWorkspace().getProject(rawModel.getGroupId(), + rawModel.getArtifactId()); + final ValueSource source = project == null + ? ValueSource.external(rawModel.getId(), wrappingExpr, rawModel.getPomFile()) + : ValueSource.local(rawModel.getId(), wrappingExpr, rawModel.getPomFile()); + return ResolvedValue.of(expr, source); + } + final String name = ConfiguredValue.getPropertyName(expr); + if (name.startsWith("project.")) { + final String projectProp = name.substring("project.".length()); + switch (projectProp) { + case "version": + return ResolvedValue.of(rawModel.getVersion(), + ValueSource.local(rawModel.getId(), name, rawModel.getPomFile())); + case "groupId": + return ResolvedValue.of(rawModel.getGroupId(), + ValueSource.local(rawModel.getId(), name, rawModel.getPomFile())); + case "artifactId": + return ResolvedValue.of(rawModel.getArtifactId(), + ValueSource.local(rawModel.getId(), name, rawModel.getPomFile())); + } + } + final String value = rawModel.getRawProperties().getProperty(name); + if (value != null) { + return resolveValue(value, name, rawModel); + } + final PomWrapper parent = rawModel.getParentWrapper(); + if (parent == null) { + return ResolvedValue.of(expr); + } + return resolveValue(expr, wrappingExpr, parent); + } + + private PomWrapper getPomWrapper(Parent parent) { + if (parent == null) { + return null; + } + final LocalProject parentProject = mavenContext.getWorkspace().getProject(parent.getGroupId(), parent.getArtifactId()); + if (parentProject != null) { + return getModuleWrapper(parentProject); + } + final Artifact pomArtifact = new DefaultArtifact(parent.getGroupId(), + parent.getArtifactId(), ArtifactCoords.TYPE_POM, parent.getVersion()); + final File pomXml; + try { + pomXml = mavenContext.getRepositorySystem() + .resolveArtifact(mavenContext.getRepositorySystemSession(), + new ArtifactRequest().setArtifact( + pomArtifact) + .setRepositories(mavenContext.getRemoteRepositories())) + .getArtifact().getFile(); + } catch (Exception e) { + throw new RuntimeException("Failed to resolve " + pomArtifact, e); + } + final Model parentModel; + try { + parentModel = ModelUtils.readModel(pomXml.toPath()); + parentModel.setPomFile(pomXml); + } catch (Exception e) { + throw new RuntimeException("Failed to read " + pomXml, e); + } + return new ModelWrapper(parentModel); + } + + private static Collection getEnforcedPlatformBoms(List managedDeps) { + final List enforcedPlatformBoms = new ArrayList<>(3); + for (Dependency d : managedDeps) { + final Artifact a = d.getArtifact(); + if (a.getExtension().equals("json") + && a.getArtifactId().endsWith(BootstrapConstants.PLATFORM_DESCRIPTOR_ARTIFACT_ID_SUFFIX) + && a.getClassifier().equals(a.getVersion())) { + enforcedPlatformBoms.add(ArtifactCoords.pom(a.getGroupId(), + PlatformArtifacts.ensureBomArtifactId(a.getArtifactId()), a.getVersion())); + } + } + return enforcedPlatformBoms; + } + + private static void ensureResolvable(LocalProject project, List createdDirs) { + if (!project.getRawModel().getPackaging().equals(ArtifactCoords.TYPE_POM)) { + final Path classesDir = project.getClassesDir(); + if (!Files.exists(classesDir)) { + Path topDirToCreate = classesDir; + while (!Files.exists(topDirToCreate.getParent())) { + topDirToCreate = topDirToCreate.getParent(); + } + try { + Files.createDirectories(classesDir); + createdDirs.add(topDirToCreate); + } catch (IOException e) { + throw new RuntimeException("Failed to create " + classesDir, e); + } + } + } + } + + private interface PomWrapper { + + String getGroupId(); + + String getArtifactId(); + + String getVersion(); + + Properties getRawProperties(); + + PomWrapper getParentWrapper(); + + default WorkspaceModuleId getId() { + return WorkspaceModuleId.of(getGroupId(), getArtifactId(), getVersion()); + } + + Path getPomFile(); + } + + private class ModuleWrapper implements PomWrapper { + final LocalProject project; + final List effectiveManagedDeps; + final List effectiveDirectDeps; + final List effectiveRepos; + + private List rawDirectDeps; + private List rawManagedDeps; + private List rawPlugins; + private List rawManagedPlugins; + private Properties rawProps; + private List activePomProfiles; + + final Map managedDeps = new HashMap<>(); + final Map bomImports = new LinkedHashMap<>(); + final Map managedPlugins = new HashMap<>(); + ConfiguredArtifact managedQuarkusPlugin; + ConfiguredArtifact quarkusPlugin; + + ConfiguredModule module; + + public ModuleWrapper(LocalProject project, List effectiveManagedDeps, + List effectiveDirectDeps, + List effectiveRepos) { + super(); + this.project = project; + this.effectiveManagedDeps = effectiveManagedDeps; + this.effectiveDirectDeps = effectiveDirectDeps; + this.effectiveRepos = effectiveRepos; + } + + public void setQuarkusPlugin(ConfiguredArtifact quarkusPlugin) { + this.quarkusPlugin = quarkusPlugin; + getConfiguredModule().setQuarkusPlugin(quarkusPlugin); + } + + private Plugin getEffectivePluginConfig(String groupId, String artifactId) { + if (project.getModelBuildingResult() != null + && project.getModelBuildingResult().getEffectiveModel().getBuild() != null) { + for (Plugin p : project.getModelBuildingResult().getEffectiveModel().getBuild().getPlugins()) { + if (p.getArtifactId().equals(artifactId) && p.getGroupId().equals(groupId)) { + return p; + } + } + } + return null; + } + + Model getRawModel() { + return project.getRawModel(); + } + + List getRawDirectDeps() { + if (rawDirectDeps == null) { + final List profiles = getActivePomProfiles(); + if (profiles.isEmpty()) { + rawDirectDeps = project.getRawModel().getDependencies(); + } else { + rawDirectDeps = new ArrayList<>(); + rawDirectDeps.addAll(project.getRawModel().getDependencies()); + for (Profile p : profiles) { + rawDirectDeps.addAll(p.getDependencies()); + } + } + } + return rawDirectDeps; + } + + List getRawManagedDeps() { + if (rawManagedDeps == null) { + final List profiles = getActivePomProfiles(); + if (profiles.isEmpty()) { + rawManagedDeps = project.getRawModel().getDependencyManagement() == null + ? List.of() + : project.getRawModel().getDependencyManagement().getDependencies(); + } else { + rawManagedDeps = new ArrayList<>(); + if (project.getRawModel().getDependencyManagement() != null) { + rawManagedDeps.addAll(project.getRawModel().getDependencyManagement().getDependencies()); + } + for (Profile p : profiles) { + if (p.getDependencyManagement() != null) { + rawManagedDeps.addAll(p.getDependencyManagement().getDependencies()); + } + } + } + } + return rawManagedDeps; + } + + List getRawPlugins() { + if (rawPlugins == null) { + final List profiles = getActivePomProfiles(); + if (profiles.isEmpty()) { + rawPlugins = project.getRawModel().getBuild() == null ? List.of() + : project.getRawModel().getBuild().getPlugins(); + } else { + rawPlugins = new ArrayList<>(); + if (project.getRawModel().getBuild() != null) { + rawPlugins.addAll(project.getRawModel().getBuild().getPlugins()); + } + for (Profile p : profiles) { + if (p.getBuild() != null) { + rawPlugins.addAll(p.getBuild().getPlugins()); + } + } + + } + } + return rawPlugins; + } + + List getRawManagedPlugins() { + if (rawManagedPlugins == null) { + final List profiles = getActivePomProfiles(); + if (profiles.isEmpty()) { + final PluginManagement pm = project.getRawModel().getBuild() == null ? null + : project.getRawModel().getBuild().getPluginManagement(); + rawManagedPlugins = pm == null ? List.of() : pm.getPlugins(); + } else { + rawManagedPlugins = new ArrayList<>(); + PluginManagement pm = project.getRawModel().getBuild() == null ? null + : project.getRawModel().getBuild().getPluginManagement(); + if (pm != null) { + rawManagedPlugins.addAll(pm.getPlugins()); + } + for (Profile p : profiles) { + pm = p.getBuild() == null ? null : p.getBuild().getPluginManagement(); + if (pm != null) { + rawManagedPlugins.addAll(pm.getPlugins()); + } + } + } + } + return rawManagedPlugins; + } + + private List getActivePomProfiles() { + if (activePomProfiles == null) { + if (project.getModelBuildingResult() != null) { + var effectiveProfiles = project.getModelBuildingResult().getActivePomProfiles( + project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion()); + if (effectiveProfiles.isEmpty()) { + activePomProfiles = List.of(); + } else { + // re-parse the raw model to make sure the profiles are actually raw + final Model model; + try { + model = ModelUtils.readModel(getPomFile()); + } catch (IOException e) { + throw new RuntimeException("Failed to parse " + getPomFile(), e); + } + var activeIds = effectiveProfiles.stream().map(Profile::getId).collect(Collectors.toList()); + activePomProfiles = new ArrayList<>(effectiveProfiles.size()); + for (Profile p : model.getProfiles()) { + if (activeIds.contains(p.getId())) { + activePomProfiles.add(p); + } + } + } + } else { + activePomProfiles = List.of(); + } + } + return activePomProfiles; + } + + private ConfiguredModule getConfiguredModule() { + return module == null + ? module = ConfiguredModule + .of(WorkspaceModuleId.of(project.getGroupId(), project.getArtifactId(), project.getVersion()), + project.getRawModel().getPomFile().toPath()) + : module; + } + + private ConfiguredArtifact getExtensionVersionConstraint(ArtifactKey key) { + final ConfiguredArtifact configured = managedDeps.get(key); + if (configured != null) { + getConfiguredModule().addManagedExtension(configured); + } + return configured; + } + + private ConfiguredArtifact getPluginVersionConstraint(ArtifactKey key) { + if (quarkusPlugin != null && !quarkusPlugin.getVersion().isEffectivelyNull()) { + return quarkusPlugin; + } + if (managedQuarkusPlugin != null) { + return managedQuarkusPlugin; + } + final Plugin plugin = managedPlugins.get(key); + if (plugin == null) { + return null; + } + managedQuarkusPlugin = ConfiguredArtifact.jar( + ConfiguredValue.of(plugin.getGroupId(), plugin.getGroupId()), + ConfiguredValue.of(plugin.getArtifactId(), plugin.getArtifactId()), + ConfiguredValue.of(plugin.getVersion(), resolveValue(plugin.getVersion(), this))); + getConfiguredModule().setManagedQuarkusPlugin(managedQuarkusPlugin); + return managedQuarkusPlugin; + } + + @Override + public String getGroupId() { + return project.getGroupId(); + } + + @Override + public String getArtifactId() { + return project.getArtifactId(); + } + + @Override + public String getVersion() { + return project.getVersion(); + } + + @Override + public Properties getRawProperties() { + if (rawProps == null) { + final List profiles = getActivePomProfiles(); + if (profiles.isEmpty()) { + rawProps = project.getRawModel().getProperties(); + } else { + rawProps = new Properties(); + rawProps.putAll(project.getRawModel().getProperties()); + for (Profile p : profiles) { + rawProps.putAll(p.getProperties()); + } + } + } + return rawProps; + } + + @Override + public PomWrapper getParentWrapper() { + return getPomWrapper(project.getRawModel().getParent()); + } + + @Override + public Path getPomFile() { + return project.getRawModel().getPomFile().toPath(); + } + } + + private class ModelWrapper implements PomWrapper { + + private final Model model; + + private ModelWrapper(Model model) { + this.model = model; + } + + @Override + public String getGroupId() { + return ModelUtils.getGroupId(model); + } + + @Override + public String getArtifactId() { + return model.getArtifactId(); + } + + @Override + public String getVersion() { + return ModelUtils.getVersion(model); + } + + @Override + public Properties getRawProperties() { + return model.getProperties(); + } + + @Override + public PomWrapper getParentWrapper() { + return getPomWrapper(model.getParent()); + } + + @Override + public Path getPomFile() { + return model.getPomFile().toPath(); + } + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ResolvedValue.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ResolvedValue.java new file mode 100644 index 00000000000000..7e115de20f4a6c --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ResolvedValue.java @@ -0,0 +1,56 @@ +package io.quarkus.devtools.project.state; + +import java.util.Objects; + +public class ResolvedValue { + + public static ResolvedValue of(String value) { + return of(value, null); + } + + public static ResolvedValue of(String value, ValueSource source) { + return new ResolvedValue(value, source); + } + + private final String value; + private final ValueSource source; + + private ResolvedValue(String value, ValueSource source) { + this.value = value; + this.source = source; + } + + public String getValue() { + return value; + } + + public boolean hasSource() { + return source != null; + } + + public ValueSource getSource() { + return source; + } + + @Override + public int hashCode() { + return Objects.hash(source, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ResolvedValue other = (ResolvedValue) obj; + return Objects.equals(source, other.source) && Objects.equals(value, other.value); + } + + @Override + public String toString() { + return source == null ? value : value + " from " + source; + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ValueSource.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ValueSource.java new file mode 100644 index 00000000000000..fbc4244598b182 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/ValueSource.java @@ -0,0 +1,36 @@ +package io.quarkus.devtools.project.state; + +import java.nio.file.Path; + +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; + +public interface ValueSource { + + static ValueSource external(WorkspaceModuleId module, Path p) { + return new ExternalValueSource(module, p); + } + + static ValueSource external(WorkspaceModuleId module, String property, Path p) { + return new ExternalValueSource(module, property, p); + } + + static ValueSource local(WorkspaceModuleId module, Path p) { + return new LocalValueSource(module, p); + } + + static ValueSource local(WorkspaceModuleId module, String property, Path p) { + return new LocalValueSource(module, property, p); + } + + boolean isExternal(); + + default boolean isProperty() { + return getProperty() != null; + } + + String getProperty(); + + Path getPath(); + + WorkspaceModuleId getModule(); +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/WorkspaceQuarkusInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/WorkspaceQuarkusInfo.java new file mode 100644 index 00000000000000..572cea85e3730f --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/WorkspaceQuarkusInfo.java @@ -0,0 +1,234 @@ +package io.quarkus.devtools.project.state; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.devtools.project.update.ProjectUpdateInfos; +import io.quarkus.devtools.project.update.QuarkusPomUpdateRecipe; +import io.quarkus.devtools.project.update.QuarkusUpdateRunner; +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.registry.ExtensionCatalogResolver; +import io.quarkus.registry.catalog.Extension; +import io.quarkus.registry.catalog.ExtensionCatalog; + +public class WorkspaceQuarkusInfo { + + public static void main(String[] args) throws Exception { + //load(Path.of("/home/aloubyansky/git/kogito-runtimes/springboot/bom")); + //MavenProjectConfigurationLoader.load(Path.of("/home/aloubyansky/git/debezium")); + //MavenProjectConfigurationLoader.load(Path.of("/home/aloubyansky/git/optaplanner")); + MavenProjectConfigurationLoader.load(Path.of("/home/aloubyansky/git/camel-quarkus")); + //MavenProjectConfigurationLoader.load(Path.of("/home/aloubyansky/git/kogito-runtimes")); + //MavenProjectConfigurationLoader.load(Path.of("/home/aloubyansky/git/kogito-examples")); + //generateUpdateRecipe(Path.of("/home/aloubyansky/git/camel-quarkus")); + //load(Path.of("/home/aloubyansky/git/quarkus-copy")); + //generateUpdateRecipe(Path.of("/home/aloubyansky/git/quarkus-todo-app")); + //generateUpdateRecipe(Path.of("/home/aloubyansky/playground/code-with-quarkus")); + //load(Path.of("/home/aloubyansky/git/quarkus-todo-app/quarkus-todo-reactive")); + //generateUpdateRecipe(Path.of("/home/aloubyansky/git/keycloak")); + //generateUpdateRecipe(Path.of("/home/aloubyansky/git/cos-fleetshard")); + //generateUpdateRecipe(Path.of("/home/aloubyansky/playground/code-with-quarkus")); + } + + public static void generateUpdateRecipe(Path projectDir) throws Exception { + final BootstrapMavenContext mavenCtx = new BootstrapMavenContext( + BootstrapMavenContext.config() + .setCurrentProject(projectDir.toString()) + .setEffectiveModelBuilder(true) + .setPreferPomsFromWorkspace(true)); + + final ConfiguredProject project = MavenProjectConfigurationLoader.load(projectDir, mavenCtx); + + final ExtensionCatalog latestCatalog = ExtensionCatalogResolver.builder() + .artifactResolver(new MavenArtifactResolver(mavenCtx)).build().resolveExtensionCatalog(); + final Map latestMap = new HashMap<>(latestCatalog.getExtensions().size()); + latestCatalog.getExtensions().forEach(e -> latestMap.put(e.getArtifact().getKey(), e)); + + final Map recipes = new HashMap<>(); + for (ConfiguredModule module : project.getModules()) { + if (!module.isQuarkusApplication()) { + continue; + } + + List unavailableExtensions = new ArrayList<>(0); + List latestExtensions = new ArrayList<>(module.getDirectExtensionDeps().size()); + for (ConfiguredArtifact a : module.getDirectExtensionDeps()) { + if (a.isLocal()) { + continue; + } + final Extension latestExtension = latestMap.get(a.getKey()); + if (latestExtension == null) { + unavailableExtensions.add(a); + } else { + latestExtensions.add(latestExtension); + } + } + + if (!unavailableExtensions.isEmpty()) { + log("WARN: the configured Quarkus registries did not provide any compatibility information for the following extensions in the context of the currently recommended Quarkus platforms:"); + unavailableExtensions.forEach(e -> System.out.println("- " + e.getEffectiveCoords())); + } + + List recommendedOrigins = List.of(); + Map recommendedPlatforms = new HashMap<>(0); + ArtifactCoords recommendedCoreBom = null; + if (!latestExtensions.isEmpty()) { + recommendedOrigins = ProjectUpdateInfos.getRecommendedOrigins(latestCatalog, latestExtensions); + recommendedPlatforms = new HashMap<>(); + for (ExtensionCatalog c : recommendedOrigins) { + if (c.isPlatform()) { + recommendedPlatforms.put(c.getBom().getArtifactId(), c); + if (c.getBom().getArtifactId().equals("quarkus-bom")) { + recommendedCoreBom = c.getBom(); + } + } + } + } + + // BOM UPDATES + Path removedFromBuildFile = null; + for (ConfiguredBom configuredBom : module.getPlatformBoms()) { + final ArtifactCoords currentBom = configuredBom.getArtifact().getEffectiveCoords(); + final ExtensionCatalog recommendedPlatform = recommendedPlatforms.remove(currentBom.getArtifactId()); + if (recommendedPlatform == null) { + final Path buildFile; + if (configuredBom.isImported()) { + buildFile = module.getBuildFile(); + recipes.computeIfAbsent(buildFile, k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .removeManagedDependency(configuredBom.getArtifact().getGroupId().getRawValue(), + configuredBom.getArtifact().getArtifactId().getRawValue(), null); + removedFromBuildFile = buildFile; + } else { + System.out.println("REMOVE BOM IMPORT " + configuredBom); + } + continue; + } + final ArtifactCoords recommendedBom = recommendedPlatform.getBom(); + if (!recommendedBom.equals(currentBom)) { + String newGroupId = null; + String newVersion = null; + if (!recommendedBom.getGroupId().equals(currentBom.getGroupId())) { + if (configuredBom.getArtifact().getGroupId().isProperty()) { + final ValueSource groupIdSrc = configuredBom.getArtifact().getGroupId().getResolvedValue() + .getSource(); + recipes.computeIfAbsent(groupIdSrc.getPath(), k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateProperty(groupIdSrc.getProperty(), recommendedBom.getGroupId()); + } else { + newGroupId = recommendedBom.getGroupId(); + } + } + if (!recommendedBom.getVersion().equals(currentBom.getVersion())) { + if (configuredBom.getArtifact().getVersion().isProperty()) { + final ValueSource versionSrc = configuredBom.getArtifact().getVersion().getResolvedValue() + .getSource(); + recipes.computeIfAbsent(versionSrc.getPath(), k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateProperty(versionSrc.getProperty(), recommendedBom.getVersion()); + } else { + newVersion = recommendedBom.getVersion(); + } + } + if (newGroupId != null || newVersion != null) { + final String currentGroupId = configuredBom.getArtifact().getGroupId().getRawValue(); + final String currentArtifactId = configuredBom.getArtifact().getArtifactId().getRawValue(); + recipes.computeIfAbsent(module.getBuildFile(), k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateManagedDependency(currentGroupId, currentArtifactId, + newGroupId == null ? currentGroupId : newGroupId, currentArtifactId, newVersion); + } + } + } + + // NEW BOM IMPORTS + for (ExtensionCatalog newImport : recommendedPlatforms.values()) { + Path buildFile = module.getBuildFile(); + if (removedFromBuildFile != null) { + buildFile = removedFromBuildFile; + } + final ArtifactCoords bom = newImport.getBom(); + recipes.computeIfAbsent(buildFile, k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .addManagedDependency(bom.getGroupId(), bom.getArtifactId(), bom.getClassifier(), bom.getType(), + bom.getVersion(), "import"); + } + + // PLUGIN UPDATES + if (recommendedCoreBom != null && module.getQuarkusPlugin() != null) { + final ConfiguredArtifact configuredPlugin = module.getQuarkusPlugin(); + if (!recommendedCoreBom.getGroupId().equals(configuredPlugin.getGroupId().getEffectiveValue())) { + if (configuredPlugin.getGroupId().isProperty()) { + final ValueSource groupIdSrc = configuredPlugin.getGroupId().getResolvedValue().getSource(); + recipes.computeIfAbsent(groupIdSrc.getPath(), k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateProperty(groupIdSrc.getProperty(), recommendedCoreBom.getGroupId()); + } else { + // TODO + System.out.println( + "UPDATE PLUGIN GROUP-ID " + configuredPlugin + " to be in line with " + recommendedCoreBom); + } + } + if (!recommendedCoreBom.getVersion().equals(configuredPlugin.getVersion().getEffectiveValue())) { + if (configuredPlugin.getVersion().isProperty()) { + final ValueSource versionSrc = configuredPlugin.getVersion().getResolvedValue().getSource(); + recipes.computeIfAbsent(versionSrc.getPath(), k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateProperty(versionSrc.getProperty(), recommendedCoreBom.getVersion()); + } else { + final Path buildFile = configuredPlugin.getVersion().getResolvedValue().getSource().getPath(); + if (buildFile == null) { + System.out.println( + "WARN: could not update plugin version because the target build file is not known"); + } else { + recipes.computeIfAbsent(buildFile, k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updatePluginVersion(configuredPlugin.getGroupId().getEffectiveValue(), + configuredPlugin.getArtifactId().getEffectiveValue(), + recommendedCoreBom.getVersion()); + } + } + } + } + + if (!latestExtensions.isEmpty()) { + final Map nonPlatformOrigins = new HashMap<>(latestCatalog.getExtensions().size()); + for (ExtensionCatalog c : recommendedOrigins) { + if (!c.isPlatform()) { + c.getExtensions() + .forEach(e -> nonPlatformOrigins.putIfAbsent(e.getArtifact().getKey(), e.getArtifact())); + } + } + + if (!nonPlatformOrigins.isEmpty()) { + for (ConfiguredArtifact configuredExt : module.getDirectExtensionDeps()) { + final ArtifactCoords recommendedCoords = nonPlatformOrigins + .remove(configuredExt.getEffectiveCoords().getKey()); + if (recommendedCoords != null + && !configuredExt.getVersion().getEffectiveValue().equals(recommendedCoords.getVersion())) { + if (configuredExt.getVersion().isProperty()) { + final ValueSource versionSrc = configuredExt.getVersion().getResolvedValue().getSource(); + recipes.computeIfAbsent(versionSrc.getPath(), k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateProperty(versionSrc.getProperty(), recommendedCoords.getVersion()); + } else { + recipes.computeIfAbsent(module.getBuildFile(), + k -> QuarkusPomUpdateRecipe.generator().setPom(k)) + .updateDependency(configuredExt.getGroupId().getRawValue(), + configuredExt.getArtifactId().getRawValue(), null, null, + recommendedCoords.getVersion()); + } + } + } + } + } + } + + log("Applying " + recipes.size() + " update recipe(s)"); + recipes.values().forEach(g -> { + QuarkusUpdateRunner.applyDirectly(g.generate(), true); + }); + log("Done!"); + } + + private static void log(Object o) { + System.out.println(o == null ? "null" : o.toString()); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ExtensionInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ExtensionInfo.java new file mode 100644 index 00000000000000..d0d498e31eae8c --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ExtensionInfo.java @@ -0,0 +1,11 @@ +package io.quarkus.devtools.project.state.info; + +import io.quarkus.devtools.project.state.ConfiguredArtifact; +import io.quarkus.registry.catalog.Extension; + +public class ExtensionInfo { + + private ConfiguredArtifact artifact; + private Extension metadata; + private boolean directDependency; +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ExtensionOriginInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ExtensionOriginInfo.java new file mode 100644 index 00000000000000..9ba806c0997c5d --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ExtensionOriginInfo.java @@ -0,0 +1,13 @@ +package io.quarkus.devtools.project.state.info; + +import java.util.List; + +import io.quarkus.devtools.project.state.ConfiguredBom; +import io.quarkus.registry.catalog.ExtensionOrigin; + +public class ExtensionOriginInfo { + + private ConfiguredBom bom; + private ExtensionOrigin metadata; + private List extensions; +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ModuleInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ModuleInfo.java new file mode 100644 index 00000000000000..a269f50906bf82 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ModuleInfo.java @@ -0,0 +1,12 @@ +package io.quarkus.devtools.project.state.info; + +import java.util.List; + +import io.quarkus.bootstrap.workspace.WorkspaceModuleId; + +public class ModuleInfo { + + private WorkspaceModuleId id; + private List extensionOrigins; + private PluginInfo plugin; +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/PluginInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/PluginInfo.java new file mode 100644 index 00000000000000..130c9c6af8e804 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/PluginInfo.java @@ -0,0 +1,8 @@ +package io.quarkus.devtools.project.state.info; + +import io.quarkus.devtools.project.state.ConfiguredArtifact; + +public class PluginInfo { + + private ConfiguredArtifact artifact; +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ProjectInfo.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ProjectInfo.java new file mode 100644 index 00000000000000..1e1042448d0543 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/state/info/ProjectInfo.java @@ -0,0 +1,8 @@ +package io.quarkus.devtools.project.state.info; + +import java.util.List; + +public class ProjectInfo { + + private List modules; +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java index 5b0de3be5e4d67..cc598159a7f476 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/ProjectUpdateInfos.java @@ -192,7 +192,7 @@ public static ProjectState resolveRecommendedState(ProjectState currentState, Ex return stateBuilder.build(); } - private static List getRecommendedOrigins(ExtensionCatalog extensionCatalog, List extensions) + public static List getRecommendedOrigins(ExtensionCatalog extensionCatalog, List extensions) throws QuarkusCommandException { final List extOrigins = new ArrayList<>(extensions.size()); for (Extension e : extensions) { diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusPomUpdateRecipe.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusPomUpdateRecipe.java new file mode 100644 index 00000000000000..68bc5a2a630aa2 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusPomUpdateRecipe.java @@ -0,0 +1,406 @@ +package io.quarkus.devtools.project.update; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.jboss.logging.Logger; + +import io.quarkus.maven.dependency.ArtifactCoords; +import io.quarkus.maven.dependency.ArtifactKey; + +public class QuarkusPomUpdateRecipe { + + private static final String NAME = QuarkusPomUpdateRecipe.class.getName(); + + public static Generator generator() { + return new Generator(); + } + + public static class Generator { + + private Path pom; + private final Map propUpdates = new HashMap<>(3); + private final Map updatedManagedDeps = new HashMap<>(3); + private final Map removedManagedDeps = new HashMap<>(1); + private final Map addedManagedDeps = new HashMap<>(3); + private final Map updatedDeps = new HashMap<>(3); + private final Map pluginVersionUpdates = new HashMap<>(0); + + private Generator() { + } + + public Generator setPom(Path pom) { + this.pom = pom; + return this; + } + + public Generator updateProperty(String name, String newValue) { + return updateProperty(name, newValue, false); + } + + public Generator updateProperty(String name, String newValue, boolean addIfMissing) { + final UpdateProperty update = new UpdateProperty(name, newValue, addIfMissing, false); + final UpdateProperty prev = propUpdates.put(update.getKey(), update); + if (prev != null && prev.isConflicting(update)) { + Logger.getLogger(getClass()).warn("Caught conflicting update of property " + name + " in " + pom + + " with values " + newValue + " and " + prev.newValue); + } + return this; + } + + public Generator updateManagedDependency(String oldGroupId, String oldArtifactId, String newGroupId, + String newArtifactId, String newVersion) { + return updateManagedDependency(oldGroupId, oldArtifactId, newGroupId, newArtifactId, newVersion, null); + } + + public Generator updateManagedDependency(String oldGroupId, String oldArtifactId, String newGroupId, + String newArtifactId, String newVersion, String versionPattern) { + final UpdateManagedDependency update = new UpdateManagedDependency(oldGroupId, oldArtifactId, newGroupId, + newArtifactId, newVersion, versionPattern); + final UpdateManagedDependency prev = updatedManagedDeps.put(update.getKey(), update); + if (prev != null && prev.isConflicting(update)) { + Logger.getLogger(getClass()) + .warn("Caught conflicting update of managed dependency " + update.getKey() + " in " + pom + + " with values " + newGroupId + ":" + newArtifactId + ":" + newVersion + + " and version pattern " + versionPattern); + } + return this; + } + + public Generator removeManagedDependency(String groupId, String artifactId, String scope) { + final RemoveManagedDependency update = new RemoveManagedDependency(groupId, artifactId, scope); + removedManagedDeps.put(update.getKey(), update); + return this; + } + + public Generator addManagedDependency(String groupId, String artifactId, String classifier, + String type, String version, String scope) { + final AddManagedDependency update = new AddManagedDependency(groupId, artifactId, classifier, + type, version, scope); + final AddManagedDependency prev = addedManagedDeps.put(update.getKey(), update); + if (prev != null && prev.isConflicting(update)) { + Logger.getLogger(getClass()) + .warn("Caught conflicting addition of managed dependency " + update.getKey() + " in " + pom + + " with values " + update.getKey() + ":" + version + + " and scope " + scope); + } + return this; + } + + public Generator updateDependency(String oldGroupId, String oldArtifactId, String newGroupId, + String newArtifactId, String newVersion) { + final UpdateDependency update = new UpdateDependency(oldGroupId, oldArtifactId, newGroupId, newArtifactId, + newVersion); + final UpdateDependency prev = updatedDeps.put(update.getKey(), update); + if (prev != null && prev.isConflicting(update)) { + Logger.getLogger(getClass()) + .warn("Caught conflicting update of dependency " + update.getKey() + " in " + pom + + " with values " + newGroupId + ":" + newArtifactId + ":" + newVersion); + } + return this; + } + + public Generator updatePluginVersion(String groupId, String artifactId, String newVersion) { + final UpdatePluginVersion update = new UpdatePluginVersion(groupId, artifactId, newVersion); + final UpdatePluginVersion prev = pluginVersionUpdates.put(update.getKey(), update); + if (prev != null && prev.isConflicting(update)) { + Logger.getLogger(getClass()) + .warn("Caught conflicting update of plugin " + update.getKey() + " in " + pom + + " with values " + newVersion + " and " + prev.newVersion); + } + return this; + } + + public QuarkusPomUpdateRecipe generate() { + final StringWriter writer = new StringWriter(); + try (StringWriter w = writer) { + println(w, "type: specs.openrewrite.org/v1beta/recipe"); + println(w, "name: " + NAME); + println(w, "displayName: Update Quarkus project"); + println(w, "recipeList:"); + for (UpdateProperty pu : propUpdates.values()) { + pu.addToRecipe(w); + } + for (UpdateManagedDependency pu : updatedManagedDeps.values()) { + pu.addToRecipe(w); + } + for (RemoveManagedDependency pu : removedManagedDeps.values()) { + pu.addToRecipe(w); + } + for (AddManagedDependency pu : addedManagedDeps.values()) { + pu.addToRecipe(w); + } + for (UpdateDependency pu : updatedDeps.values()) { + pu.addToRecipe(w); + } + for (UpdatePluginVersion pu : pluginVersionUpdates.values()) { + pu.addToRecipe(w); + } + } catch (IOException e) { + throw new RuntimeException("Failed to write a recipe", e); + } + return new QuarkusPomUpdateRecipe(pom, writer.getBuffer().toString()); + } + } + + private final Path pom; + private final String recipe; + + private QuarkusPomUpdateRecipe(Path pom, String recipe) { + this.pom = Objects.requireNonNull(pom); + this.recipe = Objects.requireNonNull(recipe); + } + + public String getName() { + return NAME; + } + + public Path getPom() { + return pom; + } + + public Path getProjectDir() { + final Path parent = pom.getParent(); + return parent == null ? Path.of("") : parent; + } + + public String toYaml() { + return recipe; + } + + @Override + public String toString() { + return recipe; + } + + private static class UpdateManagedDependency { + final String oldGroupId; + final String oldArtifactId; + final String newGroupId; + final String newArtifactId; + final String newVersion; + final String versionPattern; + + private UpdateManagedDependency(String oldGroupId, String oldArtifactId, String newGroupId, String newArtifactId, + String newVersion, String versionPattern) { + super(); + this.oldGroupId = oldGroupId; + this.oldArtifactId = oldArtifactId; + this.newGroupId = newGroupId; + this.newArtifactId = newArtifactId; + this.newVersion = newVersion; + this.versionPattern = versionPattern; + } + + private ArtifactKey getKey() { + return ArtifactKey.ga(oldGroupId, oldArtifactId); + } + + private boolean isConflicting(UpdateManagedDependency other) { + return !Objects.equals(newGroupId, other.newGroupId) + || !Objects.equals(newArtifactId, other.newArtifactId) + || !Objects.equals(newVersion, other.newVersion) + || !Objects.equals(versionPattern, other.versionPattern); + } + + private void addToRecipe(Writer w) throws IOException { + println(w, " - org.openrewrite.maven.ChangeManagedDependencyGroupIdAndArtifactId:"); + println(w, " oldGroupId: " + oldGroupId); + println(w, " oldArtifactId: " + oldArtifactId); + println(w, " newGroupId: " + (newGroupId != null && !newGroupId.equals(oldGroupId) ? newGroupId : oldGroupId)); + println(w, " newArtifactId: " + + (newArtifactId != null && !newArtifactId.equals(oldArtifactId) ? newArtifactId : oldArtifactId)); + if (newVersion != null) { + println(w, " newVersion: " + newVersion); + if (versionPattern != null) { + println(w, " versionPattern: " + versionPattern); + } + } + } + } + + private static class RemoveManagedDependency { + final String groupId; + final String artifactId; + final String scope; + + private RemoveManagedDependency(String groupId, String artifactId, String scope) { + super(); + this.groupId = groupId; + this.artifactId = artifactId; + this.scope = scope; + } + + private ArtifactKey getKey() { + return ArtifactKey.ga(groupId, artifactId); + } + + private boolean isConflicting(UpdateManagedDependency other) { + return false; + } + + private void addToRecipe(Writer w) throws IOException { + println(w, " - org.openrewrite.maven.RemoveManagedDependency:"); + println(w, " groupId: " + groupId); + println(w, " artifactId: " + artifactId); + if (scope != null) { + println(w, " scope: " + scope); + } + } + } + + private static class AddManagedDependency { + final String groupId; + final String artifactId; + final String classifier; + final String type; + final String version; + final String scope; + + private AddManagedDependency(String groupId, String artifactId, String classifier, String type, String version, + String scope) { + super(); + this.groupId = groupId; + this.artifactId = artifactId; + this.classifier = classifier; + this.type = type; + this.version = version; + this.scope = scope; + } + + private ArtifactKey getKey() { + return ArtifactKey.of(groupId, artifactId, classifier, type); + } + + private boolean isConflicting(AddManagedDependency other) { + return !Objects.equals(version, other.version) + || Objects.equals(scope, other.scope); + } + + private void addToRecipe(Writer w) throws IOException { + println(w, " - org.openrewrite.maven.AddManagedDependency:"); + println(w, " groupId: " + groupId); + println(w, " artifactId: " + artifactId); + if (classifier != null && !classifier.isEmpty()) { + println(w, " classifier: " + classifier); + } + if (type != null && !type.isEmpty() && !type.equals(ArtifactCoords.TYPE_JAR)) { + println(w, " type: " + type); + } + println(w, " version: " + version); + if (scope != null) { + println(w, " scope: " + scope); + } + } + } + + private static class UpdateDependency { + final String oldGroupId; + final String oldArtifactId; + final String newGroupId; + final String newArtifactId; + final String newVersion; + + private UpdateDependency(String oldGroupId, String oldArtifactId, String newGroupId, String newArtifactId, + String newVersion) { + super(); + this.oldGroupId = oldGroupId; + this.oldArtifactId = oldArtifactId; + this.newGroupId = newGroupId; + this.newArtifactId = newArtifactId; + this.newVersion = newVersion; + } + + private ArtifactKey getKey() { + return ArtifactKey.ga(oldGroupId, oldArtifactId); + } + + private boolean isConflicting(UpdateDependency other) { + return !Objects.equals(newVersion, other.newVersion) + || Objects.equals(newGroupId, other.newGroupId) + || Objects.equals(newArtifactId, other.newArtifactId); + } + + private void addToRecipe(Writer w) throws IOException { + println(w, " - org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId:"); + println(w, " oldGroupId: " + oldGroupId); + println(w, " oldArtifactId: " + oldArtifactId); + if (newGroupId != null && !newGroupId.isEmpty()) { + println(w, " newGroupId: " + newGroupId); + } + if (newArtifactId != null && !newArtifactId.isEmpty()) { + println(w, " newArtifactId: " + newArtifactId); + } + println(w, " newVersion: " + newVersion); + } + } + + private static class UpdatePluginVersion { + final String groupId; + final String artifactId; + final String newVersion; + + private UpdatePluginVersion(String groupId, String artifactId, String newVersion) { + super(); + this.groupId = groupId; + this.artifactId = artifactId; + this.newVersion = newVersion; + } + + private ArtifactKey getKey() { + return ArtifactKey.ga(groupId, artifactId); + } + + private boolean isConflicting(UpdatePluginVersion other) { + return !Objects.equals(newVersion, other.newVersion); + } + + private void addToRecipe(Writer w) throws IOException { + println(w, " - org.openrewrite.maven.UpgradePluginVersion:"); + println(w, " oldGroupId: " + groupId); + println(w, " oldArtifactId: " + artifactId); + println(w, " newVersion: " + newVersion); + } + } + + private static class UpdateProperty { + + final String key; + final String newValue; + final boolean addIfMissing; + final boolean trustParent; + + private UpdateProperty(String key, String newValue, boolean addIfMissing, boolean trustParent) { + this.key = key; + this.newValue = newValue; + this.addIfMissing = addIfMissing; + this.trustParent = trustParent; + } + + private String getKey() { + return key; + } + + private boolean isConflicting(UpdateProperty other) { + return !newValue.equals(other.newValue); + } + + private void addToRecipe(Writer w) throws IOException { + println(w, " - org.openrewrite.maven.ChangePropertyValue:"); + println(w, " key: " + key); + println(w, " newValue: " + newValue); + println(w, " addIfMissing: " + addIfMissing); + println(w, " trustParent: " + trustParent); + } + } + + private static void println(Writer writer, String line) throws IOException { + writer.write(line); + writer.write(System.lineSeparator()); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRunner.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRunner.java new file mode 100644 index 00000000000000..5fcdcf39fed3a4 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/update/QuarkusUpdateRunner.java @@ -0,0 +1,96 @@ +package io.quarkus.devtools.project.update; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class QuarkusUpdateRunner { + + public static void applyDirectly(QuarkusPomUpdateRecipe quarkusRecipe, boolean dryRun) { + System.out.println("Updating " + quarkusRecipe.getPom() + " with:"); + System.out.println(quarkusRecipe.toYaml()); + + /* @formatter:off + final Environment environment; + try (InputStream is = new ByteArrayInputStream(quarkusRecipe.toYaml().getBytes())) { + environment = Environment.builder() + .load(new YamlResourceLoader(is, quarkusRecipe.getPom().toUri(), System.getProperties())) + .build(); + } catch (Exception e) { + throw new RuntimeException("Failed initialize Open Rewrite environment", e); + } + Recipe recipe = environment.activateRecipes(quarkusRecipe.getName()); + + ExecutionContext ctx = new InMemoryExecutionContext(Throwable::printStackTrace); + MavenParser mvnParser = MavenParser.builder().build(); + List poms = mvnParser.parse(List.of(quarkusRecipe.getPom()), null, ctx); + List results = recipe.run(poms, ctx).getResults(); + for (Result result : results) { + if (dryRun) { + System.out.println(result.diff(null)); + } else { + try { + Files.writeString(result.getAfter().getSourcePath(), result.getAfter().printAll()); + } catch (IOException e) { + throw new UncheckedIOException("Failed to apply update to " + quarkusRecipe.getPom(), e); + } + } + } + @formatter:on*/ + } + + public static void apply(QuarkusPomUpdateRecipe quarkusRecipe, boolean dryRun) { + + System.out.println("Updating " + quarkusRecipe.getPom() + " with:"); + System.out.println(quarkusRecipe.toYaml()); + + final Path configLocation = quarkusRecipe.getProjectDir().resolve("quarkus-update.yaml"); + try (BufferedWriter writer = Files.newBufferedWriter(configLocation)) { + writer.write(quarkusRecipe.toYaml()); + } catch (IOException e) { + throw new RuntimeException("Failed to persist " + configLocation, e); + } + + Path diffFile = null; + final List commandLine = new ArrayList<>(); + commandLine.add("mvn"); + commandLine.add("-pl"); + commandLine.add("."); + if (dryRun) { + commandLine.add("org.openrewrite.maven:rewrite-maven-plugin:4.39.0:dryRun"); + final Path outputDir = quarkusRecipe.getProjectDir().resolve("target").resolve("rewrite"); + commandLine.add("-DreportOutputDirectory=" + outputDir); + diffFile = outputDir.resolve("rewrite.patch"); + } else { + commandLine.add("org.openrewrite.maven:rewrite-maven-plugin:4.39.0:run"); + } + commandLine.add("-Drewrite.configLocation=" + configLocation); + commandLine.add("-Drewrite.activeRecipes=" + quarkusRecipe.getName()); + + Process process = null; + try { + process = new ProcessBuilder() + .directory(quarkusRecipe.getProjectDir().toFile()) + .command(commandLine) + .inheritIO() + .start(); + if (process.waitFor() != 0) { + System.out.println("ERROR: failed to apply updates to " + quarkusRecipe.getProjectDir()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (diffFile != null && Files.exists(diffFile)) { + try { + System.out.println(Files.readString(diffFile)); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } +}