Skip to content

Commit

Permalink
Allow to filter dependencies in multi-build mode
Browse files Browse the repository at this point in the history
  • Loading branch information
essobedo committed Apr 18, 2021
1 parent bfbd433 commit 4795a69
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.List;
import java.util.Optional;
import java.util.Set;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
Expand Down Expand Up @@ -56,6 +57,37 @@ public class PackageConfig {
@ConfigItem
public Optional<List<String>> userConfiguredIgnoredEntries;

/**
* List of all the dependencies that have been defined as optional to include into the final package of the application.
* Each optional dependency needs to be expressed in the following format:
* <p>
* groupId:artifactId:classifier:type
* <p>
* With the classifier and type being optional.
* <p>
* If the type is missing, the artifact is assumed to be of type {@code jar}.
* <p>
* This parameter is optional, if absent, no optional dependencies will be included into the final package of
* the application.
* <p>
* For backward compatibility reasons, this parameter is ignored by default and can be enabled by setting the
* parameter {@code quarkus.package.filter-optional-dependencies} to {@code true}.
* <p>
* This parameter is meant to be used in modules where multi-builds have been configured to avoid getting a final
* package with unused dependencies.
*/
@ConfigItem
public Optional<Set<String>> optionalDependencies;

/**
* Flag indicating whether the optional dependencies should be filtered out or not.
* <p>
* This parameter is meant to be used in modules where multi-builds have been configured to avoid getting a final
* package with unused dependencies.
*/
@ConfigItem(defaultValue = "false")
public boolean filterOptionalDependencies;

/**
* The suffix that is applied to the runner jar and native images
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package io.quarkus.deployment.pkg.builditem;

import java.nio.file.Path;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;

import io.quarkus.bootstrap.model.AppArtifactKey;
import io.quarkus.builder.item.SimpleBuildItem;

/**
Expand All @@ -16,12 +19,15 @@ public final class OutputTargetBuildItem extends SimpleBuildItem {
private final String baseName;
private final boolean rebuild;
private final Properties buildSystemProperties;
private final Optional<Set<AppArtifactKey>> optionalDependencies;

public OutputTargetBuildItem(Path outputDirectory, String baseName, boolean rebuild, Properties buildSystemProperties) {
public OutputTargetBuildItem(Path outputDirectory, String baseName, boolean rebuild, Properties buildSystemProperties,
Optional<Set<AppArtifactKey>> optionalDependencies) {
this.outputDirectory = outputDirectory;
this.baseName = baseName;
this.rebuild = rebuild;
this.buildSystemProperties = buildSystemProperties;
this.optionalDependencies = optionalDependencies;
}

public Path getOutputDirectory() {
Expand All @@ -39,4 +45,8 @@ public boolean isRebuild() {
public Properties getBuildSystemProperties() {
return buildSystemProperties;
}

public Optional<Set<AppArtifactKey>> getOptionalDependencies() {
return optionalDependencies;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,10 @@
import org.apache.commons.lang3.SystemUtils;
import org.jboss.logging.Logger;

import io.quarkus.bootstrap.BootstrapDependencyProcessingException;
import io.quarkus.bootstrap.model.AppArtifact;
import io.quarkus.bootstrap.model.AppArtifactKey;
import io.quarkus.bootstrap.model.AppDependency;
import io.quarkus.bootstrap.model.PersistentAppModel;
import io.quarkus.bootstrap.resolver.AppModelResolverException;
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
import io.quarkus.bootstrap.runner.SerializedApplication;
import io.quarkus.bootstrap.util.IoUtils;
Expand Down Expand Up @@ -155,7 +153,15 @@ OutputTargetBuildItem outputTarget(BuildSystemTargetBuildItem bst, PackageConfig
String name = packageConfig.outputName.orElseGet(bst::getBaseName);
Path path = packageConfig.outputDirectory.map(s -> bst.getOutputDirectory().resolve(s))
.orElseGet(bst::getOutputDirectory);
return new OutputTargetBuildItem(path, name, bst.isRebuild(), bst.getBuildSystemProps());
Optional<Set<AppArtifactKey>> optionalDependencies;
if (packageConfig.filterOptionalDependencies) {
optionalDependencies = Optional.of(packageConfig.optionalDependencies
.map(set -> set.stream().map(AppArtifactKey::fromString).collect(Collectors.toSet()))
.orElse(Collections.emptySet()));
} else {
optionalDependencies = Optional.empty();
}
return new OutputTargetBuildItem(path, name, bst.isRebuild(), bst.getBuildSystemProps(), optionalDependencies);
}

@BuildStep(onlyIf = JarRequired.class)
Expand Down Expand Up @@ -195,8 +201,7 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem
if (legacyJarRequired.isEmpty() && (!uberJarRequired.isEmpty()
|| packageConfig.type.equalsIgnoreCase(PackageConfig.UBER_JAR))) {
return buildUberJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, applicationArchivesBuildItem,
packageConfig, applicationInfo, generatedClasses, generatedResources, closeablesBuildItem,
mainClassBuildItem);
packageConfig, applicationInfo, generatedClasses, generatedResources, mainClassBuildItem);
} else if (!legacyJarRequired.isEmpty() || packageConfig.isLegacyJar()
|| packageConfig.type.equalsIgnoreCase(PackageConfig.LEGACY)) {
return buildLegacyThinJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses,
Expand Down Expand Up @@ -243,7 +248,6 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
ApplicationInfoBuildItem applicationInfo,
List<GeneratedClassBuildItem> generatedClasses,
List<GeneratedResourceBuildItem> generatedResources,
QuarkusBuildCloseablesBuildItem closeablesBuildItem,
MainClassBuildItem mainClassBuildItem) throws Exception {

//we use the -runner jar name, unless we are building both types
Expand All @@ -252,6 +256,7 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
Files.deleteIfExists(runnerJar);

buildUberJar0(curateOutcomeBuildItem,
outputTargetBuildItem,
transformedClasses,
applicationArchivesBuildItem,
packageConfig,
Expand All @@ -276,6 +281,7 @@ private String suffixToClassifier(String suffix) {
}

private void buildUberJar0(CurateOutcomeBuildItem curateOutcomeBuildItem,
OutputTargetBuildItem outputTargetBuildItem,
TransformedClassesBuildItem transformedClasses,
ApplicationArchivesBuildItem applicationArchivesBuildItem,
PackageConfig packageConfig,
Expand Down Expand Up @@ -306,7 +312,8 @@ private void buildUberJar0(CurateOutcomeBuildItem curateOutcomeBuildItem,
final AppArtifact depArtifact = appDep.getArtifact();

// Exclude files that are not jars (typically, we can have XML files here, see https://github.com/quarkusio/quarkus/issues/2852)
if (!isAppDepAJar(depArtifact)) {
// And exclude dependencies that should not be kept
if (!isAppDepAJar(depArtifact) || !keepDependency(appDep, outputTargetBuildItem.getOptionalDependencies())) {
continue;
}

Expand Down Expand Up @@ -424,7 +431,8 @@ private JarBuildItem buildLegacyThinJar(CurateOutcomeBuildItem curateOutcomeBuil

log.info("Building thin jar: " + runnerJar);

doLegacyThinJarGeneration(curateOutcomeBuildItem, transformedClasses, applicationArchivesBuildItem, applicationInfo,
doLegacyThinJarGeneration(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses,
applicationArchivesBuildItem, applicationInfo,
packageConfig, generatedResources, libDir, generatedClasses, runnerZipFs, mainClassBuildItem);
}
runnerJar.toFile().setReadable(true, false);
Expand Down Expand Up @@ -576,7 +584,8 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
if (rebuild) {
jars.addAll(appDep.getArtifact().getPaths().toList());
} else {
copyDependency(curateOutcomeBuildItem, copiedArtifacts, mainLib, baseLib, jars, true, classPath, appDep);
copyDependency(curateOutcomeBuildItem, outputTargetBuildItem, copiedArtifacts, mainLib, baseLib, jars, true,
classPath, appDep);
}
if (curateOutcomeBuildItem.getEffectiveModel().getRunnerParentFirstArtifacts()
.contains(appDep.getArtifact().getKey())) {
Expand Down Expand Up @@ -631,7 +640,8 @@ private JarBuildItem buildThinJar(CurateOutcomeBuildItem curateOutcomeBuildItem,
Path deploymentLib = libDir.resolve(DEPLOYMENT_LIB);
Files.createDirectories(deploymentLib);
for (AppDependency appDep : curateOutcomeBuildItem.getEffectiveModel().getFullDeploymentDeps()) {
copyDependency(curateOutcomeBuildItem, copiedArtifacts, deploymentLib, baseLib, jars, false, classPath,
copyDependency(curateOutcomeBuildItem, outputTargetBuildItem, copiedArtifacts, deploymentLib, baseLib, jars,
false, classPath,
appDep);
}

Expand Down Expand Up @@ -753,13 +763,15 @@ private boolean decompile(Path fernflowerJar, Path decompiledOutputDir, Path jar
return true;
}

private void copyDependency(CurateOutcomeBuildItem curateOutcomeBuildItem, Map<AppArtifactKey, List<Path>> runtimeArtifacts,
Path libDir, Path baseLib, List<Path> jars, boolean allowParentFirst, StringBuilder classPath, AppDependency appDep)
private void copyDependency(CurateOutcomeBuildItem curateOutcomeBuildItem, OutputTargetBuildItem outputTargetBuildItem,
Map<AppArtifactKey, List<Path>> runtimeArtifacts, Path libDir, Path baseLib, List<Path> jars,
boolean allowParentFirst, StringBuilder classPath, AppDependency appDep)
throws IOException {
final AppArtifact depArtifact = appDep.getArtifact();

// Exclude files that are not jars (typically, we can have XML files here, see https://github.com/quarkusio/quarkus/issues/2852)
if (!isAppDepAJar(depArtifact)) {
// And exclude dependencies that should not be kept
if (!isAppDepAJar(depArtifact) || !keepDependency(appDep, outputTargetBuildItem.getOptionalDependencies())) {
return;
}
if (runtimeArtifacts.containsKey(depArtifact.getKey())) {
Expand Down Expand Up @@ -876,8 +888,9 @@ private NativeImageSourceJarBuildItem buildNativeImageThinJar(CurateOutcomeBuild

log.info("Building native image source jar: " + runnerJar);

doLegacyThinJarGeneration(curateOutcomeBuildItem, transformedClasses, applicationArchivesBuildItem, applicationInfo,
packageConfig, generatedResources, libDir, allClasses, runnerZipFs, mainClassBuildItem);
doLegacyThinJarGeneration(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses,
applicationArchivesBuildItem, applicationInfo, packageConfig, generatedResources, libDir, allClasses,
runnerZipFs, mainClassBuildItem);
}
runnerJar.toFile().setReadable(true, false);
return new NativeImageSourceJarBuildItem(runnerJar, libDir);
Expand All @@ -898,6 +911,7 @@ private NativeImageSourceJarBuildItem buildNativeImageUberJar(CurateOutcomeBuild
.resolve(outputTargetBuildItem.getBaseName() + packageConfig.runnerSuffix + ".jar");

buildUberJar0(curateOutcomeBuildItem,
outputTargetBuildItem,
transformedClasses,
applicationArchivesBuildItem,
packageConfig,
Expand Down Expand Up @@ -937,6 +951,7 @@ public void accept(Path jsonPath) {
}

private void doLegacyThinJarGeneration(CurateOutcomeBuildItem curateOutcomeBuildItem,
OutputTargetBuildItem outputTargetBuildItem,
TransformedClassesBuildItem transformedClasses,
ApplicationArchivesBuildItem applicationArchivesBuildItem,
ApplicationInfoBuildItem applicationInfo,
Expand All @@ -946,7 +961,7 @@ private void doLegacyThinJarGeneration(CurateOutcomeBuildItem curateOutcomeBuild
List<GeneratedClassBuildItem> allClasses,
FileSystem runnerZipFs,
MainClassBuildItem mainClassBuildItem)
throws BootstrapDependencyProcessingException, AppModelResolverException, IOException {
throws IOException {
final Map<String, String> seen = new HashMap<>();
final StringBuilder classPath = new StringBuilder();
final Map<String, List<byte[]>> services = new HashMap<>();
Expand All @@ -955,7 +970,8 @@ private void doLegacyThinJarGeneration(CurateOutcomeBuildItem curateOutcomeBuild
final Set<String> finalIgnoredEntries = new HashSet<>(IGNORED_ENTRIES);
packageConfig.userConfiguredIgnoredEntries.ifPresent(finalIgnoredEntries::addAll);

copyLibraryJars(runnerZipFs, transformedClasses, libDir, classPath, appDeps, services, finalIgnoredEntries);
copyLibraryJars(runnerZipFs, outputTargetBuildItem, transformedClasses, libDir, classPath, appDeps, services,
finalIgnoredEntries);

AppArtifact appArtifact = curateOutcomeBuildItem.getEffectiveModel().getAppArtifact();
// the manifest needs to be the first entry in the jar, otherwise JarInputStream does not work properly
Expand All @@ -967,15 +983,17 @@ private void doLegacyThinJarGeneration(CurateOutcomeBuildItem curateOutcomeBuild
generatedResources, seen, finalIgnoredEntries);
}

private void copyLibraryJars(FileSystem runnerZipFs, TransformedClassesBuildItem transformedClasses, Path libDir,
private void copyLibraryJars(FileSystem runnerZipFs, OutputTargetBuildItem outputTargetBuildItem,
TransformedClassesBuildItem transformedClasses, Path libDir,
StringBuilder classPath, List<AppDependency> appDeps, Map<String, List<byte[]>> services,
Set<String> ignoredEntries) throws IOException {

for (AppDependency appDep : appDeps) {
final AppArtifact depArtifact = appDep.getArtifact();

// Exclude files that are not jars (typically, we can have XML files here, see https://github.com/quarkusio/quarkus/issues/2852)
if (!isAppDepAJar(depArtifact)) {
// And exclude dependencies that should not be kept
if (!isAppDepAJar(depArtifact) || !keepDependency(appDep, outputTargetBuildItem.getOptionalDependencies())) {
continue;
}

Expand Down Expand Up @@ -1249,6 +1267,29 @@ public void accept(Path path) {
}
}

/**
* Indicates whether the given dependency should be kept or not.
* <p>
* A dependency will be kept if:
* <p>
* <ul>
* <li>The dependency is not optional</li>
* <li>The dependency is part of the given optional dependencies</li>
* <li>The given optional dependencies are absent</li>
* </ul>
*
* @param appDep the dependency to test.
* @param optionalDependencies the optional dependencies expected into the final package.
* @return {@code true} if the dependency should be kept, {@code false} otherwise.
*/
private static boolean keepDependency(AppDependency appDep, Optional<Set<AppArtifactKey>> optionalDependencies) {
if (appDep.isOptional()) {
return optionalDependencies.map(appArtifactKeys -> appArtifactKeys.contains(appDep.getArtifact().getKey()))
.orElse(true);
}
return true;
}

private void addDir(FileSystem fs, final String relativePath)
throws IOException {
final Path targetDir = fs.getPath(relativePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,29 @@ void testMultiBuildMode() throws MavenInvocationException, InterruptedException,
testDir = initProject("projects/multi-build-mode");
build(null);

launch("foo-", "Foo: hello, from foo");
launch("bar-", "Bar: hello, from bar");
for (TestContext context : TestContext.values()) {
if (context == TestContext.FAST_NO_PREFIX) {
continue;
}
launch(context, "foo-", "Foo: hello, from foo-?/MultiSet");
launch(context, "bar-", "Bar: hello, from bar-FileUtils/?");
launch(context, "foo-full-", "Foo: hello, from foo-FileUtils/MultiSet");
launch(context, "bar-empty-", "Bar: hello, from bar-?/?");
}
}

private void launch() throws IOException {
launch("", "hello, from foo");
launch(TestContext.FAST_NO_PREFIX, "", "hello, from foo");
}

private void launch(String outputPrefix, String expectedMessage) throws IOException {
File output = new File(testDir, String.format("target/%soutput.log", outputPrefix));
private void launch(TestContext context, String outputPrefix, String expectedMessage) throws IOException {
File output = new File(testDir, String.format("target/%s%soutput.log", context.prefix, outputPrefix));
output.createNewFile();
Process process = JarRunnerIT.doLaunch(new File(testDir, String.format("target/%squarkus-app", outputPrefix)),
Paths.get("quarkus-run.jar"), output,
Collections.emptyList()).start();
Process process = JarRunnerIT
.doLaunch(new File(testDir, String.format("target/%s%squarkus-app", context.prefix, outputPrefix)),
Paths.get(context.jarFileName), output,
Collections.emptyList())
.start();
try {
Assertions.assertEquals(expectedMessage, DevModeTestUtils.getHttpResponse("/hello"));
} finally {
Expand Down Expand Up @@ -133,4 +142,19 @@ private void ensureManifestOfJarIsReadableByJarInputStream(File jar) throws IOEx
}
}
}

enum TestContext {
FAST_NO_PREFIX("", "quarkus-run.jar"),
FAST("fast-", "quarkus-run.jar"),
LEGACY("legacy-", "legacy-runner.jar"),
UBER("uber-", "uber-runner.jar");

final String prefix;
final String jarFileName;

TestContext(String prefix, String jarFileName) {
this.prefix = prefix;
this.jarFileName = jarFileName;
}
}
}
Loading

0 comments on commit 4795a69

Please sign in to comment.