Skip to content

Commit

Permalink
Merge pull request quarkusio#44167 from aloubyansky/runtime-model-only
Browse files Browse the repository at this point in the history
An option to resolve only the runtime part of the ApplicationModel
aloubyansky authored Nov 5, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 2fe27df + 227776a commit b6d8822
Showing 9 changed files with 242 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -83,6 +83,12 @@ public class DependencySbomMojo extends AbstractMojo {
@Parameter(property = "quarkus.dependency.sbom.schema-version")
String schemaVersion;

/**
* Whether to limit application dependencies to only those that are included in the runtime
*/
@Parameter(property = "quarkus.dependency.sbom.runtime-only")
boolean runtimeOnly;

protected MavenArtifactResolver resolver;

@Override
@@ -111,7 +117,8 @@ private ApplicationModel resolveApplicationModel()
project.getVersion());
final BootstrapAppModelResolver modelResolver;
try {
modelResolver = new BootstrapAppModelResolver(getResolver());
modelResolver = new BootstrapAppModelResolver(getResolver())
.setRuntimeModelOnly(runtimeOnly);
if (mode != null) {
if (mode.equalsIgnoreCase("test")) {
modelResolver.setTest(true);
Original file line number Diff line number Diff line change
@@ -85,6 +85,12 @@ public class DependencyTreeMojo extends AbstractMojo {
@Parameter(property = "appendOutput", required = false, defaultValue = "false")
boolean appendOutput;

/**
* Whether to log only the runtime dependencies of the Quarkus application
*/
@Parameter(property = "runtimeOnly")
boolean runtimeOnly;

protected MavenArtifactResolver resolver;

@Override
@@ -134,7 +140,8 @@ private void logTree(final Consumer<String> log) throws MojoExecutionException {
project.getVersion());
final BootstrapAppModelResolver modelResolver;
try {
modelResolver = new BootstrapAppModelResolver(resolver());
modelResolver = new BootstrapAppModelResolver(resolver())
.setRuntimeModelOnly(runtimeOnly);
if (mode != null) {
if (mode.equalsIgnoreCase("test")) {
modelResolver.setTest(true);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.maven;

import io.quarkus.bootstrap.resolver.TsArtifact;
import io.quarkus.bootstrap.resolver.TsQuarkusExt;

public class ConditionalDependencyTreeMojoRuntimeOnlyTest extends DependencyTreeMojoTestBase {
@Override
protected String mode() {
return "prod";
}

@Override
protected boolean isIncubatingModelResolver() {
return true;
}

@Override
protected boolean isRuntimeOnly() {
return true;
}

@Override
protected void initRepo() {

final TsQuarkusExt coreExt = new TsQuarkusExt("test-core-ext");

var tomatoExt = new TsQuarkusExt("quarkus-tomato").addDependency(coreExt);
var mozzarellaExt = new TsQuarkusExt("quarkus-mozzarella").addDependency(coreExt);
var basilExt = new TsQuarkusExt("quarkus-basil").addDependency(coreExt);

var oilJar = TsArtifact.jar("quarkus-oil");

var capreseExt = new TsQuarkusExt("quarkus-caprese")
.setDependencyCondition(tomatoExt, mozzarellaExt, basilExt)
.addDependency(coreExt);
capreseExt.getDeployment().addDependency(oilJar);
capreseExt.install(repoBuilder);

var saladExt = new TsQuarkusExt("quarkus-salad")
.setConditionalDeps(capreseExt)
.addDependency(coreExt);

app = TsArtifact.jar("app-with-conditional-deps")
.addDependency(tomatoExt)
.addDependency(mozzarellaExt)
.addDependency(basilExt)
.addDependency(saladExt)
.addDependency(oilJar);

appModel = app.getPomModel();
app.install(repoBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -65,6 +65,10 @@ protected boolean isIncubatingModelResolver() {
return false;
}

protected boolean isRuntimeOnly() {
return false;
}

@Test
public void test() throws Exception {

@@ -81,6 +85,7 @@ public void test() throws Exception {
mojo.resolver = mvnResolver;
mojo.mode = mode();
mojo.graph = isGraph();
mojo.runtimeOnly = isRuntimeOnly();

final Path mojoLog = workDir.resolve(getClass().getName() + ".log");
final PrintStream defaultOut = System.out;
@@ -92,8 +97,12 @@ public void test() throws Exception {
System.setOut(defaultOut);
}

String expectedFileName = app.getArtifactFileName() + "." + mode();
if (isRuntimeOnly()) {
expectedFileName += ".rt";
}
assertThat(mojoLog).hasSameTextualContentAs(
Path.of("").normalize().toAbsolutePath()
.resolve("target").resolve("test-classes").resolve(app.getArtifactFileName() + "." + mode()));
.resolve("target").resolve("test-classes").resolve(expectedFileName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[info] Quarkus application PROD mode build dependency tree:
[info] io.quarkus.bootstrap.test:app-with-conditional-deps:pom:1
[info] ├─ io.quarkus.bootstrap.test:quarkus-tomato:1 (compile)
[info] │ └─ io.quarkus.bootstrap.test:test-core-ext:1 (compile)
[info] ├─ io.quarkus.bootstrap.test:quarkus-mozzarella:1 (compile)
[info] ├─ io.quarkus.bootstrap.test:quarkus-basil:1 (compile)
[info] ├─ io.quarkus.bootstrap.test:quarkus-salad:1 (compile)
[info] │ └─ io.quarkus.bootstrap.test:quarkus-caprese:1 (compile)
[info] └─ io.quarkus.bootstrap.test:quarkus-oil:1 (compile)
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.quarkus.bootstrap.resolver.test;

import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
import io.quarkus.bootstrap.resolver.TsArtifact;
import io.quarkus.bootstrap.resolver.TsQuarkusExt;
import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
import io.quarkus.maven.dependency.DependencyFlags;

public class RuntimeOnlyApplicationModelTestCase extends CollectDependenciesBase {

private static final boolean runtimeOnly = true;

@Override
protected BootstrapAppModelResolver newAppModelResolver(LocalProject currentProject) throws Exception {
var resolver = super.newAppModelResolver(currentProject);
resolver.setIncubatingModelResolver(false);
resolver.setRuntimeModelOnly(runtimeOnly);
return resolver;
}

@Override
protected void setupDependencies() {

final TsQuarkusExt extA = new TsQuarkusExt("ext-a");
install(extA, false);
if (!runtimeOnly) {
addCollectedDeploymentDep(extA.getDeployment());
}

installAsDep(extA.getRuntime(),
DependencyFlags.DIRECT
| DependencyFlags.RUNTIME_EXTENSION_ARTIFACT
| DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT);

final TsQuarkusExt extB = new TsQuarkusExt("ext-b");
install(extB, false);

final TsQuarkusExt extC = new TsQuarkusExt("ext-c");
extC.setDependencyCondition(extB);
install(extC, false);

final TsQuarkusExt extD = new TsQuarkusExt("ext-d");
install(extD, false);
installAsDep(extD.getRuntime(),
DependencyFlags.DIRECT
| DependencyFlags.RUNTIME_EXTENSION_ARTIFACT
| DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT);
if (!runtimeOnly) {
addCollectedDeploymentDep(extD.getDeployment());
}

final TsArtifact libE = TsArtifact.jar("lib-e");
install(libE, true);
final TsArtifact libEBuildTIme = TsArtifact.jar("lib-e-build-time");
install(libEBuildTIme);
if (!runtimeOnly) {
addCollectedDeploymentDep(libEBuildTIme);
}

final TsQuarkusExt extE = new TsQuarkusExt("ext-e");
extE.setDependencyCondition(extD);
extE.getRuntime().addDependency(libE);
extE.getDeployment().addDependency(libEBuildTIme);
install(extE, false);
addCollectedDep(extE.getRuntime(), DependencyFlags.RUNTIME_EXTENSION_ARTIFACT);
if (!runtimeOnly) {
addCollectedDeploymentDep(extE.getDeployment());
}

final TsQuarkusExt extF = new TsQuarkusExt("ext-f");
extF.setConditionalDeps(extC, extE);
install(extF, false);
installAsDep(extF.getRuntime(),
DependencyFlags.DIRECT
| DependencyFlags.RUNTIME_EXTENSION_ARTIFACT
| DependencyFlags.TOP_LEVEL_RUNTIME_EXTENSION_ARTIFACT);
if (!runtimeOnly) {
addCollectedDeploymentDep(extF.getDeployment());
}
}
}
Original file line number Diff line number Diff line change
@@ -62,6 +62,7 @@ public class BootstrapAppModelResolver implements AppModelResolver {
protected boolean test;
private boolean collectReloadableDeps = true;
private boolean incubatingModelResolver;
private boolean runtimeModelOnly;

public BootstrapAppModelResolver(MavenArtifactResolver mvn) {
this.mvn = mvn;
@@ -110,6 +111,11 @@ public BootstrapAppModelResolver setCollectReloadableDependencies(boolean collec
return this;
}

public BootstrapAppModelResolver setRuntimeModelOnly(boolean runtimeModelOnly) {
this.runtimeModelOnly = runtimeModelOnly;
return this;
}

public void addRemoteRepositories(List<RemoteRepository> repos) {
mvn.addRemoteRepositories(repos);
}
@@ -371,6 +377,7 @@ private ApplicationModel buildAppModel(ResolvedDependencyBuilder appArtifact,
.setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty())
.setCollectCompileOnly(filteredProvidedDeps)
.setDependencyLogging(depLogConfig)
.setRuntimeModelOnly(runtimeModelOnly)
.resolve(collectRtDepsRequest);
} else {
ApplicationDependencyTreeResolver.newInstance()
@@ -379,6 +386,7 @@ private ApplicationModel buildAppModel(ResolvedDependencyBuilder appArtifact,
.setCollectReloadableModules(collectReloadableDeps && reloadableModules.isEmpty())
.setCollectCompileOnly(filteredProvidedDeps)
.setBuildTreeConsumer(depLogConfig == null ? null : depLogConfig.getMessageConsumer())
.setRuntimeModelOnly(runtimeModelOnly)
.resolve(collectRtDepsRequest);
}
if (logTime) {
Original file line number Diff line number Diff line change
@@ -106,6 +106,7 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) {
private boolean collectReloadableModules;
private Consumer<String> buildTreeConsumer;
private List<Dependency> collectCompileOnly;
private boolean runtimeModelOnly;

public ApplicationDependencyTreeResolver setArtifactResolver(MavenArtifactResolver resolver) {
this.resolver = resolver;
@@ -139,6 +140,11 @@ public ApplicationDependencyTreeResolver setCollectCompileOnly(List<Dependency>
return this;
}

public ApplicationDependencyTreeResolver setRuntimeModelOnly(boolean runtimeModelOnly) {
this.runtimeModelOnly = runtimeModelOnly;
return this;
}

public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolverException {

this.managedDeps = collectRtDepsRequest.getManagedDependencies();
@@ -202,13 +208,15 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver
}
}

for (ExtensionDependency extDep : topExtensionDeps) {
injectDeploymentDependencies(extDep);
}
if (!runtimeModelOnly) {
for (ExtensionDependency extDep : topExtensionDeps) {
injectDeploymentDependencies(extDep);
}

if (!activatedConditionalDeps.isEmpty()) {
for (ConditionalDependency cd : activatedConditionalDeps) {
injectDeploymentDependencies(cd.getExtensionDependency());
if (!activatedConditionalDeps.isEmpty()) {
for (ConditionalDependency cd : activatedConditionalDeps) {
injectDeploymentDependencies(cd.getExtensionDependency());
}
}
}

@@ -231,7 +239,9 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver
}

collectPlatformProperties();
collectCompileOnly(collectRtDepsRequest, root);
if (!runtimeModelOnly) {
collectCompileOnly(collectRtDepsRequest, root);
}
}

/**
@@ -886,7 +896,7 @@ void activate() {
return;
}
activated = true;
clearWalkingFlag(COLLECT_TOP_EXTENSION_RUNTIME_NODES);
clearWalkingFlag((byte) (COLLECT_DIRECT_DEPS | COLLECT_TOP_EXTENSION_RUNTIME_NODES));
final ExtensionDependency extDep = getExtensionDependency();
final DependencyNode originalNode = collectDependencies(info.runtimeArtifact, extDep.exclusions,
extDep.runtimeNode.getRepositories());
Original file line number Diff line number Diff line change
@@ -131,14 +131,15 @@ public static IncubatingApplicationModelResolver newInstance() {

private final List<ExtensionDependency> topExtensionDeps = new ArrayList<>();
private final Map<ArtifactKey, ExtensionInfo> allExtensions = new ConcurrentHashMap<>();
private List<ConditionalDependency> conditionalDepsToProcess = new ArrayList<>();
private Collection<ConditionalDependency> conditionalDepsToProcess = new ConcurrentLinkedDeque<>();

private MavenArtifactResolver resolver;
private List<Dependency> managedDeps;
private ApplicationModelBuilder appBuilder;
private boolean collectReloadableModules;
private DependencyLoggingConfig depLogging;
private List<Dependency> collectCompileOnly;
private boolean runtimeModelOnly;

public IncubatingApplicationModelResolver setArtifactResolver(MavenArtifactResolver resolver) {
this.resolver = resolver;
@@ -172,6 +173,11 @@ public IncubatingApplicationModelResolver setCollectCompileOnly(List<Dependency>
return this;
}

public IncubatingApplicationModelResolver setRuntimeModelOnly(boolean runtimeModelOnly) {
this.runtimeModelOnly = runtimeModelOnly;
return this;
}

public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolverException {
this.managedDeps = collectRtDepsRequest.getManagedDependencies();
// managed dependencies will be a bit augmented with every added extension, so let's load the properties early
@@ -183,10 +189,13 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver
final List<ConditionalDependency> activatedConditionalDeps = activateConditionalDeps();

// resolve and inject deployment dependency branches for the top (first met) runtime extension nodes
injectDeployment(activatedConditionalDeps);
if (!runtimeModelOnly) {
injectDeployment(activatedConditionalDeps);
}
root = normalize(resolver.getSession(), root);
processDeploymentDeps(root);
populateModelBuilder(root);

// clear the reloadable flags
for (var d : appBuilder.getDependencies()) {
if (!d.isFlagSet(DependencyFlags.RELOADABLE) && !d.isFlagSet(DependencyFlags.VISITED)) {
clearReloadableFlag(d);
@@ -198,10 +207,14 @@ public void resolve(CollectRequest collectRtDepsRequest) throws AppModelResolver
if (d.isFlagSet(DependencyFlags.RELOADABLE)) {
appBuilder.addReloadableWorkspaceModule(d.getKey());
}
appBuilder.addDependency(d);
if (!runtimeModelOnly) {
d.setFlags(DependencyFlags.DEPLOYMENT_CP);
}
}

collectCompileOnly(collectRtDepsRequest, root);
if (!runtimeModelOnly) {
collectCompileOnly(collectRtDepsRequest, root);
}
}

private List<ConditionalDependency> activateConditionalDeps() {
@@ -213,7 +226,7 @@ private List<ConditionalDependency> activateConditionalDeps() {
while (!conditionalDepsToProcess.isEmpty() && checkDependencyConditions) {
checkDependencyConditions = false;
var unsatisfiedConditionalDeps = conditionalDepsToProcess;
conditionalDepsToProcess = new ArrayList<>();
conditionalDepsToProcess = new ConcurrentLinkedDeque<>();
for (ConditionalDependency cd : unsatisfiedConditionalDeps) {
if (cd.isSatisfied()) {
cd.activate();
@@ -228,7 +241,7 @@ private List<ConditionalDependency> activateConditionalDeps() {
return activatedConditionalDeps;
}

private void processDeploymentDeps(DependencyNode root) {
private void populateModelBuilder(DependencyNode root) {
var app = new AppDep(root);
final ModelResolutionTaskRunner taskRunner = new ModelResolutionTaskRunner();
app.scheduleChildVisits(taskRunner, AppDep::scheduleDeploymentVisit);
@@ -237,14 +250,15 @@ private void processDeploymentDeps(DependencyNode root) {
for (var d : app.children) {
d.addToModel();
}

if (depLogging != null) {
new AppDepLogger().log(app);
}
}

private void injectDeployment(List<ConditionalDependency> activatedConditionalDeps) {
final ConcurrentLinkedDeque<Runnable> injectQueue = new ConcurrentLinkedDeque<>();
// non-conditional deployment branches should be added before the activated conditional ones to have consistent
// dependency graph structures
collectDeploymentDeps(injectQueue);
if (!activatedConditionalDeps.isEmpty()) {
collectConditionalDeploymentDeps(activatedConditionalDeps, injectQueue);
@@ -258,42 +272,34 @@ private void collectConditionalDeploymentDeps(List<ConditionalDependency> activa
ConcurrentLinkedDeque<Runnable> injectQueue) {
var taskRunner = new ModelResolutionTaskRunner();
for (ConditionalDependency cd : activatedConditionalDeps) {
taskRunner.run(() -> {
var resolvedDep = appBuilder.getDependency(getKey(cd.conditionalDep.ext.info.deploymentArtifact));
if (resolvedDep == null) {
var extDep = cd.getExtensionDependency();
extDep.collectDeploymentDeps();
injectQueue.add(() -> extDep.injectDeploymentNode(cd.conditionalDep.ext.getParentDeploymentNode()));
} else {
// if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath
// in which case we also clear the reloadable flag on it, in case it's coming from the workspace
resolvedDep.clearFlag(DependencyFlags.RELOADABLE);
}
});
injectDeploymentDep(taskRunner, cd.getExtensionDependency(), injectQueue, true);
}
taskRunner.waitForCompletion();
}

private void collectDeploymentDeps(ConcurrentLinkedDeque<Runnable> injectQueue) {
var taskRunner = new ModelResolutionTaskRunner();
for (ExtensionDependency extDep : topExtensionDeps) {
taskRunner.run(() -> {
var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact));
if (resolvedDep == null) {
extDep.collectDeploymentDeps();
injectQueue.add(() -> extDep.injectDeploymentNode(null));
} else {
// if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath
// in which case we also clear the reloadable flag on it, in case it's coming from the workspace
resolvedDep.clearFlag(DependencyFlags.RELOADABLE);
}
});
injectDeploymentDep(taskRunner, extDep, injectQueue, false);
}
// non-conditional deployment branches should be added before the activated conditional ones to have consistent
// dependency graph structures
taskRunner.waitForCompletion();
}

private void injectDeploymentDep(ModelResolutionTaskRunner taskRunner, ExtensionDependency extDep,
ConcurrentLinkedDeque<Runnable> injectQueue, boolean conditionalDep) {
taskRunner.run(() -> {
var resolvedDep = appBuilder.getDependency(getKey(extDep.info.deploymentArtifact));
if (resolvedDep == null) {
extDep.collectDeploymentDeps();
injectQueue.add(() -> extDep.injectDeploymentNode(conditionalDep ? extDep.getParentDeploymentNode() : null));
} else {
// if resolvedDep isn't null, it means the deployment artifact is on the runtime classpath
// in which case we also clear the reloadable flag on it, in case it's coming from the workspace
resolvedDep.clearFlag(DependencyFlags.RELOADABLE);
}
});
}

/**
* Resolves and adds compile-only dependencies to the application model with the {@link DependencyFlags#COMPILE_ONLY} flag.
* Compile-only dependencies are resolved as direct dependencies of the root with all the previously resolved dependencies
@@ -452,11 +458,15 @@ private void processRuntimeDeps(DependencyNode root) {
appRoot.walkingFlags |= COLLECT_RELOADABLE_MODULES;
}

visitRuntimeDeps(appRoot);
appBuilder.getApplicationArtifact().addDependencies(appRoot.allDeps);
appRoot.setChildFlags();
}

private void visitRuntimeDeps(AppDep appRoot) {
final ModelResolutionTaskRunner taskRunner = new ModelResolutionTaskRunner();
appRoot.scheduleChildVisits(taskRunner, AppDep::scheduleRuntimeVisit);
taskRunner.waitForCompletion();
appBuilder.getApplicationArtifact().addDependencies(appRoot.allDeps);
appRoot.setChildFlags();
}

private class AppDep {
@@ -501,8 +511,7 @@ void scheduleDeploymentVisit(ModelResolutionTaskRunner taskRunner) {
void visitDeploymentDependency() {
if (!appBuilder.hasDependency(getKey(node.getArtifact()))) {
try {
resolvedDep = newDependencyBuilder(node, resolver)
.setFlags(DependencyFlags.DEPLOYMENT_CP);
resolvedDep = newDependencyBuilder(node, resolver);
} catch (BootstrapMavenException e) {
throw new RuntimeException(e);
}
@@ -1053,7 +1062,6 @@ void activate() {
} else {
currentChildren.addAll(originalNode.getChildren());
}
conditionalDep.walkingFlags = COLLECT_DIRECT_DEPS;
if (collectReloadableModules) {
conditionalDep.walkingFlags |= COLLECT_RELOADABLE_MODULES;
}

0 comments on commit b6d8822

Please sign in to comment.