From 646c0f3cc51295569f197c875d9ef1af903a6198 Mon Sep 17 00:00:00 2001 From: Alexey Loubyansky Date: Fri, 5 Nov 2021 15:17:44 +0100 Subject: [PATCH] Support for project modules that produce multiple JARs (with classifiers) --- .../deployment/ApplicationArchiveImpl.java | 5 + .../io/quarkus/deployment/CodeGenerator.java | 6 +- .../quarkus/deployment/QuarkusAugmentor.java | 9 +- .../builditem/ArchiveRootBuildItem.java | 3 +- .../NativeImageResourcePatternsBuildItem.java | 2 +- .../deployment/dev/CompilationProvider.java | 4 +- .../deployment/dev/DevModeContext.java | 40 +-- .../quarkus/deployment/dev/DevModeMain.java | 6 +- .../deployment/dev/IDEDevModeMain.java | 59 ++-- .../dev/JavaCompilationProvider.java | 10 +- .../deployment/dev/QuarkusCompiler.java | 6 +- .../dev/RuntimeUpdatesProcessor.java | 24 +- .../deployment/dev/testing/TestSupport.java | 6 +- .../index/ApplicationArchiveBuildStep.java | 141 +++++---- .../deployment/mutability/DevModeTask.java | 3 +- .../steps/ApplicationIndexBuildStep.java | 2 +- .../io/quarkus/gradle/tasks/QuarkusDev.java | 16 +- .../gradle/tasks/QuarkusGenerateCode.java | 4 +- .../gradle/tasks/QuarkusGradleUtils.java | 15 - .../GradleApplicationModelBuilder.java | 94 +++--- .../main/java/io/quarkus/maven/DevMojo.java | 98 +++--- .../io/quarkus/maven/GenerateCodeMojo.java | 7 +- .../maven/QuarkusMavenWorkspaceBuilder.java | 20 +- .../quarkus/awt/deployment/AwtProcessor.java | 4 +- .../deployment/KotlinCompilationProvider.java | 4 +- .../deployment/ScalaCompilationProvider.java | 4 +- .../http/deployment/VertxHttpProcessor.java | 46 +-- .../devmode/console/DevConsoleProcessor.java | 144 ++++----- .../bootstrap/app-model/pom.xml | 4 + .../bootstrap/model/AppDependency.java | 2 +- .../io/quarkus/bootstrap/util/PathsUtils.java | 16 - .../bootstrap/workspace/ArtifactSources.java | 56 ++++ .../workspace/DefaultArtifactSources.java | 58 ++++ ...ssedSources.java => DefaultSourceDir.java} | 49 ++- .../workspace/DefaultWorkspaceModule.java | 64 ++-- .../bootstrap/workspace/ProcessedSources.java | 14 - .../bootstrap/workspace/SourceDir.java | 25 ++ .../bootstrap/workspace/WorkspaceModule.java | 31 +- .../quarkus/maven/dependency/Dependency.java | 4 +- .../maven/dependency/ResolvedDependency.java | 34 +++ .../io/quarkus/paths/ArchivePathTree.java | 202 +++++++++++++ .../io/quarkus/paths/DirectoryPathTree.java | 145 +++++++++ .../java/io/quarkus/paths/EmptyPathTree.java | 48 +++ .../java/io/quarkus/paths/FilePathTree.java | 111 +++++++ .../io/quarkus/paths/MultiRootPathTree.java | 92 ++++++ .../java/io/quarkus/paths/OpenPathTree.java | 8 + .../java/io/quarkus/paths/PathCollection.java | 11 + .../java/io/quarkus/paths/PathFilter.java | 150 +++++++++ .../main/java/io/quarkus/paths/PathList.java | 25 +- .../main/java/io/quarkus/paths/PathTree.java | 48 +++ .../io/quarkus/paths/PathTreeBuilder.java | 71 +++++ .../main/java/io/quarkus/paths/PathUtils.java | 26 ++ .../main/java/io/quarkus/paths/PathVisit.java | 21 ++ .../java/io/quarkus/paths/PathVisitBase.java | 42 +++ .../java/io/quarkus/paths/PathVisitor.java | 6 + .../main/java/io/quarkus}/util/GlobUtil.java | 2 +- .../java/io/quarkus}/util/GlobUtilTest.java | 3 +- independent-projects/bootstrap/core/pom.xml | 4 - .../io/quarkus/bootstrap/IDELauncherImpl.java | 30 +- .../bootstrap/app/AdditionalDependency.java | 6 - .../bootstrap/app/CuratedApplication.java | 23 +- .../bootstrap/app/QuarkusBootstrap.java | 19 +- .../AbstractClassPathElement.java | 35 ++- .../PathTreeClassPathElement.java | 286 ++++++++++++++++++ .../bootstrap/devmode/DependenciesFilter.java | 34 ++- .../resolver/BootstrapAppModelResolver.java | 84 ++--- .../DeploymentInjectingDependencyVisitor.java | 54 +--- .../maven/workspace/LocalProject.java | 153 ++++++++-- .../maven/workspace/LocalWorkspace.java | 55 ++-- .../test/LocalWorkspaceDiscoveryTest.java | 16 +- .../builder/QuarkusModelBuilderTest.java | 42 ++- .../java/io/quarkus/maven/it/DevMojoIT.java | 29 ++ .../projects/multijar-module/beans/pom.xml | 67 ++++ .../src/main/java/org/acme/AcmeBean.java | 12 + .../beans/src/main/java/org/acme/Named.java | 6 + .../beans/src/main/java/org/acme/Other.java | 12 + .../src/main/resources/META-INF/beans.xml | 0 .../src/test/java/org/acme/TestBean.java | 12 + .../src/test/resources/META-INF/beans.xml | 0 .../projects/multijar-module/pom.xml | 68 +++++ .../projects/multijar-module/runner/pom.xml | 113 +++++++ .../src/main/java/org/acme/HelloResource.java | 31 ++ .../src/main/resources/application.properties | 3 + .../src/test/java/com/acme/ResourceTest.java | 23 ++ .../jacoco/deployment/JacocoProcessor.java | 24 +- .../io/quarkus/test/QuarkusDevModeTest.java | 16 +- .../AbstractJvmQuarkusTestExtension.java | 29 +- .../test/junit/IntegrationTestUtil.java | 30 +- 88 files changed, 2707 insertions(+), 759 deletions(-) delete mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PathsUtils.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java rename independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/{DefaultProcessedSources.java => DefaultSourceDir.java} (52%) delete mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ProcessedSources.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenPathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeBuilder.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitBase.java create mode 100644 independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitor.java rename {core/deployment/src/main/java/io/quarkus/deployment => independent-projects/bootstrap/app-model/src/main/java/io/quarkus}/util/GlobUtil.java (99%) rename {core/deployment/src/test/java/io/quarkus/deployment => independent-projects/bootstrap/app-model/src/test/java/io/quarkus}/util/GlobUtilTest.java (99%) create mode 100644 independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/pom.xml create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/AcmeBean.java create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Named.java create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Other.java create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/resources/META-INF/beans.xml create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/java/org/acme/TestBean.java create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/resources/META-INF/beans.xml create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/pom.xml create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/runner/pom.xml create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/java/org/acme/HelloResource.java create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/resources/application.properties create mode 100644 integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/test/java/com/acme/ResourceTest.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java index 5ec62ab2fc928..3ede12215b0d8 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java @@ -65,6 +65,11 @@ public PathCollection getResolvedPaths() { } @Override + @Deprecated + /** + * @deprecated in favor of {@link #getKey()} + * @return archive key + */ public AppArtifactKey getArtifactKey() { return artifactKey == null ? null : new AppArtifactKey(artifactKey.getGroupId(), artifactKey.getArtifactId(), artifactKey.getClassifier(), diff --git a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java index 39a650eebf009..5be736243a852 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/CodeGenerator.java @@ -10,9 +10,9 @@ import java.util.function.Consumer; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.prebuild.CodeGenException; import io.quarkus.deployment.codegen.CodeGenData; +import io.quarkus.paths.PathCollection; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ConfigUtils; import io.smallrye.config.PropertiesConfigSource; @@ -25,7 +25,7 @@ public class CodeGenerator { // used by Gradle and Maven public static void initAndRun(ClassLoader classLoader, - PathsCollection sourceParentDirs, Path generatedSourcesDir, Path buildDir, + PathCollection sourceParentDirs, Path generatedSourcesDir, Path buildDir, Consumer sourceRegistrar, ApplicationModel appModel, Properties properties, String launchMode) throws CodeGenException { List generators = init(classLoader, sourceParentDirs, generatedSourcesDir, buildDir, sourceRegistrar); @@ -36,7 +36,7 @@ public static void initAndRun(ClassLoader classLoader, } public static List init(ClassLoader deploymentClassLoader, - PathsCollection sourceParentDirs, + PathCollection sourceParentDirs, Path generatedSourcesDir, Path buildDir, Consumer sourceRegistrar) throws CodeGenException { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java index 5ae5264e33b2b..832d8b3187eaa 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -18,7 +18,6 @@ import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.builder.BuildChain; import io.quarkus.builder.BuildChainBuilder; import io.quarkus.builder.BuildExecutionBuilder; @@ -46,7 +45,7 @@ public class QuarkusAugmentor { private final ClassLoader classLoader; private final ClassLoader deploymentClassLoader; - private final PathsCollection root; + private final PathCollection root; private final Set> finalResults; private final List> buildChainCustomizers; private final LaunchMode launchMode; @@ -193,7 +192,7 @@ public static final class Builder { List additionalApplicationArchives = new ArrayList<>(); Collection excludedFromIndexing = Collections.emptySet(); ClassLoader classLoader; - PathsCollection root; + PathCollection root; Path targetDir; Set> finalResults = new HashSet<>(); private final List> buildChainCustomizers = new ArrayList<>(); @@ -274,7 +273,7 @@ public Builder setClassLoader(ClassLoader classLoader) { return this; } - public PathsCollection getRoot() { + public PathCollection getRoot() { return root; } @@ -283,7 +282,7 @@ public Builder addFinal(Class clazz) { return this; } - public Builder setRoot(PathsCollection root) { + public Builder setRoot(PathCollection root) { this.root = root; return this; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java index 62b3826fc4d4b..e773ef7393854 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ArchiveRootBuildItem.java @@ -13,6 +13,7 @@ import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.fs.util.ZipUtils; +import io.quarkus.paths.PathCollection; public final class ArchiveRootBuildItem extends SimpleBuildItem { @@ -29,7 +30,7 @@ public Builder addArchiveRoot(Path root) { return this; } - public Builder addArchiveRoots(PathsCollection paths) { + public Builder addArchiveRoots(PathCollection paths) { paths.forEach(archiveRoots::add); return this; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourcePatternsBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourcePatternsBuildItem.java index 2b062767950af..d5dbc44c36d01 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourcePatternsBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/nativeimage/NativeImageResourcePatternsBuildItem.java @@ -8,7 +8,7 @@ import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.pkg.NativeConfig; -import io.quarkus.deployment.util.GlobUtil; +import io.quarkus.util.GlobUtil; /** * A build item that indicates that a set of resource paths defined by regular expression patterns or globs should be diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java index fb41ff763b174..7a1e19d3da00a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/CompilationProvider.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Set; -import io.quarkus.bootstrap.model.PathsCollection; +import io.quarkus.paths.PathCollection; public interface CompilationProvider extends Closeable { @@ -23,7 +23,7 @@ default Set handledSourcePaths() { void compile(Set files, Context context); - Path getSourcePath(Path classFilePath, PathsCollection sourcePaths, String classesPath); + Path getSourcePath(Path classFilePath, PathCollection sourcePaths, String classesPath); @Override default void close() throws IOException { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java index b8b04379e8099..92ac32c3ba27e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeContext.java @@ -16,8 +16,9 @@ import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.model.AppArtifactKey; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.maven.dependency.ArtifactKey; +import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathList; /** * Object that is used to pass context data from the plugin doing the invocation @@ -27,7 +28,7 @@ */ public class DevModeContext implements Serializable { - public static final CompilationUnit EMPTY_COMPILATION_UNIT = new CompilationUnit(PathsCollection.of(), null, null, null); + public static final CompilationUnit EMPTY_COMPILATION_UNIT = new CompilationUnit(PathList.of(), null, null, null); public static final String ENABLE_PREVIEW_FLAG = "--enable-preview"; @@ -245,7 +246,7 @@ public static class ModuleInfo implements Serializable { private final CompilationUnit test; private final String preBuildOutputDir; - private final PathsCollection sourceParents; + private final PathCollection sourceParents; private final String targetDir; ModuleInfo(Builder builder) { @@ -255,6 +256,7 @@ public static class ModuleInfo implements Serializable { this.main = new CompilationUnit(builder.sourcePaths, builder.classesPath, builder.resourcePaths, builder.resourcesOutputPath); + if (builder.testClassesPath != null) { this.test = new CompilationUnit(builder.testSourcePaths, builder.testClassesPath, builder.testResourcePaths, builder.testResourcesOutputPath); @@ -274,7 +276,7 @@ public String getProjectDirectory() { return projectDirectory; } - public PathsCollection getSourceParents() { + public PathCollection getSourceParents() { return sourceParents; } @@ -312,18 +314,18 @@ public static class Builder { private ArtifactKey appArtifactKey; private String name; private String projectDirectory; - private PathsCollection sourcePaths = PathsCollection.of(); + private PathCollection sourcePaths = PathList.of(); private String classesPath; - private PathsCollection resourcePaths = PathsCollection.of(); + private PathCollection resourcePaths = PathList.of(); private String resourcesOutputPath; private String preBuildOutputDir; - private PathsCollection sourceParents = PathsCollection.of(); + private PathCollection sourceParents = PathList.of(); private String targetDir; - private PathsCollection testSourcePaths = PathsCollection.of(); + private PathCollection testSourcePaths = PathList.of(); private String testClassesPath; - private PathsCollection testResourcePaths = PathsCollection.of(); + private PathCollection testResourcePaths = PathList.of(); private String testResourcesOutputPath; public Builder setArtifactKey(ArtifactKey appArtifactKey) { @@ -341,7 +343,7 @@ public Builder setProjectDirectory(String projectDirectory) { return this; } - public Builder setSourcePaths(PathsCollection sourcePaths) { + public Builder setSourcePaths(PathCollection sourcePaths) { this.sourcePaths = sourcePaths; return this; } @@ -351,7 +353,7 @@ public Builder setClassesPath(String classesPath) { return this; } - public Builder setResourcePaths(PathsCollection resourcePaths) { + public Builder setResourcePaths(PathCollection resourcePaths) { this.resourcePaths = resourcePaths; return this; } @@ -366,7 +368,7 @@ public Builder setPreBuildOutputDir(String preBuildOutputDir) { return this; } - public Builder setSourceParents(PathsCollection sourceParents) { + public Builder setSourceParents(PathCollection sourceParents) { this.sourceParents = sourceParents; return this; } @@ -376,7 +378,7 @@ public Builder setTargetDir(String targetDir) { return this; } - public Builder setTestSourcePaths(PathsCollection testSourcePaths) { + public Builder setTestSourcePaths(PathCollection testSourcePaths) { this.testSourcePaths = testSourcePaths; return this; } @@ -386,7 +388,7 @@ public Builder setTestClassesPath(String testClassesPath) { return this; } - public Builder setTestResourcePaths(PathsCollection testResourcePaths) { + public Builder setTestResourcePaths(PathCollection testResourcePaths) { this.testResourcePaths = testResourcePaths; return this; } @@ -403,12 +405,12 @@ public ModuleInfo build() { } public static class CompilationUnit implements Serializable { - private PathsCollection sourcePaths; + private PathCollection sourcePaths; private final String classesPath; - private final PathsCollection resourcePaths; + private final PathCollection resourcePaths; private final String resourcesOutputPath; - public CompilationUnit(PathsCollection sourcePaths, String classesPath, PathsCollection resourcePaths, + public CompilationUnit(PathCollection sourcePaths, String classesPath, PathCollection resourcePaths, String resourcesOutputPath) { this.sourcePaths = sourcePaths; this.classesPath = classesPath; @@ -416,7 +418,7 @@ public CompilationUnit(PathsCollection sourcePaths, String classesPath, PathsCol this.resourcesOutputPath = resourcesOutputPath; } - public PathsCollection getSourcePaths() { + public PathCollection getSourcePaths() { return sourcePaths; } @@ -424,7 +426,7 @@ public String getClassesPath() { return classesPath; } - public PathsCollection getResourcePaths() { + public PathCollection getResourcePaths() { return resourcePaths; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java index a84e17c04f6b5..dddb7ef1ee4df 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/DevModeMain.java @@ -26,10 +26,10 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.model.AppArtifactKey; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.deployment.util.ProcessUtil; import io.quarkus.dev.appstate.ApplicationStateNotification; import io.quarkus.dev.spi.DevModeType; +import io.quarkus.paths.PathList; /** * The main entry point for the dev mojo execution @@ -87,8 +87,8 @@ public void start() throws Exception { } } } - final PathsCollection.Builder appRoots = PathsCollection.builder(); - Path p = Paths.get(context.getApplicationRoot().getMain().getClassesPath()); + final PathList.Builder appRoots = PathList.builder(); + Path p = Path.of(context.getApplicationRoot().getMain().getClassesPath()); if (Files.exists(p)) { appRoots.add(p); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java index f5ad2cc7224de..e596116738d03 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/IDEDevModeMain.java @@ -16,16 +16,15 @@ import io.quarkus.bootstrap.devmode.DependenciesFilter; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.bootstrap.utils.BuildToolHelper; -import io.quarkus.bootstrap.workspace.ProcessedSources; -import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.bootstrap.workspace.ArtifactSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.deployment.dev.DevModeContext.ModuleInfo; import io.quarkus.dev.spi.DevModeType; -import io.quarkus.maven.dependency.ArtifactKey; -import io.quarkus.maven.dependency.GACT; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathList; public class IDEDevModeMain implements BiConsumer>, Closeable { @@ -50,14 +49,15 @@ public void accept(CuratedApplication curatedApplication, Map st } if (appModel != null) { - for (WorkspaceModule project : DependenciesFilter.getReloadableModules(appModel)) { + for (ResolvedDependency project : DependenciesFilter.getReloadableModules(appModel)) { final ModuleInfo module = toModule(project); - if (project == appModel.getApplicationModule()) { + if (project.getKey().equals(appModel.getAppArtifact().getKey()) + && project.getVersion().equals(appModel.getAppArtifact().getVersion())) { devModeContext.setApplicationRoot(module); } else { devModeContext.getAdditionalModules().add(module); devModeContext.getLocalArtifacts() - .add(new AppArtifactKey(project.getId().getGroupId(), project.getId().getArtifactId())); + .add(new AppArtifactKey(project.getGroupId(), project.getArtifactId())); } } } @@ -85,40 +85,49 @@ private void terminateIfRunning() { } } - private DevModeContext.ModuleInfo toModule(WorkspaceModule module) throws BootstrapGradleException { + private DevModeContext.ModuleInfo toModule(ResolvedDependency module) throws BootstrapGradleException { String classesDir = null; final Set sourceParents = new LinkedHashSet<>(); - final PathsCollection.Builder srcPaths = PathsCollection.builder(); - for (ProcessedSources src : module.getMainSources()) { - sourceParents.add(src.getSourceDir().getParentFile().toPath()); - srcPaths.add(src.getSourceDir().toPath()); + final PathList.Builder srcPaths = PathList.builder(); + final ArtifactSources sources = module.getSources(); + for (SourceDir src : sources.getSourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + sourceParents.add(p.getParent()); + if (!srcPaths.contains(p)) { + srcPaths.add(p); + } + } if (classesDir == null) { - classesDir = src.getDestinationDir().toString(); + classesDir = src.getOutputDir().toString(); } } String resourceDirectory = null; - final PathsCollection.Builder resourcesPaths = PathsCollection.builder(); - for (ProcessedSources src : module.getMainResources()) { - resourcesPaths.add(src.getSourceDir().toPath()); + final PathList.Builder resourcesPaths = PathList.builder(); + for (SourceDir src : sources.getResourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + if (!resourcesPaths.contains(p)) { + resourcesPaths.add(p); + } + } if (resourceDirectory == null) { // Peek the first one as we assume that it is the primary - resourceDirectory = src.getDestinationDir().toString(); + resourceDirectory = src.getOutputDir().toString(); } } - final ArtifactKey key = new GACT(module.getId().getGroupId(), module.getId().getArtifactId()); return new DevModeContext.ModuleInfo.Builder() - .setArtifactKey(key) - .setName(module.getId().getArtifactId()) - .setProjectDirectory(module.getModuleDir().getPath()) + .setArtifactKey(module.getKey()) + .setName(module.getArtifactId()) + .setProjectDirectory(module.getWorkspaceModule().getModuleDir().getPath()) .setSourcePaths(srcPaths.build()) .setClassesPath(classesDir) .setResourcePaths(resourcesPaths.build()) .setResourcesOutputPath(resourceDirectory) - .setSourceParents(PathsCollection.from(sourceParents)) - .setPreBuildOutputDir(module.getBuildDir().toPath().resolve("generated-sources").toAbsolutePath().toString()) - .setTargetDir(module.getBuildDir().toString()).build(); + .setSourceParents(PathList.from(sourceParents)) + .setPreBuildOutputDir(module.getWorkspaceModule().getBuildDir().toPath().resolve("generated-sources") + .toAbsolutePath().toString()) + .setTargetDir(module.getWorkspaceModule().getBuildDir().toString()).build(); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java index e2faf52ad8a0c..6957c6df0fbc0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/JavaCompilationProvider.java @@ -23,8 +23,8 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.gizmo.Gizmo; +import io.quarkus.paths.PathCollection; public class JavaCompilationProvider implements CompilationProvider { @@ -95,7 +95,7 @@ public void compile(Set filesToCompile, Context context) { } @Override - public Path getSourcePath(Path classFilePath, PathsCollection sourcePaths, String classesPath) { + public Path getSourcePath(Path classFilePath, PathCollection sourcePaths, String classesPath) { Path sourceFilePath = null; final RuntimeUpdatesClassVisitor visitor = new RuntimeUpdatesClassVisitor(sourcePaths, classesPath); try (final InputStream inputStream = Files.newInputStream(classFilePath)) { @@ -140,11 +140,11 @@ private static boolean ignoreWarningForNamespace(String message) { } static class RuntimeUpdatesClassVisitor extends ClassVisitor { - private final PathsCollection sourcePaths; + private final PathCollection sourcePaths; private final String classesPath; private String sourceFile; - public RuntimeUpdatesClassVisitor(PathsCollection sourcePaths, String classesPath) { + public RuntimeUpdatesClassVisitor(PathCollection sourcePaths, String classesPath) { super(Gizmo.ASM_API_VERSION); this.sourcePaths = sourcePaths; this.classesPath = classesPath; @@ -162,7 +162,7 @@ public Path getSourceFileForClass(final Path classFilePath) { sourceRelativeDir.append(classesDir.relativize(classFilePath.getParent())); sourceRelativeDir.append(File.separator); sourceRelativeDir.append(sourceFile); - final Path sourceFilePath = sourcesDir.resolve(Paths.get(sourceRelativeDir.toString())); + final Path sourceFilePath = sourcesDir.resolve(Path.of(sourceRelativeDir.toString())); if (Files.exists(sourceFilePath)) { return sourceFilePath; } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusCompiler.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusCompiler.java index 2ab1114dd20b9..ffb48c7be3837 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusCompiler.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/QuarkusCompiler.java @@ -23,8 +23,8 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathCollection; /** * Class that handles compilation of source files @@ -62,7 +62,7 @@ public QuarkusCompiler(CuratedApplication application, } Set paths = new HashSet<>(); for (ResolvedDependency i : application.getApplicationModel().getRuntimeDependencies()) { - for (Path p : i.getResolvedPaths()) { + for (Path p : i.getContentTree().getRoots()) { paths.add(p); } } @@ -191,7 +191,7 @@ public void compile(String sourceDir, Map> extensionToChangedF } } - public Path findSourcePath(Path classFilePath, PathsCollection sourcePaths, String classesPath) { + public Path findSourcePath(Path classFilePath, PathCollection sourcePaths, String classesPath) { for (CompilationProvider compilationProvider : compilationProviders) { Path sourcePath = compilationProvider.getSourcePath(classFilePath, sourcePaths, classesPath); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java index fe6faebe7ab43..aad0018167243 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java @@ -3,7 +3,6 @@ import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; import java.io.ByteArrayInputStream; import java.io.Closeable; @@ -54,9 +53,9 @@ import org.jboss.jandex.Indexer; import org.jboss.logging.Logger; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.runner.Timing; import io.quarkus.changeagent.ClassChangeAgent; +import io.quarkus.deployment.dev.DevModeContext.ModuleInfo; import io.quarkus.deployment.dev.filewatch.FileChangeCallback; import io.quarkus.deployment.dev.filewatch.FileChangeEvent; import io.quarkus.deployment.dev.filewatch.WatchServiceFileSystemWatcher; @@ -69,6 +68,8 @@ import io.quarkus.dev.spi.HotReplacementContext; import io.quarkus.dev.spi.HotReplacementSetup; import io.quarkus.dev.testing.TestScanningLock; +import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathList; public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable { public static final boolean IS_LINUX = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("linux"); @@ -189,8 +190,13 @@ public Path getClassesDir() { @Override public List getSourcesDir() { - return context.getAllModules().stream().flatMap(m -> m.getMain().getSourcePaths().toList().stream()) - .collect(toList()); + final List paths = new ArrayList<>(); + for (ModuleInfo m : context.getAllModules()) { + for (Path p : m.getMain().getSourcePaths()) { + paths.add(p); + } + } + return paths; } private void startTestScanningTimer() { @@ -841,12 +847,12 @@ Set checkForFileChange(Function moduleResources = correspondingResources.computeIfAbsent(compilationUnit, m -> Collections.newSetFromMap(new ConcurrentHashMap<>())); boolean doCopy = true; - PathsCollection rootPaths = compilationUnit.getResourcePaths(); + PathCollection rootPaths = compilationUnit.getResourcePaths(); String outputPath = compilationUnit.getResourcesOutputPath(); if (rootPaths.isEmpty()) { String rootPath = compilationUnit.getClassesPath(); if (rootPath != null) { - rootPaths = PathsCollection.of(Paths.get(rootPath)); + rootPaths = PathList.of(Paths.get(rootPath)); } outputPath = rootPath; doCopy = false; @@ -854,7 +860,7 @@ Set checkForFileChange(Function roots = rootPaths.toList().stream() + final List roots = rootPaths.stream() .filter(Files::exists) .filter(Files::isReadable) .collect(Collectors.toList()); @@ -1077,13 +1083,13 @@ private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map for (DevModeContext.ModuleInfo module : context.getAllModules()) { List compilationUnits = cuf.apply(module); for (DevModeContext.CompilationUnit unit : compilationUnits) { - PathsCollection rootPaths = unit.getResourcePaths(); + PathCollection rootPaths = unit.getResourcePaths(); if (rootPaths.isEmpty()) { String rootPath = unit.getClassesPath(); if (rootPath == null) { continue; } - rootPaths = PathsCollection.of(Paths.get(rootPath)); + rootPaths = PathList.of(Path.of(rootPath)); } for (Path root : rootPaths) { for (String path : watchedFilePaths.keySet()) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java index 34a38cf9ad02e..8bbcbf5f8112c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java @@ -27,7 +27,6 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.deployment.dev.ClassScanResult; import io.quarkus.deployment.dev.CompilationProvider; import io.quarkus.deployment.dev.DevModeContext; @@ -35,6 +34,7 @@ import io.quarkus.deployment.dev.RuntimeUpdatesProcessor; import io.quarkus.dev.spi.DevModeType; import io.quarkus.dev.testing.TestWatchedFiles; +import io.quarkus.paths.PathList; import io.quarkus.runtime.configuration.HyphenateEnumConverter; public class TestSupport implements TestController { @@ -169,7 +169,7 @@ public void init() { } }); if (mainModule) { - paths.addAll(curatedApplication.getQuarkusBootstrap().getApplicationRoot().toList()); + curatedApplication.getQuarkusBootstrap().getApplicationRoot().forEach(paths::add); } else { paths.add(Paths.get(module.getMain().getClassesPath())); } @@ -189,7 +189,7 @@ public void init() { .setAuxiliaryApplication(true) .setHostApplicationIsTestOnly(devModeType == DevModeType.TEST_ONLY) .setProjectRoot(Paths.get(module.getProjectDirectory())) - .setApplicationRoot(PathsCollection.from(paths)) + .setApplicationRoot(PathList.from(paths)) .build() .bootstrap(); if (mainModule) { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java index 9c34d2b6c0f3d..e5272723bfba6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/ApplicationArchiveBuildStep.java @@ -2,19 +2,19 @@ import java.io.FileInputStream; import java.io.IOException; -import java.net.URI; -import java.net.URL; +import java.io.InputStream; +import java.io.UncheckedIOException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Stream; @@ -44,8 +44,11 @@ import io.quarkus.maven.dependency.GACT; import io.quarkus.maven.dependency.GACTV; import io.quarkus.maven.dependency.ResolvedDependency; -import io.quarkus.paths.PathCollection; +import io.quarkus.paths.OpenPathTree; import io.quarkus.paths.PathList; +import io.quarkus.paths.PathTree; +import io.quarkus.paths.PathVisit; +import io.quarkus.paths.PathVisitor; import io.quarkus.runtime.annotations.ConfigDocMapKey; import io.quarkus.runtime.annotations.ConfigDocSection; import io.quarkus.runtime.annotations.ConfigItem; @@ -172,7 +175,7 @@ private void addIndexDependencyPaths(List indexDepende throw new RuntimeException( "Could not resolve artifact " + key + " among the runtime dependencies of the application"); } - for (Path path : artifact.getResolvedPaths()) { + for (Path path : artifact.getContentTree().getRoots()) { if (!root.isExcludedFromIndexing(path) && !root.getPaths().contains(path) && indexedDeps.add(path)) { appArchives.add(createApplicationArchive(buildCloseables, indexCache, path, key, removedResources)); @@ -209,64 +212,96 @@ private static void addMarkerFilePaths(Set applicationArchiveMarkers, IndexCache indexCache, Map> removed) throws IOException { - Set markedUris = new HashSet<>(); - for (String marker : applicationArchiveMarkers) { - Enumeration resources = Thread.currentThread().getContextClassLoader().getResources(marker); - - while (resources.hasMoreElements()) { - URL resource = resources.nextElement(); - String s = resource.toString(); - if ("jar".equals(resource.getProtocol())) { - // Path is in the format jar:file:/path/to/jarfile.jar!/META-INF/jandex.idx. Only the path to jar - // is needed, for comparisons and opening fs. Remove the protocol an path inside jar here. - // Results in file:/path/to/jarfile.jar - s = s.substring(4, s.lastIndexOf("!")); - } else if ("file".equals(resource.getProtocol())) { - s = s.substring(0, s.length() - marker.length()); - } - markedUris.add(URI.create(s)); + for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) { + if (!dep.isRuntimeCp() || !ArtifactCoords.TYPE_JAR.equals(dep.getType())) { + continue; } - } - - List applicationArchives = new ArrayList<>(); - for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getRuntimeDependencies()) { - if (!ArtifactCoords.TYPE_JAR.equals(dep.getType())) { + final PathTree contentTree = dep.getContentTree(); + if (contentTree.isEmpty()) { continue; } - - final PathCollection artifactPaths = dep.getResolvedPaths(); - for (Path p : artifactPaths) { - if (root.isExcludedFromIndexing(p)) { - continue; + OpenPathTree openTree = null; + AtomicBoolean markerFound = new AtomicBoolean(false); + try { + final OpenPathTree tree = openTree = contentTree.openTree(); + for (String marker : applicationArchiveMarkers) { + tree.visitIfExists(marker, new PathVisitor() { + @Override + public void visitPath(PathVisit visit) { + if (root.isExcludedFromIndexing(visit.getRoot())) { + return; + } + markerFound.set(true); + + final PathList.Builder rootDirs = PathList.builder(); + final Collection artifactPaths = tree.getRoots(); + final List indexes = new ArrayList<>(artifactPaths.size()); + for (Path p : artifactPaths) { + if (!indexedPaths.add(p)) { + continue; + } + + boolean isDirectory = Files.isDirectory(p); + if (isDirectory) { + rootDirs.add(p); + } else { + FileSystem fs; + try { + fs = ZipUtils.newFileSystem(p); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + buildCloseables.add(fs); + fs.getRootDirectories().forEach(rootDirs::add); + } + } + try { + indexes.add(indexPathTree(tree, removed.get(dep.getKey()))); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + buildCloseables.add(tree); + appArchives + .add(new ApplicationArchiveImpl( + indexes.size() == 1 ? indexes.get(0) : CompositeIndex.create(indexes), + rootDirs.build(), PathList.from(artifactPaths), dep.getKey())); + + } + }); + if (markerFound.get()) { + break; + } } - - if (markedUris.contains(p.toUri())) { - applicationArchives.add(dep); - break; + } finally { + if (openTree != null && openTree != contentTree && !markerFound.get()) { + openTree.close(); } } } + } - for (ResolvedDependency dep : applicationArchives) { - final PathList.Builder rootDirs = PathList.builder(); - final PathCollection artifactPaths = dep.getResolvedPaths(); - final List indexes = new ArrayList<>(artifactPaths.size()); - for (Path p : artifactPaths) { - boolean isDirectory = Files.isDirectory(p); - if (isDirectory) { - rootDirs.add(p); - } else { - FileSystem fs = ZipUtils.newFileSystem(p); - buildCloseables.add(fs); - fs.getRootDirectories().forEach(rootDirs::add); + private static Index indexPathTree(OpenPathTree tree, Set removed) throws IOException { + Indexer indexer = new Indexer(); + tree.visit(new PathVisitor() { + @Override + public void visitPath(PathVisit visit) { + final Path path = visit.getPath(); + final Path fileName = path.getFileName(); + if (fileName == null + || !fileName.toString().endsWith(".class") + || Files.isDirectory(path) + || removed != null && removed.contains(visit.getRelativePath("/"))) { + return; + } + try (InputStream in = Files.newInputStream(path)) { + indexer.index(in); + } catch (IOException e) { + throw new RuntimeException(e); } - indexes.add(indexPath(indexCache, p, removed.get(dep.getKey()), isDirectory)); - indexedPaths.add(p); } - appArchives - .add(new ApplicationArchiveImpl(indexes.size() == 1 ? indexes.get(0) : CompositeIndex.create(indexes), - rootDirs.build(), artifactPaths, dep.getKey())); - } + }); + return indexer.complete(); } private static Index handleFilePath(Path path, Set removed) throws IOException { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java b/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java index 15df00956e5b3..bf825ef04fd0b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java @@ -81,7 +81,8 @@ private static DevModeContext createDevModeContext(Path appRoot, ApplicationMode public void run(ResolvedDependency dep, Path moduleClasses, boolean appArtifact) { ((ResolvedArtifactDependency) dep).setResolvedPaths(PathList.of(moduleClasses)); - DevModeContext.ModuleInfo module = new DevModeContext.ModuleInfo.Builder().setArtifactKey(dep.getKey()) + DevModeContext.ModuleInfo module = new DevModeContext.ModuleInfo.Builder() + .setArtifactKey(dep.getKey()) .setName(dep.getArtifactId()) .setClassesPath(moduleClasses.toAbsolutePath().toString()) .setResourcesOutputPath(moduleClasses.toAbsolutePath().toString()) diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationIndexBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationIndexBuildStep.java index 3d7f9dc91faad..b400a25f0a2c0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationIndexBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationIndexBuildStep.java @@ -32,7 +32,7 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (file.toString().endsWith(".class")) { + if (file.getFileName().toString().endsWith(".class")) { log.debugf("Indexing %s", file); try (InputStream stream = Files.newInputStream(file)) { indexer.index(stream); diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 88ebef62663c2..cb7c22a029fb7 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -40,11 +40,11 @@ import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.deployment.dev.DevModeContext; import io.quarkus.deployment.dev.QuarkusDevModeLauncher; import io.quarkus.gradle.tooling.ToolingUtils; import io.quarkus.maven.dependency.GACT; +import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; public class QuarkusDev extends QuarkusTask { @@ -342,7 +342,7 @@ private void addSelfWithLocalDeps(Project project, GradleDevModeLauncher.Builder } private void addLocalProject(Project project, GradleDevModeLauncher.Builder builder, Set addeDeps, boolean root) { - final GACT key = new GACT(project.getGroup().toString(), project.getName()); + final GACT key = new GACT(project.getGroup().toString(), project.getName(), "", "jar"); if (addeDeps.contains(key)) { return; } @@ -399,14 +399,14 @@ private void addLocalProject(Project project, GradleDevModeLauncher.Builder buil } DevModeContext.ModuleInfo.Builder moduleBuilder = new DevModeContext.ModuleInfo.Builder() - .setArtifactKey(new GACT(key.getGroupId(), key.getArtifactId())) + .setArtifactKey(key) .setName(project.getName()) .setProjectDirectory(project.getProjectDir().getAbsolutePath()) - .setSourcePaths(PathsCollection.from(sourcePaths)) + .setSourcePaths(PathList.from(sourcePaths)) .setClassesPath(classesDir) - .setResourcePaths(PathsCollection.from(resourcesSrcDirs)) + .setResourcePaths(PathList.from(resourcesSrcDirs)) .setResourcesOutputPath(resourcesOutputPath) - .setSourceParents(PathsCollection.from(sourceParentPaths)) + .setSourceParents(PathList.from(sourceParentPaths)) .setPreBuildOutputDir(project.getBuildDir().toPath().resolve("generated-sources").toAbsolutePath().toString()) .setTargetDir(project.getBuildDir().toString()); @@ -445,9 +445,9 @@ private void addLocalProject(Project project, GradleDevModeLauncher.Builder buil // currently resources dir should exist testResourcesOutputPath = testClassesDir; } - moduleBuilder.setTestSourcePaths(PathsCollection.from(testSourcePaths)) + moduleBuilder.setTestSourcePaths(PathList.from(testSourcePaths)) .setTestClassesPath(testClassesDir) - .setTestResourcePaths(PathsCollection.from(testResourcesSrcDirs)) + .setTestResourcePaths(PathList.from(testResourcesSrcDirs)) .setTestResourcesOutputPath(testResourcesOutputPath); } } diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java index cbe570046ec45..3057fc80e8317 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGenerateCode.java @@ -28,8 +28,8 @@ import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.deployment.CodeGenerator; +import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; public class QuarkusGenerateCode extends QuarkusTask { @@ -131,7 +131,7 @@ public void prepareQuarkus() { } initAndRun.get().invoke(null, deploymentClassLoader, - PathsCollection.from(sourcesDirectories), + PathList.from(sourcesDirectories), paths.get(0), buildDir, sourceRegistrar, diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java index 43f39890e148f..65af2220ef3cc 100644 --- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java +++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusGradleUtils.java @@ -13,7 +13,6 @@ import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.util.IoUtils; public class QuarkusGradleUtils { @@ -28,20 +27,6 @@ public static SourceSet getSourceSet(Project project, String sourceSetName) { return javaConvention.getSourceSets().getByName(sourceSetName); } - public static PathsCollection getOutputPaths(Project project) { - final SourceSet mainSourceSet = getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME); - final PathsCollection.Builder builder = PathsCollection.builder(); - mainSourceSet.getOutput().getClassesDirs().filter(f -> f.exists()).forEach(f -> builder.add(f.toPath())); - final File resourcesDir = mainSourceSet.getOutput().getResourcesDir(); - if (resourcesDir != null && resourcesDir.exists()) { - final Path p = resourcesDir.toPath(); - if (!builder.contains(p)) { - builder.add(p); - } - } - return builder.build(); - } - public static String getClassesDir(SourceSet sourceSet, File tmpDir, boolean test) { return getClassesDir(sourceSet, tmpDir, true, test); } diff --git a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java index 1b9377c625695..e62cb452a8c55 100644 --- a/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java +++ b/devtools/gradle/gradle-model/src/main/java/io/quarkus/gradle/tooling/GradleApplicationModelBuilder.java @@ -44,9 +44,10 @@ import io.quarkus.bootstrap.model.gradle.ModelParameter; import io.quarkus.bootstrap.model.gradle.impl.ModelParameterImpl; import io.quarkus.bootstrap.resolver.AppModelResolverException; -import io.quarkus.bootstrap.workspace.DefaultProcessedSources; +import io.quarkus.bootstrap.workspace.DefaultArtifactSources; +import io.quarkus.bootstrap.workspace.DefaultSourceDir; import io.quarkus.bootstrap.workspace.DefaultWorkspaceModule; -import io.quarkus.bootstrap.workspace.ProcessedSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; @@ -177,33 +178,33 @@ public static ResolvedDependency getProjectArtifact(Project project, LaunchMode final DefaultWorkspaceModule mainModule = new DefaultWorkspaceModule( new GAV(appArtifact.getGroupId(), appArtifact.getArtifactId(), appArtifact.getVersion()), project.getProjectDir(), project.getBuildDir()); + mainModule.setBuildFiles(PathList.of(project.getBuildFile().toPath())); + initProjectModule(project, mainModule, javaConvention.getSourceSets().findByName(SourceSet.MAIN_SOURCE_SET_NAME), - false); + SourceSet.MAIN_SOURCE_SET_NAME, ""); if (mode.equals(LaunchMode.TEST)) { initProjectModule(project, mainModule, javaConvention.getSourceSets().findByName(SourceSet.TEST_SOURCE_SET_NAME), - true); + SourceSet.TEST_SOURCE_SET_NAME, "tests"); } final PathList.Builder paths = PathList.builder(); - mainModule.getMainSources().forEach(src -> { - collectDestinationDirs(src, paths); - }); - mainModule.getMainResources().forEach(src -> { - collectDestinationDirs(src, paths); - }); + collectDestinationDirs(mainModule.getMainSources().getSourceDirs(), paths); + collectDestinationDirs(mainModule.getMainSources().getResourceDirs(), paths); return appArtifact.setWorkspaceModule(mainModule).setResolvedPaths(paths.build()).build(); } - private static void collectDestinationDirs(ProcessedSources src, final PathList.Builder paths) { - if (!src.getDestinationDir().exists()) { - return; - } - final Path path = src.getDestinationDir().toPath(); - if (paths.contains(path)) { - return; + private static void collectDestinationDirs(Collection sources, final PathList.Builder paths) { + for (SourceDir src : sources) { + if (!Files.exists(src.getOutputDir())) { + return; + } + final Path path = src.getOutputDir(); + if (paths.contains(path)) { + return; + } + paths.add(path); } - paths.add(path); } private static void processQuarkusDir(ResolvedDependencyBuilder artifactBuilder, Path quarkusDir, @@ -431,6 +432,10 @@ private void collectDependencies(org.gradle.api.artifacts.ResolvedDependency res } } + private static String toNonNullClassifier(String resolvedClassifier) { + return resolvedClassifier == null ? "" : resolvedClassifier; + } + private DefaultWorkspaceModule initProjectModuleAndBuildPaths(final Project project, ResolvedArtifact resolvedArtifact, ApplicationModelBuilder appModel, final ResolvedDependencyBuilder appDep, final JavaPluginConvention javaExt, PathList.Builder buildPaths, String sourceName, boolean test) { @@ -442,24 +447,13 @@ private DefaultWorkspaceModule initProjectModuleAndBuildPaths(final Project proj resolvedArtifact.getModuleVersion().getId().getVersion()), project.getProjectDir(), project.getBuildDir()); + projectModule.setBuildFiles(PathList.of(project.getBuildFile().toPath())); - initProjectModule(project, projectModule, javaExt.getSourceSets().findByName(sourceName), test); + final String classifier = toNonNullClassifier(resolvedArtifact.getClassifier()); + initProjectModule(project, projectModule, javaExt.getSourceSets().findByName(sourceName), sourceName, classifier); - if (test) { - projectModule.getTestSources().forEach(src -> { - collectDestinationDirs(src, buildPaths); - }); - projectModule.getTestResources().forEach(src -> { - collectDestinationDirs(src, buildPaths); - }); - } else { - projectModule.getMainSources().forEach(src -> { - collectDestinationDirs(src, buildPaths); - }); - projectModule.getMainResources().forEach(src -> { - collectDestinationDirs(src, buildPaths); - }); - } + collectDestinationDirs(projectModule.getSources(classifier).getSourceDirs(), buildPaths); + collectDestinationDirs(projectModule.getSources(classifier).getResourceDirs(), buildPaths); appModel.addReloadableWorkspaceModule( new GACT(resolvedArtifact.getModuleVersion().getId().getGroup(), resolvedArtifact.getName())); @@ -483,18 +477,18 @@ private void processQuarkusDependency(ResolvedDependencyBuilder artifactBuilder, }); } - private static void initProjectModule(Project project, DefaultWorkspaceModule module, SourceSet sourceSet, boolean test) { + private static void initProjectModule(Project project, DefaultWorkspaceModule module, SourceSet sourceSet, + String sourceName, String classifier) { if (sourceSet == null) { return; } - module.setBuildFiles(PathList.of(project.getBuildFile().toPath())); - final FileCollection allClassesDirs = sourceSet.getOutput().getClassesDirs(); // some plugins do not add source directories to source sets and they may be missing from sourceSet.getAllJava() // see https://github.com/quarkusio/quarkus/issues/20755 + final DefaultArtifactSources artifactSources = new DefaultArtifactSources(classifier); project.getTasks().withType(AbstractCompile.class, t -> { if (!t.getEnabled()) { return; @@ -513,13 +507,9 @@ private static void initProjectModule(Project project, DefaultWorkspaceModule mo if (a.getRelativePath().getSegments().length == 1) { final File srcDir = a.getFile().getParentFile(); if (srcDirs.add(srcDir)) { - DefaultProcessedSources sources = new DefaultProcessedSources(srcDir, destDir, + DefaultSourceDir sources = new DefaultSourceDir(srcDir, destDir, Collections.singletonMap("compiler", t.getName())); - if (test) { - module.addTestSources(sources); - } else { - module.addMainSources(sources); - } + artifactSources.addSources(sources); } } }); @@ -545,24 +535,20 @@ private static void initProjectModule(Project project, DefaultWorkspaceModule mo if (a.getRelativePath().getSegments().length == 1) { final File srcDir = a.getFile().getParentFile(); if (srcDirs.add(srcDir)) { - addResources(module, srcDir, destDir, test); + addResources(artifactSources, srcDir, destDir); } } }); }); // there could be a task generating resources - if (resourcesOutputDir.exists() && (test ? module.getTestResources().isEmpty() : module.getMainResources().isEmpty())) { - sourceSet.getResources().getSrcDirs().forEach(srcDir -> addResources(module, srcDir, resourcesOutputDir, test)); + if (resourcesOutputDir.exists() && artifactSources.getResourceDirs().isEmpty()) { + sourceSet.getResources().getSrcDirs().forEach(srcDir -> addResources(artifactSources, srcDir, resourcesOutputDir)); } + module.addArtifactSources(artifactSources); } - private static void addResources(DefaultWorkspaceModule module, File srcDir, final File destDir, boolean test) { - final DefaultProcessedSources resrc = new DefaultProcessedSources(srcDir, destDir); - if (test) { - module.addTestResources(resrc); - } else { - module.addMainResources(resrc); - } + private static void addResources(DefaultArtifactSources module, File srcDir, final File destDir) { + module.addResources(new DefaultSourceDir(srcDir, destDir)); } private void addSubstitutedProject(PathList.Builder paths, File projectFile) { @@ -635,8 +621,6 @@ private static ArtifactCoords toArtifactCoords(ResolvedArtifact a) { } private static ArtifactKey toAppDependenciesKey(String groupId, String artifactId, String classifier) { - // Default classifier is empty string and not null value, lets keep it that way - classifier = classifier == null ? "" : classifier; return new GACT(groupId, artifactId, classifier, ArtifactCoords.TYPE_JAR); } } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java index 27da90bd61bc4..24464cdc17408 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -84,19 +84,20 @@ import io.quarkus.bootstrap.devmode.DependenciesFilter; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.maven.options.BootstrapMavenOptions; import io.quarkus.bootstrap.util.BootstrapUtils; -import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.bootstrap.workspace.ArtifactSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.deployment.dev.DevModeContext; import io.quarkus.deployment.dev.DevModeMain; import io.quarkus.deployment.dev.QuarkusDevModeLauncher; import io.quarkus.maven.MavenDevModeLauncher.Builder; import io.quarkus.maven.components.MavenVersionEnforcer; -import io.quarkus.maven.dependency.GACT; import io.quarkus.maven.dependency.GACTV; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; /** @@ -676,7 +677,7 @@ private String getSourceEncoding() { return null; } - private void addProject(MavenDevModeLauncher.Builder builder, WorkspaceModule module, boolean root) throws Exception { + private void addProject(MavenDevModeLauncher.Builder builder, ResolvedDependency module, boolean root) throws Exception { String projectDirectory; Set sourcePaths; @@ -687,15 +688,28 @@ private void addProject(MavenDevModeLauncher.Builder builder, WorkspaceModule mo Set testResourcePaths; List activeProfiles = Collections.emptyList(); - final MavenProject mavenProject = session.getProjectMap().get( - String.format("%s:%s:%s", module.getId().getGroupId(), module.getId().getArtifactId(), - module.getId().getVersion())); + final MavenProject mavenProject = module.getClassifier().isEmpty() + ? session.getProjectMap() + .get(String.format("%s:%s:%s", module.getGroupId(), module.getArtifactId(), module.getVersion())) + : null; + final ArtifactSources sources = module.getSources(); if (mavenProject == null) { - projectDirectory = module.getModuleDir().getAbsolutePath(); - sourcePaths = module.getMainSources().stream().map(src -> src.getSourceDir().toPath().toAbsolutePath()) - .collect(Collectors.toCollection(LinkedHashSet::new)); - testSourcePaths = module.getTestSources().stream().map(src -> src.getSourceDir().toPath().toAbsolutePath()) - .collect(Collectors.toCollection(LinkedHashSet::new)); + projectDirectory = module.getWorkspaceModule().getModuleDir().getAbsolutePath(); + sourcePaths = new LinkedHashSet<>(); + for (SourceDir src : sources.getSourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + sourcePaths.add(p.toAbsolutePath()); + } + } + testSourcePaths = new LinkedHashSet<>(); + ArtifactSources testSources = module.getWorkspaceModule().getTestSources(); + if (testSources != null) { + for (SourceDir src : testSources.getSourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + testSourcePaths.add(p.toAbsolutePath()); + } + } + } } else { projectDirectory = mavenProject.getBasedir().getPath(); sourcePaths = mavenProject.getCompileSourceRoots().stream() @@ -710,26 +724,40 @@ private void addProject(MavenDevModeLauncher.Builder builder, WorkspaceModule mo } final Path sourceParent; - if (module.getMainSources().isEmpty()) { - if (module.getMainResources().isEmpty()) { + if (sources.getSourceDirs() == null) { + if (sources.getResourceDirs() == null) { throw new MojoExecutionException("The project does not appear to contain any sources or resources"); } - sourceParent = module.getMainResources().iterator().next().getSourceDir().toPath().toAbsolutePath().getParent(); + sourceParent = sources.getResourceDirs().iterator().next().getDir().toAbsolutePath().getParent(); } else { - sourceParent = module.getMainSources().iterator().next().getSourceDir().toPath().toAbsolutePath().getParent(); + sourceParent = sources.getSourceDirs().iterator().next().getDir().toAbsolutePath().getParent(); } - Path classesDir = module.getMainSources().iterator().next().getDestinationDir().toPath().toAbsolutePath(); + Path classesDir = sources.getSourceDirs().iterator().next().getOutputDir().toAbsolutePath(); if (Files.isDirectory(classesDir)) { classesPath = classesDir.toString(); } - Path testClassesDir = module.getTestSources().iterator().next().getDestinationDir().toPath().toAbsolutePath(); + Path testClassesDir = module.getWorkspaceModule().getTestSources().getSourceDirs().iterator().next().getOutputDir() + .toAbsolutePath(); testClassesPath = testClassesDir.toString(); - resourcePaths = module.getMainResources().stream().map(src -> src.getSourceDir().toPath().toAbsolutePath()) - .collect(Collectors.toCollection(LinkedHashSet::new)); - testResourcePaths = module.getTestResources().stream().map(src -> src.getSourceDir().toPath().toAbsolutePath()) - .collect(Collectors.toCollection(LinkedHashSet::new)); + resourcePaths = new LinkedHashSet<>(); + for (SourceDir src : sources.getResourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + resourcePaths.add(p.toAbsolutePath()); + } + } + + testResourcePaths = new LinkedHashSet<>(); + ArtifactSources testSources = module.getWorkspaceModule().getTestSources(); + if (testSources != null) { + for (SourceDir src : testSources.getResourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + testResourcePaths.add(p.toAbsolutePath()); + } + } + } + // Add the resources and test resources from the profiles for (Profile profile : activeProfiles) { final BuildBase build = profile.getBuild(); @@ -750,27 +778,27 @@ private void addProject(MavenDevModeLauncher.Builder builder, WorkspaceModule mo } if (classesPath == null && (!sourcePaths.isEmpty() || !resourcePaths.isEmpty())) { - throw new MojoExecutionException("Hot reloadable dependency " + module.getId() + throw new MojoExecutionException("Hot reloadable dependency " + module.getWorkspaceModule().getId() + " has not been compiled yet (the classes directory " + classesDir + " does not exist)"); } Path targetDir = Paths.get(project.getBuild().getDirectory()); DevModeContext.ModuleInfo moduleInfo = new DevModeContext.ModuleInfo.Builder() - .setArtifactKey(new GACT(module.getId().getGroupId(), module.getId().getArtifactId())) - .setName(module.getId().getArtifactId()) + .setArtifactKey(module.getKey()) + .setName(module.getArtifactId()) .setProjectDirectory(projectDirectory) - .setSourcePaths(PathsCollection.from(sourcePaths)) + .setSourcePaths(PathList.from(sourcePaths)) .setClassesPath(classesPath) .setResourcesOutputPath(classesPath) - .setResourcePaths(PathsCollection.from(resourcePaths)) - .setSourceParents(PathsCollection.of(sourceParent.toAbsolutePath())) + .setResourcePaths(PathList.from(resourcePaths)) + .setSourceParents(PathList.of(sourceParent.toAbsolutePath())) .setPreBuildOutputDir(targetDir.resolve("generated-sources").toAbsolutePath().toString()) .setTargetDir(targetDir.toAbsolutePath().toString()) - .setTestSourcePaths(PathsCollection.from(testSourcePaths)) + .setTestSourcePaths(PathList.from(testSourcePaths)) .setTestClassesPath(testClassesPath) .setTestResourcesOutputPath(testClassesPath) - .setTestResourcePaths(PathsCollection.from(testResourcePaths)) + .setTestResourcePaths(PathList.from(testResourcePaths)) .build(); if (root) { @@ -947,14 +975,14 @@ private QuarkusDevModeLauncher newLauncher() throws Exception { builder.jvmArgs("-D" + BootstrapConstants.SERIALIZED_APP_MODEL + "=" + appModelLocation); if (noDeps) { - addProject(builder, appModel.getApplicationModule(), true); + addProject(builder, appModel.getAppArtifact(), true); appModel.getApplicationModule().getBuildFiles().forEach(p -> builder.watchedBuildFile(p)); builder.localArtifact(new AppArtifactKey(project.getGroupId(), project.getArtifactId())); } else { - for (WorkspaceModule project : DependenciesFilter.getReloadableModules(appModel)) { - addProject(builder, project, project == appModel.getApplicationModule()); - project.getBuildFiles().forEach(p -> builder.watchedBuildFile(p)); - builder.localArtifact(new AppArtifactKey(project.getId().getGroupId(), project.getId().getArtifactId())); + for (ResolvedDependency project : DependenciesFilter.getReloadableModules(appModel)) { + addProject(builder, project, project == appModel.getAppArtifact()); + project.getWorkspaceModule().getBuildFiles().forEach(p -> builder.watchedBuildFile(p)); + builder.localArtifact(new AppArtifactKey(project.getGroupId(), project.getArtifactId())); } } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java index 2464f0b2f07e2..8a4505c2db35e 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/GenerateCodeMojo.java @@ -16,7 +16,8 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; +import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; // in the PROCESS_RESOURCES phase because we want the config to be available @@ -72,12 +73,12 @@ void generateCode(Path sourcesDir, Thread.currentThread().setContextClassLoader(deploymentClassLoader); final Class codeGenerator = deploymentClassLoader.loadClass("io.quarkus.deployment.CodeGenerator"); - final Method initAndRun = codeGenerator.getMethod("initAndRun", ClassLoader.class, PathsCollection.class, + final Method initAndRun = codeGenerator.getMethod("initAndRun", ClassLoader.class, PathCollection.class, Path.class, Path.class, Consumer.class, ApplicationModel.class, Properties.class, String.class); initAndRun.invoke(null, deploymentClassLoader, - PathsCollection.of(sourcesDir), + PathList.of(sourcesDir), generatedSourcesDir(test), buildDir().toPath(), sourceRegistrar, diff --git a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java index 64d4917608d87..d983df29a0d7f 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/QuarkusMavenWorkspaceBuilder.java @@ -7,7 +7,8 @@ import org.apache.maven.project.MavenProject; import io.quarkus.bootstrap.model.ApplicationModelBuilder; -import io.quarkus.bootstrap.workspace.DefaultProcessedSources; +import io.quarkus.bootstrap.workspace.DefaultArtifactSources; +import io.quarkus.bootstrap.workspace.DefaultSourceDir; import io.quarkus.bootstrap.workspace.DefaultWorkspaceModule; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.bootstrap.workspace.WorkspaceModuleId; @@ -24,22 +25,25 @@ static WorkspaceModule toProjectModule(MavenProject project) { final DefaultWorkspaceModule module = new DefaultWorkspaceModule(getId(project), project.getBasedir(), new File(build.getDirectory())); + final DefaultArtifactSources sourceSet = new DefaultArtifactSources(DefaultWorkspaceModule.MAIN); final File classesDir = new File(build.getOutputDirectory()); project.getCompileSourceRoots() - .forEach(s -> module.addMainSources(new DefaultProcessedSources(new File(s), classesDir))); - final File testClassesDir = new File(build.getTestOutputDirectory()); - project.getTestCompileSourceRoots() - .forEach(s -> module.addTestSources(new DefaultProcessedSources(new File(s), testClassesDir))); - + .forEach(s -> sourceSet.addSources(new DefaultSourceDir(new File(s), classesDir))); for (Resource r : build.getResources()) { - module.addMainResources(new DefaultProcessedSources(new File(r.getDirectory()), + sourceSet.addResources(new DefaultSourceDir(new File(r.getDirectory()), r.getTargetPath() == null ? classesDir : new File(r.getTargetPath()))); } + module.addArtifactSources(sourceSet); + final DefaultArtifactSources testSourceSet = new DefaultArtifactSources(DefaultWorkspaceModule.TEST); + final File testClassesDir = new File(build.getTestOutputDirectory()); + project.getTestCompileSourceRoots() + .forEach(s -> testSourceSet.addSources(new DefaultSourceDir(new File(s), testClassesDir))); for (Resource r : build.getTestResources()) { - module.addTestResources(new DefaultProcessedSources(new File(r.getDirectory()), + testSourceSet.addResources(new DefaultSourceDir(new File(r.getDirectory()), r.getTargetPath() == null ? testClassesDir : new File(r.getTargetPath()))); } + module.addArtifactSources(testSourceSet); module.setBuildFiles(PathList.of(project.getFile().toPath())); diff --git a/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java b/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java index aec0f2b0e83b5..ab5c53a1b851d 100644 --- a/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java +++ b/extensions/awt/deployment/src/main/java/io/quarkus/awt/deployment/AwtProcessor.java @@ -5,7 +5,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -21,6 +20,7 @@ import io.quarkus.deployment.builditem.nativeimage.UnsupportedOSBuildItem; import io.quarkus.deployment.pkg.NativeConfig; import io.quarkus.deployment.pkg.steps.NativeBuild; +import io.quarkus.fs.util.ZipUtils; class AwtProcessor { @@ -120,7 +120,7 @@ GeneratedResourceBuildItem yankI18NPropertiesFromHostJDK(BuildProducer filesToCompile, Context context) { } @Override - public Path getSourcePath(Path classFilePath, PathsCollection sourcePaths, String classesPath) { + public Path getSourcePath(Path classFilePath, PathCollection sourcePaths, String classesPath) { // return same class so it is not removed return classFilePath; } diff --git a/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java b/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java index 0e4f32e03d3ef..b071478a0a1db 100644 --- a/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java +++ b/extensions/scala/deployment/src/main/java/io/quarkus/scala/deployment/ScalaCompilationProvider.java @@ -6,8 +6,8 @@ import java.util.Set; import java.util.stream.Collectors; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.deployment.dev.CompilationProvider; +import io.quarkus.paths.PathCollection; import scala.collection.JavaConverters; import scala.tools.nsc.Global; import scala.tools.nsc.Settings; @@ -36,7 +36,7 @@ public void compile(Set files, Context context) { } @Override - public Path getSourcePath(Path classFilePath, PathsCollection sourcePaths, String classesPath) { + public Path getSourcePath(Path classFilePath, PathCollection sourcePaths, String classesPath) { return classFilePath; } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 767c0b1dfa8f6..770786bfac86f 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -1,9 +1,7 @@ package io.quarkus.vertx.http.deployment; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.security.CodeSource; @@ -39,7 +37,6 @@ import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; -import io.quarkus.fs.util.ZipUtils; import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; import io.quarkus.netty.runtime.virtual.VirtualServerChannel; import io.quarkus.runtime.LaunchMode; @@ -59,6 +56,7 @@ import io.quarkus.vertx.http.runtime.cors.CORSRecorder; import io.quarkus.vertx.http.runtime.filters.Filter; import io.quarkus.vertx.http.runtime.filters.GracefulShutdownFilter; +import io.smallrye.common.classloader.ClassPathUtils; import io.vertx.core.Handler; import io.vertx.core.http.impl.Http1xServerRequest; import io.vertx.core.impl.VertxImpl; @@ -345,24 +343,28 @@ void registerExchangeAttributeBuilders(final BuildProducer { + final Path serviceDescriptorFilePath = root + .resolve("META-INF/services/io.quarkus.vertx.http.runtime.attribute.ExchangeAttributeBuilder"); + if (!Files.exists(serviceDescriptorFilePath)) { + logger.debug("Skipping registration of service providers for " + ExchangeAttributeBuilder.class + + " since no service descriptor file found"); + return; + } + // we register all the listed providers since the access log configuration is a runtime construct + // and we won't know at build time which attributes the user application will choose + final ServiceProviderBuildItem serviceProviderBuildItem; + try { + serviceProviderBuildItem = ServiceProviderBuildItem.allProviders(ExchangeAttributeBuilder.class.getName(), + serviceDescriptorFilePath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + exchangeAttributeBuilderService.produce(serviceProviderBuildItem); + }); + } catch (UncheckedIOException e) { + throw new BuildException(e.getCause(), Collections.emptyList()); } } diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index b542742b630fe..11c08fec1e177 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -4,16 +4,12 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import java.net.URISyntaxException; +import java.io.UncheckedIOException; import java.net.URL; import java.net.URLConnection; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; @@ -70,7 +66,6 @@ import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; -import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.ResolvedDependency; import io.quarkus.netty.runtime.virtual.VirtualChannel; import io.quarkus.netty.runtime.virtual.VirtualServerChannel; @@ -93,6 +88,7 @@ import io.quarkus.qute.Variant; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.TemplateHtmlBuilder; +import io.quarkus.runtime.util.ClassPathUtils; import io.quarkus.utilities.OS; import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; @@ -707,51 +703,61 @@ public Optional getVariant() { @BuildStep void collectTemplates(BuildProducer devTemplatePaths) { + final Enumeration devTemplateURLs; try { - ClassLoader classLoader = DevConsoleProcessor.class.getClassLoader(); - Enumeration devTemplateURLs = classLoader.getResources("/dev-templates"); - while (devTemplateURLs.hasMoreElements()) { - URL devTemplatesURL = devTemplateURLs.nextElement(); - String devTemplatesURLStr = devTemplatesURL.toExternalForm(); - if (devTemplatesURLStr.startsWith("jar:file:") && devTemplatesURLStr.endsWith("!/dev-templates")) { - String jarPath = devTemplatesURLStr.substring(9, devTemplatesURLStr.length() - 15); - if (File.separatorChar == '\\') { - // on Windows this will be /C:/some/path, so turn it into C:\some\path - jarPath = jarPath.substring(1).replace('/', '\\'); - } - try (FileSystem fs = ZipUtils - .newFileSystem(Paths.get(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name())), classLoader)) { - scanTemplates(fs, null, fs.getRootDirectories(), devTemplatePaths); - } - } else if ("file".equals(devTemplatesURL.getProtocol())) { - // This can happen if you run an example app in dev mode - // and this app is part of a multi-module project which also declares the extension - // Just try to locate the pom.properties file in the target/maven-archiver directory - // Note that this hack will not work if addMavenDescriptor=false or if the pomPropertiesFile is overriden - Path classes = Paths.get(devTemplatesURL.toURI()).getParent(); - Path target = classes != null ? classes.getParent() : null; - if (target != null) { - Path mavenArchiver = target.resolve("maven-archiver"); - if (mavenArchiver.toFile().canRead()) { - scanTemplates(null, mavenArchiver, Collections.singleton(classes), devTemplatePaths); - } - } + devTemplateURLs = DevConsoleProcessor.class.getClassLoader().getResources("/dev-templates"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + while (devTemplateURLs.hasMoreElements()) { + URL devTemplatesUrl = devTemplateURLs.nextElement(); + ClassPathUtils.consumeAsPath(devTemplatesUrl, devTemplatesDir -> { + final Path classesDir = devTemplatesDir.getParent(); + if (classesDir == null) { + return; + } + final Entry entry = readPomPropertiesIfBuilt(devTemplatesUrl, classesDir); + if (entry == null) { + return; + } + try { + scanTemplates(entry, devTemplatesDir, devTemplatePaths); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + + private Entry readPomPropertiesIfBuilt(URL devTemplatesURL, final Path classesDir) { + Entry entry = null; + try { + if ("jar".equals(devTemplatesURL.getProtocol())) { + final Path metaInf = classesDir.resolve("META-INF").resolve("maven"); + if (Files.exists(metaInf)) { + entry = ArtifactInfoUtil.groupIdAndArtifactId(metaInf); } + } else { + final Path rootDir = classesDir.getParent(); + final Path mavenArchiver = rootDir == null ? null : rootDir.resolve("maven-archiver"); + if (mavenArchiver == null || !mavenArchiver.toFile().canRead()) { + // it's a module output dir and the Maven metadata hasn't been generated + return null; + } + entry = ArtifactInfoUtil.groupIdAndArtifactId(mavenArchiver); } - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + if (entry == null) { + throw new RuntimeException("Missing POM metadata in " + devTemplatesURL); } + return entry; } - private void scanTemplates(FileSystem fs, Path pomPropertiesPath, Iterable rootDirectories, + private void scanTemplates(Entry entry, Path devTemplatesPath, BuildProducer devTemplatePaths) throws IOException { - Entry entry = fs != null ? ArtifactInfoUtil.groupIdAndArtifactId(fs) - : ArtifactInfoUtil.groupIdAndArtifactId(pomPropertiesPath); - if (entry == null) { - throw new RuntimeException("Missing pom metadata [fileSystem: " + fs + ", rootDirectories: " + rootDirectories - + ", pomPath: " + pomPropertiesPath + "]"); - } String prefix; // don't move stuff for our "root" dev console artifact, since it includes the main template if (entry.getKey().equals("io.quarkus") @@ -760,35 +766,33 @@ private void scanTemplates(FileSystem fs, Path pomPropertiesPath, Iterable else prefix = entry.getKey() + "." + entry.getValue() + "/"; - for (Path root : rootDirectories) { - Path devTemplatesPath = root.resolve("dev-templates"); - Files.walkFileTree(root, new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - if (dir.equals(root) || dir.toString().equals("/") || dir.startsWith(devTemplatesPath)) - return FileVisitResult.CONTINUE; - return FileVisitResult.SKIP_SUBTREE; - } + final Path root = devTemplatesPath.getParent(); + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (dir.equals(root) || dir.toString().equals("/") || dir.startsWith(devTemplatesPath)) + return FileVisitResult.CONTINUE; + return FileVisitResult.SKIP_SUBTREE; + } - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - String contents = Files.readString(file); - // don't move tags yet, since we don't know how to use them afterwards - String relativePath = devTemplatesPath.relativize(file).toString(); - String correctedPath; - if (File.separatorChar == '\\') { - relativePath = relativePath.replace('\\', '/'); - } - if (relativePath.startsWith(DevTemplatePathBuildItem.TAGS)) - correctedPath = relativePath; - else - correctedPath = prefix + relativePath; - devTemplatePaths - .produce(new DevTemplatePathBuildItem(correctedPath, contents)); - return super.visitFile(file, attrs); + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String contents = Files.readString(file); + // don't move tags yet, since we don't know how to use them afterwards + String relativePath = devTemplatesPath.relativize(file).toString(); + String correctedPath; + if (File.separatorChar == '\\') { + relativePath = relativePath.replace('\\', '/'); } - }); - } + if (relativePath.startsWith(DevTemplatePathBuildItem.TAGS)) + correctedPath = relativePath; + else + correctedPath = prefix + relativePath; + devTemplatePaths + .produce(new DevTemplatePathBuildItem(correctedPath, contents)); + return super.visitFile(file, attrs); + } + }); } public static class JavaDocResolver implements ValueResolver { diff --git a/independent-projects/bootstrap/app-model/pom.xml b/independent-projects/bootstrap/app-model/pom.xml index d3d813f510eda..c51ef8263dda4 100644 --- a/independent-projects/bootstrap/app-model/pom.xml +++ b/independent-projects/bootstrap/app-model/pom.xml @@ -32,6 +32,10 @@ + + io.quarkus + quarkus-fs-util + org.jboss.logging jboss-logging diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java index 4199d3eb0143e..2561406f12736 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/model/AppDependency.java @@ -74,7 +74,7 @@ public String toString() { if (isOptional()) { buf.append("optional "); } - if (isWorkspacetModule()) { + if (isWorkspaceModule()) { buf.append("local "); } if (isRuntimeExtensionArtifact()) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PathsUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PathsUtils.java deleted file mode 100644 index bbae89af02dd6..0000000000000 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/util/PathsUtils.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.quarkus.bootstrap.util; - -import io.quarkus.bootstrap.model.PathsCollection; -import java.io.File; -import java.util.Collection; - -public class PathsUtils { - - public static PathsCollection toPathsCollection(Collection c) { - final PathsCollection.Builder builder = PathsCollection.builder(); - for (File f : c) { - builder.add(f.toPath()); - } - return builder.build(); - } -} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java new file mode 100644 index 0000000000000..c2e4f2a70fe1a --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ArtifactSources.java @@ -0,0 +1,56 @@ +package io.quarkus.bootstrap.workspace; + +import io.quarkus.paths.EmptyPathTree; +import io.quarkus.paths.MultiRootPathTree; +import io.quarkus.paths.PathTree; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public interface ArtifactSources { + + String getClassifier(); + + Collection getSourceDirs(); + + Collection getResourceDirs(); + + default boolean isOutputAvailable() { + for (SourceDir src : getSourceDirs()) { + if (src.isOutputAvailable()) { + return true; + } + } + for (SourceDir src : getResourceDirs()) { + if (src.isOutputAvailable()) { + return true; + } + } + return false; + } + + default PathTree getOutputTree() { + final Collection sourceDirs = getSourceDirs(); + final Collection resourceDirs = getResourceDirs(); + final List trees = new ArrayList<>(sourceDirs.size() + resourceDirs.size()); + for (SourceDir src : sourceDirs) { + final PathTree outputTree = src.getOutputTree(); + if (outputTree != null && !outputTree.isEmpty() && !trees.contains(outputTree)) { + trees.add(outputTree); + } + } + for (SourceDir src : resourceDirs) { + final PathTree outputTree = src.getOutputTree(); + if (outputTree != null && !outputTree.isEmpty() && !trees.contains(outputTree)) { + trees.add(outputTree); + } + } + if (trees.isEmpty()) { + return EmptyPathTree.getInstance(); + } + if (trees.size() == 1) { + return trees.get(0); + } + return new MultiRootPathTree(trees.toArray(new PathTree[0])); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java new file mode 100644 index 0000000000000..a0024ba7adf0a --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultArtifactSources.java @@ -0,0 +1,58 @@ +package io.quarkus.bootstrap.workspace; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; + +public class DefaultArtifactSources implements ArtifactSources, Serializable { + + private final String classifier; + private final Collection sources = new ArrayList<>(1); + private final Collection resources = new ArrayList<>(1); + + public DefaultArtifactSources(String classifier) { + this.classifier = classifier; + } + + public DefaultArtifactSources(String classifier, SourceDir sources, SourceDir resources) { + this.classifier = Objects.requireNonNull(classifier, "The classifier is null"); + this.sources.add(sources); + this.resources.add(resources); + } + + @Override + public String getClassifier() { + return classifier; + } + + public void addSources(SourceDir src) { + this.sources.add(src); + } + + @Override + public Collection getSourceDirs() { + return sources; + } + + public void addResources(SourceDir src) { + this.resources.add(src); + } + + @Override + public Collection getResourceDirs() { + return resources; + } + + @Override + public String toString() { + final StringBuilder s = new StringBuilder(); + s.append(classifier); + if (s.length() > 0) { + s.append(' '); + } + s.append("sources: ").append(sources); + s.append(" resources: ").append(resources); + return s.toString(); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultProcessedSources.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultSourceDir.java similarity index 52% rename from independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultProcessedSources.java rename to independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultSourceDir.java index e5171b4767690..39a8ce9b2da12 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultProcessedSources.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultSourceDir.java @@ -1,36 +1,53 @@ package io.quarkus.bootstrap.workspace; +import io.quarkus.paths.DirectoryPathTree; +import io.quarkus.paths.PathTree; import java.io.File; import java.io.Serializable; +import java.nio.file.Path; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Objects; -public class DefaultProcessedSources implements ProcessedSources, Serializable { +public class DefaultSourceDir implements SourceDir, Serializable { - private final File srcDir; - private final File destinationDir; + private final PathTree srcTree; + private final PathTree outputTree; private final Map data; - public DefaultProcessedSources(File srcDir, File destinationDir) { + public DefaultSourceDir(File srcDir, File destinationDir) { this(srcDir, destinationDir, Collections.emptyMap()); } - public DefaultProcessedSources(File srcDir, File destinationDir, Map data) { - this.srcDir = srcDir; - this.destinationDir = destinationDir; + public DefaultSourceDir(File srcDir, File destinationDir, Map data) { + this(new DirectoryPathTree(srcDir.toPath()), new DirectoryPathTree(destinationDir.toPath()), data); + } + + public DefaultSourceDir(PathTree srcTree, PathTree outputTree, Map data) { + this.srcTree = srcTree; + this.outputTree = outputTree; this.data = data; } @Override - public File getSourceDir() { - return srcDir; + public Path getDir() { + return srcTree.getRoots().iterator().next(); + } + + @Override + public PathTree getSourceTree() { + return srcTree; + } + + @Override + public Path getOutputDir() { + return outputTree.getRoots().iterator().next(); } @Override - public File getDestinationDir() { - return destinationDir; + public PathTree getOutputTree() { + return outputTree; } public T getValue(Object key, Class type) { @@ -40,7 +57,7 @@ public T getValue(Object key, Class type) { @Override public int hashCode() { - return Objects.hash(data, destinationDir, srcDir); + return Objects.hash(data, outputTree, srcTree); } @Override @@ -51,15 +68,15 @@ public boolean equals(Object obj) { return false; if (getClass() != obj.getClass()) return false; - DefaultProcessedSources other = (DefaultProcessedSources) obj; - return Objects.equals(data, other.data) && Objects.equals(destinationDir, other.destinationDir) - && Objects.equals(srcDir, other.srcDir); + DefaultSourceDir other = (DefaultSourceDir) obj; + return Objects.equals(data, other.data) && Objects.equals(outputTree, other.outputTree) + && Objects.equals(srcTree, other.srcTree); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); - buf.append(srcDir).append(" -> ").append(destinationDir); + buf.append(srcTree.getRoots()).append(" -> ").append(outputTree.getRoots()); if (!data.isEmpty()) { final Iterator> i = data.entrySet().iterator(); Map.Entry e = i.next(); diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java index 6377863c7c3ec..77c2a1248a735 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/DefaultWorkspaceModule.java @@ -4,20 +4,20 @@ import io.quarkus.paths.PathList; import java.io.File; import java.io.Serializable; -import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; +import java.util.HashMap; +import java.util.Map; public class DefaultWorkspaceModule implements WorkspaceModule, Serializable { + public static final String MAIN = ""; + public static final String TEST = "tests"; + private final WorkspaceModuleId id; private final File moduleDir; private final File buildDir; - private final Collection mainSources = new ArrayList<>(1); - private final Collection mainResources = new ArrayList<>(1); - private final Collection testSources = new ArrayList<>(1); - private final Collection testResources = new ArrayList<>(1); private PathCollection buildFiles; + private final Map sourcesSets = new HashMap<>(); public DefaultWorkspaceModule(WorkspaceModuleId id, File moduleDir, File buildDir) { super(); @@ -41,40 +41,23 @@ public File getBuildDir() { return buildDir; } - @Override - public Collection getMainSources() { - return mainSources; - } - - public void addMainSources(ProcessedSources mainSources) { - this.mainSources.add(mainSources); + public void addArtifactSources(ArtifactSources src) { + sourcesSets.put(src.getClassifier(), src); } @Override - public Collection getMainResources() { - return mainResources; - } - - public void addMainResources(ProcessedSources mainResources) { - this.mainResources.add(mainResources); + public boolean hasSources(String classifier) { + return sourcesSets.containsKey(classifier); } @Override - public Collection getTestSources() { - return testSources; - } - - public void addTestSources(ProcessedSources testSources) { - this.testSources.add(testSources); + public ArtifactSources getSources(String name) { + return sourcesSets.get(name); } @Override - public Collection getTestResources() { - return testResources; - } - - public void addTestResources(ProcessedSources testResources) { - this.testResources.add(testResources); + public Collection getSourceClassifiers() { + return sourcesSets.keySet(); } public void setBuildFiles(PathCollection buildFiles) { @@ -91,22 +74,9 @@ public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(id); buf.append(" ").append(moduleDir); - appendSources(buf, "sources", getMainSources()); - appendSources(buf, "resources", getMainResources()); - appendSources(buf, "test-sources", getTestSources()); - appendSources(buf, "test-resources", getTestResources()); + sourcesSets.values().forEach(a -> { + buf.append(" ").append(a); + }); return buf.toString(); } - - private void appendSources(StringBuilder buf, String name, Collection sources) { - if (!sources.isEmpty()) { - buf.append(" ").append(name).append("["); - final Iterator i = sources.iterator(); - buf.append(i.next()); - while (i.hasNext()) { - buf.append(";").append(i.next()); - } - buf.append("]"); - } - } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ProcessedSources.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ProcessedSources.java deleted file mode 100644 index 1ff2191407cf0..0000000000000 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/ProcessedSources.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.quarkus.bootstrap.workspace; - -import java.io.File; - -public interface ProcessedSources { - - File getSourceDir(); - - File getDestinationDir(); - - default T getValue(Object key, Class type) { - return null; - } -} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java new file mode 100644 index 0000000000000..731e737a2222c --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/SourceDir.java @@ -0,0 +1,25 @@ +package io.quarkus.bootstrap.workspace; + +import io.quarkus.paths.PathTree; +import java.nio.file.Files; +import java.nio.file.Path; + +public interface SourceDir { + + Path getDir(); + + PathTree getSourceTree(); + + default boolean isOutputAvailable() { + final Path outputDir = getOutputDir(); + return outputDir != null && Files.exists(outputDir); + } + + Path getOutputDir(); + + PathTree getOutputTree(); + + default T getValue(Object key, Class type) { + return null; + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java index 42e8f02154885..829620ca9b646 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/bootstrap/workspace/WorkspaceModule.java @@ -1,6 +1,8 @@ package io.quarkus.bootstrap.workspace; +import io.quarkus.paths.EmptyPathTree; import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathTree; import java.io.File; import java.util.Collection; @@ -12,13 +14,34 @@ public interface WorkspaceModule { File getBuildDir(); - Collection getMainSources(); + Collection getSourceClassifiers(); - Collection getMainResources(); + boolean hasSources(String classifier); - Collection getTestSources(); + ArtifactSources getSources(String classifier); - Collection getTestResources(); + default boolean hasMainSources() { + return hasSources(DefaultWorkspaceModule.MAIN); + } + + default boolean hasTestSources() { + return hasSources(DefaultWorkspaceModule.TEST); + } + + default ArtifactSources getMainSources() { + return getSources(DefaultWorkspaceModule.MAIN); + } + + default ArtifactSources getTestSources() { + return getSources(DefaultWorkspaceModule.TEST); + } PathCollection getBuildFiles(); + + default PathTree getContentTree(String classifier) { + //System.out.println("WorkspaceModule.getContentTree " + /* getId() + */ ":" + classifier); + final ArtifactSources artifactSources = getSources(classifier); + return artifactSources == null || !artifactSources.isOutputAvailable() ? EmptyPathTree.getInstance() + : artifactSources.getOutputTree(); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java index 9cd0e8cd93f06..a027e48c3c0d3 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/Dependency.java @@ -26,12 +26,12 @@ default boolean isDeploymentCp() { return isFlagSet(DependencyFlags.DEPLOYMENT_CP); } - default boolean isWorkspacetModule() { + default boolean isWorkspaceModule() { return isFlagSet(DependencyFlags.WORKSPACE_MODULE); } default boolean isReloadable() { - return isFlagSet(DependencyFlags.RELOADABLE) && isWorkspacetModule(); + return isFlagSet(DependencyFlags.RELOADABLE) && isWorkspaceModule(); } default boolean isFlagSet(int flag) { diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java index 0c0b3cd118697..c59d7a0055e75 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/maven/dependency/ResolvedDependency.java @@ -1,7 +1,14 @@ package io.quarkus.maven.dependency; +import io.quarkus.bootstrap.workspace.ArtifactSources; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.paths.DirectoryPathTree; +import io.quarkus.paths.EmptyPathTree; +import io.quarkus.paths.MultiRootPathTree; import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathTree; +import java.nio.file.Files; +import java.nio.file.Path; public interface ResolvedDependency extends Dependency { @@ -15,4 +22,31 @@ default boolean isResolved() { default WorkspaceModule getWorkspaceModule() { return null; } + + default ArtifactSources getSources() { + final WorkspaceModule module = getWorkspaceModule(); + return module == null ? null : module.getSources(getClassifier()); + } + + default PathTree getContentTree() { + final WorkspaceModule module = getWorkspaceModule(); + final PathTree workspaceTree = module == null ? EmptyPathTree.getInstance() : module.getContentTree(getClassifier()); + if (!workspaceTree.isEmpty()) { + return workspaceTree; + } + final PathCollection paths = getResolvedPaths(); + if (paths == null || paths.isEmpty()) { + return EmptyPathTree.getInstance(); + } + if (paths.isSinglePath()) { + final Path p = paths.getSinglePath(); + return Files.isDirectory(p) ? new DirectoryPathTree(p) : PathTree.ofArchive(p); + } + final PathTree[] trees = new PathTree[paths.size()]; + int i = 0; + for (Path p : paths) { + trees[i++] = Files.isDirectory(p) ? new DirectoryPathTree(p) : PathTree.ofArchive(p); + } + return new MultiRootPathTree(trees); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java new file mode 100644 index 0000000000000..699f6ece70584 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/ArchivePathTree.java @@ -0,0 +1,202 @@ +package io.quarkus.paths; + +import io.quarkus.fs.util.ZipUtils; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +class ArchivePathTree implements PathTree { + + private final Path archive; + private final PathFilter pathFilter; + + ArchivePathTree(Path archive) { + this(archive, null); + } + + ArchivePathTree(Path archive, PathFilter pathFilter) { + this.archive = archive; + this.pathFilter = pathFilter; + } + + @Override + public Collection getRoots() { + return Collections.singletonList(archive); + } + + @Override + public URL getUrlIfExists(String name) { + if (!PathFilter.isVisible(pathFilter, name)) { + return null; + } + try (FileSystem fs = openFs()) { + return getUrlOrNull(fs, name); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read " + archive, e); + } + } + + private URL getUrlOrNull(FileSystem fs, String name) { + final Path path = fs.getPath(name); + return Files.exists(path) ? getUrlForElement(PathUtils.asString(path, "/")) : null; + } + + @Override + public void visit(PathVisitor visitor) { + try (FileSystem fs = openFs()) { + visit(fs, visitor); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read " + archive, e); + } + } + + private void visit(FileSystem fs, PathVisitor visitor) { + for (Path dir : fs.getRootDirectories()) { + DirectoryPathTree.visit(dir, visitor, newVisit(dir)); + } + } + + @Override + public void visitIfExists(String relativePath, PathVisitor visitor) { + if (!PathFilter.isVisible(pathFilter, relativePath)) { + return; + } + try (FileSystem fs = openFs()) { + visitIfExists(fs, relativePath, visitor); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read " + archive, e); + } + } + + private void visitIfExists(FileSystem fs, String relativePath, PathVisitor visitor) { + for (Path root : fs.getRootDirectories()) { + final Path path = root.resolve(relativePath); + if (!Files.exists(path)) { + continue; + } + newVisit(root).visit(visitor, path); + return; + } + } + + private FileSystem openFs() throws IOException { + return ZipUtils.newFileSystem(archive); + } + + private PathVisitBase newVisit(Path dir) { + return new PathVisitBase(dir, pathFilter) { + + @Override + public Path getRoot() { + return archive; + } + + @Override + public URL getUrl() { + return getUrlForElement(getRelativePath("/")); + } + }; + } + + private URL getUrlForElement(String path) { + final URL jarPath; + try { + jarPath = archive.toUri().toURL(); + } catch (MalformedURLException e) { + throw new UncheckedIOException("Failed to translate " + archive + " to URL", e); + } + String urlFile = jarPath.getProtocol() + ":" + jarPath.getPath() + "!/" + path; + try { + return new URL("jar", null, urlFile); + } catch (MalformedURLException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public OpenPathTree openTree() { + final FileSystem fs; + try { + fs = openFs(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return new OpenPathTree() { + + private final Lock writeLock; + final DirectoryPathTree dirTree; + { + ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + this.writeLock = readWriteLock.writeLock(); + dirTree = new DirectoryPathTree(fs.getPath("/"), pathFilter); + } + + @Override + public Collection getRoots() { + return ArchivePathTree.this.getRoots(); + } + + @Override + public void visit(PathVisitor visitor) { + dirTree.visit(visitor); + } + + @Override + public void visitIfExists(String relativePath, PathVisitor visitor) { + dirTree.visitIfExists(relativePath, visitor); + } + + @Override + public URL getUrlIfExists(String relativePath) { + return dirTree.getUrlIfExists(relativePath); + } + + @Override + public OpenPathTree openTree() { + return this; + } + + @Override + public void close() throws IOException { + writeLock.lock(); + try { + fs.close(); + } finally { + writeLock.unlock(); + } + } + + @Override + public PathTree getInitialTree() { + return ArchivePathTree.this; + } + }; + } + + @Override + public int hashCode() { + return Objects.hash(archive, pathFilter); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ArchivePathTree other = (ArchivePathTree) obj; + return Objects.equals(archive, other.archive) && Objects.equals(pathFilter, other.pathFilter); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java new file mode 100644 index 0000000000000..9c6e2f1f55a0a --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java @@ -0,0 +1,145 @@ +package io.quarkus.paths; + +import java.io.IOException; +import java.io.Serializable; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +public class DirectoryPathTree implements OpenPathTree, Serializable { + + private Path dir; + private PathFilter pathFilter; + + public DirectoryPathTree(Path dir) { + this(dir, null); + } + + public DirectoryPathTree(Path dir, PathFilter pathFilter) { + this.dir = dir; + this.pathFilter = pathFilter; + } + + @Override + public Collection getRoots() { + return Collections.singletonList(dir); + } + + @Override + public URL getUrlIfExists(String relativePath) { + if (!PathFilter.isVisible(pathFilter, relativePath)) { + return null; + } + final Path path = dir.resolve(relativePath); + try { + return Files.exists(path) ? path.toUri().toURL() : null; + } catch (MalformedURLException e) { + throw new UncheckedIOException("Failed to translate " + path + " to URL", e); + } + } + + @Override + public void visit(PathVisitor visitor) { + visit(dir, visitor, newVisit()); + } + + @Override + public void visitIfExists(String relativePath, PathVisitor visitor) { + if (!PathFilter.isVisible(pathFilter, relativePath)) { + return; + } + final Path path = dir.resolve(relativePath); + if (!Files.exists(path)) { + return; + } + newVisit().visit(visitor, path); + } + + private PathVisitBase newVisit() { + return new PathVisitBase(dir, pathFilter) { + + @Override + public Path getRoot() { + return dir; + } + + @Override + public URL getUrl() { + try { + return getPath().toUri().toURL(); + } catch (MalformedURLException e) { + throw new UncheckedIOException("Failed to translate " + getPath() + " to " + URL.class.getName(), e); + } + } + }; + } + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + out.writeUTF(dir.toAbsolutePath().toString()); + out.writeObject(pathFilter); + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + dir = Paths.get(in.readUTF()); + pathFilter = (PathFilter) in.readObject(); + } + + protected static void visit(Path dir, PathVisitor visitor, PathVisitBase visit) { + try { + Files.walkFileTree(dir, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + return visit.visit(visitor, file); + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + return visit.visit(visitor, dir); + } + }); + } catch (IOException e) { + throw new UncheckedIOException("Failed to walk directory " + dir, e); + } + } + + @Override + public OpenPathTree openTree() { + return this; + } + + @Override + public void close() throws IOException { + } + + @Override + public PathTree getInitialTree() { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(dir, pathFilter); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DirectoryPathTree other = (DirectoryPathTree) obj; + return Objects.equals(dir, other.dir) && Objects.equals(pathFilter, other.pathFilter); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java new file mode 100644 index 0000000000000..f220fe881ad47 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/EmptyPathTree.java @@ -0,0 +1,48 @@ +package io.quarkus.paths; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; + +public class EmptyPathTree implements OpenPathTree { + + private static final EmptyPathTree INSTANCE = new EmptyPathTree(); + + public static EmptyPathTree getInstance() { + return INSTANCE; + } + + @Override + public Collection getRoots() { + return Collections.emptyList(); + } + + @Override + public void visit(PathVisitor visitor) { + } + + @Override + public void visitIfExists(String relativePath, PathVisitor visitor) { + } + + @Override + public URL getUrlIfExists(String relativePath) { + return null; + } + + @Override + public OpenPathTree openTree() { + return this; + } + + @Override + public void close() throws IOException { + } + + @Override + public PathTree getInitialTree() { + return this; + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java new file mode 100644 index 0000000000000..08b452a390a48 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/FilePathTree.java @@ -0,0 +1,111 @@ +package io.quarkus.paths; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +class FilePathTree implements OpenPathTree { + + private final Path file; + private final PathFilter pathFilter; + + FilePathTree(Path file) { + this(file, null); + } + + FilePathTree(Path file, PathFilter pathFilter) { + this.file = file; + this.pathFilter = pathFilter; + } + + @Override + public Collection getRoots() { + return Collections.singletonList(file); + } + + @Override + public URL getUrlIfExists(String relativePath) { + return null; + } + + @Override + public void visit(PathVisitor visitor) { + if (pathFilter != null) { + final String pathStr = file.getFileSystem().getSeparator().equals("/") ? file.toString() + : file.toString().replace('\\', '/'); + if (!pathFilter.isVisible(pathStr)) { + return; + } + return; + } + visitor.visitPath(new PathVisit() { + + @Override + public Path getRoot() { + return file; + } + + @Override + public Path getPath() { + return file; + } + + @Override + public URL getUrl() { + try { + return file.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException("Failed to translate " + file + " to " + URL.class.getName(), e); + } + } + + @Override + public void stopVisiting() { + } + + @Override + public Path getRelativePath() { + return file; + } + }); + } + + @Override + public void visitIfExists(String relativePath, PathVisitor visitor) { + } + + @Override + public OpenPathTree openTree() { + return this; + } + + @Override + public void close() throws IOException { + } + + @Override + public PathTree getInitialTree() { + return this; + } + + @Override + public int hashCode() { + return Objects.hash(file, pathFilter); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + FilePathTree other = (FilePathTree) obj; + return Objects.equals(file, other.file) && Objects.equals(pathFilter, other.pathFilter); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java new file mode 100644 index 0000000000000..472d9e9ae4647 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/MultiRootPathTree.java @@ -0,0 +1,92 @@ +package io.quarkus.paths; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class MultiRootPathTree implements OpenPathTree { + + private final PathTree[] trees; + private final List roots; + + public MultiRootPathTree(PathTree... trees) { + this.trees = trees; + final ArrayList tmp = new ArrayList<>(); + for (PathTree t : trees) { + tmp.addAll(t.getRoots()); + } + tmp.trimToSize(); + roots = tmp; + } + + @Override + public Collection getRoots() { + return roots; + } + + @Override + public void visit(PathVisitor visitor) { + if (trees.length == 0) { + return; + } + for (PathTree t : trees) { + t.visit(visitor); + } + } + + @Override + public void visitIfExists(String relativePath, PathVisitor visitor) { + for (PathTree tree : trees) { + tree.visitIfExists(relativePath, visitor); + } + } + + @Override + public URL getUrlIfExists(String relativePath) { + for (PathTree tree : trees) { + final URL url = tree.getUrlIfExists(relativePath); + if (url != null) { + return url; + } + } + return null; + } + + @Override + public OpenPathTree openTree() { + return this; + } + + @Override + public void close() throws IOException { + } + + @Override + public PathTree getInitialTree() { + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(trees); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MultiRootPathTree other = (MultiRootPathTree) obj; + return Arrays.equals(trees, other.trees); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenPathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenPathTree.java new file mode 100644 index 0000000000000..c9c9333d6773a --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenPathTree.java @@ -0,0 +1,8 @@ +package io.quarkus.paths; + +import java.io.Closeable; + +public interface OpenPathTree extends PathTree, Closeable { + + PathTree getInitialTree(); +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathCollection.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathCollection.java index ff5d13b0072b6..a91d730a151a3 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathCollection.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathCollection.java @@ -1,6 +1,9 @@ package io.quarkus.paths; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; public interface PathCollection extends Iterable { @@ -26,4 +29,12 @@ default Path getSinglePath() { PathCollection addAllFirst(Iterable i); Path resolveExistingOrNull(String path); + + default Stream stream() { + final List list = new ArrayList<>(size()); + for (Path p : this) { + list.add(p); + } + return list.stream(); + } } diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java new file mode 100644 index 0000000000000..c85d8d4d99723 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathFilter.java @@ -0,0 +1,150 @@ +package io.quarkus.paths; + +import io.quarkus.util.GlobUtil; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.regex.Pattern; + +public class PathFilter implements Serializable { + + public static boolean isVisible(PathFilter filter, String path) { + if (filter == null) { + return true; + } + return filter.isVisible(path.replace('\\', '/')); + } + + public static PathFilter forIncludes(Collection includes) { + return new PathFilter(includes, null); + } + + public static PathFilter forExcludes(Collection excludes) { + return new PathFilter(null, excludes); + } + + private List includes; + private List excludes; + + PathFilter(Collection includes, Collection excludes) { + this.includes = compile(includes); + this.excludes = compile(excludes); + } + + public boolean isVisible(String pathStr) { + if (includes != null) { + for (Pattern pattern : includes) { + if (pattern.matcher(pathStr).matches()) { + return true; + } + } + return false; + } + if (excludes != null) { + for (Pattern pattern : excludes) { + if (pattern.matcher(pathStr).matches()) { + return false; + } + } + return true; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + patternsHash(excludes); + result = prime * result + patternsHash(includes); + return result; + } + + private static int patternsHash(List patterns) { + int result = 1; + if (patterns != null) { + for (Pattern p : patterns) { + result = 31 * result + p.pattern().hashCode(); + } + } + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PathFilter other = (PathFilter) obj; + return patternsEqual(excludes, other.excludes) && patternsEqual(includes, other.includes); + } + + private static boolean patternsEqual(List p1, List p2) { + if (p1 == null) { + if (p2 != null) + return false; + } else if (p2 == null || p1.size() != p2.size()) { + return false; + } else { + for (int i = 0; i < p1.size(); ++i) { + if (!p1.get(i).pattern().equals(p2.get(i).pattern())) { + return false; + } + } + } + return true; + } + + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + writePatterns(out, includes); + writePatterns(out, excludes); + } + + private void writePatterns(java.io.ObjectOutputStream out, Collection patterns) throws IOException { + if (patterns == null) { + out.writeInt(-1); + } else { + out.writeInt(patterns.size()); + for (Pattern s : patterns) { + out.writeUTF(s.pattern()); + } + } + } + + private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + includes = readPatterns(in); + excludes = readPatterns(in); + } + + private List readPatterns(java.io.ObjectInputStream in) throws IOException { + int size = in.readInt(); + if (size < 0) { + return null; + } + final List patterns = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + patterns.add(compile(in.readUTF())); + } + return patterns; + } + + private static List compile(Collection expressions) { + if (expressions == null) { + return null; + } + final List compiled = new ArrayList<>(expressions.size()); + for (String expr : expressions) { + compiled.add(compile(expr)); + } + return compiled; + } + + private static Pattern compile(String expr) { + return Pattern.compile(GlobUtil.toRegexPattern(expr)); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathList.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathList.java index 84a39c330f744..e858c9b730253 100644 --- a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathList.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathList.java @@ -7,10 +7,10 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Objects; public class PathList implements PathCollection, Serializable { @@ -133,6 +133,23 @@ public String toString() { return buf.append(']').toString(); } + @Override + public int hashCode() { + return Objects.hash(paths); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PathList other = (PathList) obj; + return Objects.equals(paths, other.paths); + } + private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.writeInt(paths.size()); for (Path p : paths) { @@ -148,8 +165,4 @@ private void readObject(java.io.ObjectInputStream in) throws IOException, ClassN } this.paths = Collections.unmodifiableList(paths); } - - public Collection toList() { - return new ArrayList<>(paths); - } -} \ No newline at end of file +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java new file mode 100644 index 0000000000000..11108efa750ec --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTree.java @@ -0,0 +1,48 @@ +package io.quarkus.paths; + +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; + +public interface PathTree { + + static PathTree of(Path p) { + if (Files.isDirectory(p)) { + return new DirectoryPathTree(p); + } + if (Files.exists(p)) { + return new FilePathTree(p); + } + throw new IllegalArgumentException(p + " does not exist"); + } + + static PathTree ofArchive(Path archive) { + if (!Files.exists(archive)) { + throw new IllegalArgumentException(archive + " does not exist"); + } + return new ArchivePathTree(archive); + } + + static PathTreeBuilder builder() { + return new PathTreeBuilder(false); + } + + static PathTreeBuilder archiveBuilder() { + return new PathTreeBuilder(true); + } + + Collection getRoots(); + + default boolean isEmpty() { + return getRoots().isEmpty(); + } + + void visit(PathVisitor visitor); + + void visitIfExists(String relativePath, PathVisitor visitor); + + URL getUrlIfExists(String relativePath); + + OpenPathTree openTree(); +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeBuilder.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeBuilder.java new file mode 100644 index 0000000000000..fb8b2333a3df3 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathTreeBuilder.java @@ -0,0 +1,71 @@ +package io.quarkus.paths; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class PathTreeBuilder { + + private final boolean archive; + private Path root; + private List includes; + private List excludes; + + public PathTreeBuilder(boolean archive) { + this.archive = archive; + } + + public PathTreeBuilder setRoot(Path root) { + this.root = root; + return this; + } + + Path getRoot() { + return root; + } + + public PathTreeBuilder include(String expr) { + if (includes == null) { + includes = new ArrayList<>(1); + } + includes.add(expr); + return this; + } + + List getIncludes() { + return includes; + } + + public PathTreeBuilder exclude(String expr) { + if (excludes == null) { + excludes = new ArrayList<>(1); + } + excludes.add(expr); + return this; + } + + List getExcludes() { + return includes; + } + + PathFilter getPathFilter() { + return includes == null && excludes == null ? null : new PathFilter(includes, excludes); + } + + public PathTree build() { + if (root == null) { + throw new RuntimeException("The tree root has not been provided"); + } + if (!Files.exists(root)) { + throw new IllegalArgumentException(root + " does not exist"); + } + if (archive) { + return new ArchivePathTree(root, getPathFilter()); + } + if (Files.isDirectory(root)) { + return new DirectoryPathTree(root, getPathFilter()); + } + return new FilePathTree(root, getPathFilter()); + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java new file mode 100644 index 0000000000000..a87a7f83e3e0a --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathUtils.java @@ -0,0 +1,26 @@ +package io.quarkus.paths; + +import java.nio.file.Path; + +public interface PathUtils { + + static String asString(final Path path, String separator) { + if (path.getFileSystem().getSeparator().equals(separator)) { + return path.toString(); + } + final int nameCount = path.getNameCount(); + if (nameCount == 0) { + return ""; + } + if (nameCount == 1) { + return path.getName(0).toString(); + } + final StringBuilder s = new StringBuilder(); + s.append(path.getName(0)); + for (int i = 1; i < nameCount; ++i) { + s.append('/').append(path.getName(i).toString()); + } + return s.toString(); + } + +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java new file mode 100644 index 0000000000000..1b382e81e7054 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisit.java @@ -0,0 +1,21 @@ +package io.quarkus.paths; + +import java.net.URL; +import java.nio.file.Path; + +public interface PathVisit { + + Path getRoot(); + + Path getPath(); + + URL getUrl(); + + Path getRelativePath(); + + default String getRelativePath(String separator) { + return PathUtils.asString(getRelativePath(), separator); + } + + void stopVisiting(); +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitBase.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitBase.java new file mode 100644 index 0000000000000..39cb01b7dd013 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitBase.java @@ -0,0 +1,42 @@ +package io.quarkus.paths; + +import java.nio.file.FileVisitResult; +import java.nio.file.Path; + +abstract class PathVisitBase implements PathVisit { + + protected final Path baseDir; + protected final PathFilter pathFilter; + + private Path current; + private boolean stopVisiting; + + protected PathVisitBase(Path baseDir, PathFilter pathFilter) { + this.baseDir = baseDir; + this.pathFilter = pathFilter; + } + + @Override + public Path getPath() { + return current; + } + + @Override + public void stopVisiting() { + stopVisiting = true; + } + + @Override + public Path getRelativePath() { + return baseDir.relativize(current); + } + + protected FileVisitResult visit(PathVisitor visitor, Path path) { + if (pathFilter != null && !PathFilter.isVisible(pathFilter, baseDir.relativize(path).toString())) { + return FileVisitResult.CONTINUE; + } + this.current = path; + visitor.visitPath(this); + return stopVisiting ? FileVisitResult.TERMINATE : FileVisitResult.CONTINUE; + } +} diff --git a/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitor.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitor.java new file mode 100644 index 0000000000000..2ffbb2cf22148 --- /dev/null +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/PathVisitor.java @@ -0,0 +1,6 @@ +package io.quarkus.paths; + +public interface PathVisitor { + + void visitPath(PathVisit visit); +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/GlobUtil.java b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/util/GlobUtil.java similarity index 99% rename from core/deployment/src/main/java/io/quarkus/deployment/util/GlobUtil.java rename to independent-projects/bootstrap/app-model/src/main/java/io/quarkus/util/GlobUtil.java index 7b7ccbb1d272d..84ce28d1a1ae7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/GlobUtil.java +++ b/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/util/GlobUtil.java @@ -1,4 +1,4 @@ -package io.quarkus.deployment.util; +package io.quarkus.util; import java.util.regex.Pattern; diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/GlobUtilTest.java b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/util/GlobUtilTest.java similarity index 99% rename from core/deployment/src/test/java/io/quarkus/deployment/util/GlobUtilTest.java rename to independent-projects/bootstrap/app-model/src/test/java/io/quarkus/util/GlobUtilTest.java index 6d60735286e3e..d3df4885a7c7b 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/util/GlobUtilTest.java +++ b/independent-projects/bootstrap/app-model/src/test/java/io/quarkus/util/GlobUtilTest.java @@ -1,11 +1,10 @@ -package io.quarkus.deployment.util; +package io.quarkus.util; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/independent-projects/bootstrap/core/pom.xml b/independent-projects/bootstrap/core/pom.xml index 0f4a3dfb153cc..7863d8238f0eb 100644 --- a/independent-projects/bootstrap/core/pom.xml +++ b/independent-projects/bootstrap/core/pom.xml @@ -45,10 +45,6 @@ io.quarkus quarkus-bootstrap-gradle-resolver - - io.quarkus - quarkus-fs-util - io.smallrye.common smallrye-common-io diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java index 4368e1f28fb19..3f90687f6105b 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/IDELauncherImpl.java @@ -8,7 +8,10 @@ import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.bootstrap.utils.BuildToolHelper; +import io.quarkus.bootstrap.workspace.ArtifactSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.maven.dependency.ResolvedDependency; import java.io.Closeable; import java.io.IOException; import java.nio.file.Path; @@ -42,25 +45,26 @@ public static Closeable launch(Path classesDir, Map context) { final ApplicationModel quarkusModel = BuildToolHelper.enableGradleAppModelForDevMode(classesDir); context.put(BootstrapConstants.SERIALIZED_APP_MODEL, BootstrapUtils.serializeAppModel(quarkusModel, false)); - final Path launchingModulePath = quarkusModel.getApplicationModule().getMainSources().iterator().next() - .getDestinationDir().toPath(); + final Path launchingModulePath = quarkusModel.getApplicationModule().getMainSources().getSourceDirs().iterator() + .next().getOutputDir(); // Gradle uses a different output directory for classes, we override the one used by the IDE builder.setProjectRoot(launchingModulePath) .setApplicationRoot(launchingModulePath) .setTargetDirectory(quarkusModel.getApplicationModule().getBuildDir().toPath()); - for (WorkspaceModule additionalModule : quarkusModel.getWorkspaceModules()) { - additionalModule.getMainSources().forEach(src -> { - builder.addAdditionalApplicationArchive( - new AdditionalDependency(src.getDestinationDir().toPath(), true, false)); - - }); - additionalModule.getMainResources().forEach(src -> { - builder.addAdditionalApplicationArchive( - new AdditionalDependency(src.getDestinationDir().toPath(), true, false)); - - }); + for (ResolvedDependency dep : quarkusModel.getDependencies()) { + final WorkspaceModule module = dep.getWorkspaceModule(); + if (module == null) { + continue; + } + final ArtifactSources sources = module.getSources(dep.getClassifier()); + for (SourceDir dir : sources.getSourceDirs()) { + builder.addAdditionalApplicationArchive(new AdditionalDependency(dir.getOutputDir(), true, false)); + } + for (SourceDir dir : sources.getResourceDirs()) { + builder.addAdditionalApplicationArchive(new AdditionalDependency(dir.getOutputDir(), true, false)); + } } } else { builder.setApplicationRoot(classesDir) diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AdditionalDependency.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AdditionalDependency.java index 8c550a165cd68..0639e25332691 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AdditionalDependency.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/AdditionalDependency.java @@ -38,16 +38,10 @@ public AdditionalDependency(Path archivePath, boolean hotReloadable, boolean for } /** - * @deprecated in favor of {@link #AdditionalDependency(PathCollection, boolean, boolean)} * @param archivePath archive paths * @param hotReloadable whether the dependency is reloadable * @param forceApplicationArchive whether it should be added as an application archive */ - @Deprecated - public AdditionalDependency(PathsCollection archivePath, boolean hotReloadable, boolean forceApplicationArchive) { - this(PathList.from(archivePath), hotReloadable, forceApplicationArchive); - } - public AdditionalDependency(PathCollection paths, boolean hotReloadable, boolean forceApplicationArchive) { this.paths = paths; this.hotReloadable = hotReloadable; diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java index b12c6d051bf6c..b15f31dfb9367 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java @@ -4,6 +4,7 @@ import io.quarkus.bootstrap.classloading.ClassPathResource; import io.quarkus.bootstrap.classloading.FilteredClassPathElement; import io.quarkus.bootstrap.classloading.MemoryClassPathElement; +import io.quarkus.bootstrap.classloading.PathTreeClassPathElement; import io.quarkus.bootstrap.classloading.QuarkusClassLoader; import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.model.ApplicationModel; @@ -12,6 +13,7 @@ import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathTree; import java.io.IOException; import java.io.Serializable; import java.nio.file.Path; @@ -178,12 +180,21 @@ public void accept(ClassPathElement classPathElement) { } return; } - cpeList = new ArrayList<>(2); - for (Path path : artifact.getResolvedPaths()) { - final ClassPathElement element = ClassPathElement.fromPath(path); - consumer.accept(element); - cpeList.add(element); + final PathTree contentTree = artifact.getContentTree(); + if (contentTree.isEmpty()) { + return; } + cpeList = new ArrayList<>(2); + final ClassPathElement element = new PathTreeClassPathElement(contentTree); + consumer.accept(element); + cpeList.add(element); + /* + * for (Path path : artifact.getResolvedPaths()) { + * final ClassPathElement element = ClassPathElement.fromPath(path); + * consumer.accept(element); + * cpeList.add(element); + * } + */ augmentationElements.put(artifact.getKey(), cpeList); } @@ -310,7 +321,7 @@ public synchronized QuarkusClassLoader getBaseRuntimeClassLoader() { } private static boolean isHotReloadable(ResolvedDependency a, Set hotReloadPaths) { - for (Path p : a.getResolvedPaths()) { + for (Path p : a.getContentTree().getRoots()) { if (hotReloadPaths.contains(p)) { return true; } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java index c3113a8e06e20..c71c4f88d5224 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/QuarkusBootstrap.java @@ -5,7 +5,6 @@ import io.quarkus.bootstrap.classloading.ClassLoaderEventListener; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.AppModelResolver; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.update.DependenciesOrigin; @@ -16,6 +15,8 @@ import io.quarkus.maven.dependency.Dependency; import io.quarkus.maven.dependency.GACT; import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathList; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -43,7 +44,7 @@ public class QuarkusBootstrap implements Serializable { /** * The root of the application, where the application classes live. */ - private final PathsCollection applicationRoot; + private final PathCollection applicationRoot; /** * The root of the project. This may be different to the application root for tests that @@ -179,7 +180,7 @@ public CuratedApplication bootstrap() throws BootstrapException { return new CuratedApplication(this, appModelFactory.resolveAppModel(), classLoadingConfig); } - private static ConfiguredClassLoading createClassLoadingConfig(PathsCollection applicationRoot, Mode mode) { + private static ConfiguredClassLoading createClassLoadingConfig(PathCollection applicationRoot, Mode mode) { //look for an application.properties for (Path path : applicationRoot) { Path props = path.resolve("application.properties"); @@ -262,7 +263,7 @@ public AppModelResolver getAppModelResolver() { return appModelResolver; } - public PathsCollection getApplicationRoot() { + public PathCollection getApplicationRoot() { return applicationRoot; } @@ -304,7 +305,7 @@ public static Builder builder() { @Deprecated public static Builder builder(Path applicationRoot) { - return new Builder().setApplicationRoot(PathsCollection.of(applicationRoot)); + return new Builder().setApplicationRoot(PathList.of(applicationRoot)); } public String getBaseName() { @@ -385,7 +386,7 @@ public static class Builder { public boolean hostApplicationIsTestOnly; boolean flatClassPath; boolean rebuild; - PathsCollection applicationRoot; + PathCollection applicationRoot; String baseName; Path projectRoot; ClassLoader baseClassLoader = ClassLoader.getSystemClassLoader(); @@ -417,11 +418,11 @@ public Builder() { } public Builder setApplicationRoot(Path applicationRoot) { - this.applicationRoot = PathsCollection.of(applicationRoot); + this.applicationRoot = PathList.of(applicationRoot); return this; } - public Builder setApplicationRoot(PathsCollection applicationRoot) { + public Builder setApplicationRoot(PathCollection applicationRoot) { if (appArtifact != null) { throw new IllegalStateException("Cannot set both app artifact and application root"); } @@ -546,7 +547,7 @@ public Builder setAppArtifact(ResolvedDependency appArtifact) { throw new IllegalStateException("Cannot set both application root and app artifact"); } this.appArtifact = appArtifact; - this.applicationRoot = PathsCollection.from(appArtifact.getResolvedPaths()); + this.applicationRoot = PathList.from(appArtifact.getResolvedPaths()); if (appArtifact.getResolvedPaths().isSinglePath()) { this.projectRoot = appArtifact.getResolvedPaths().getSinglePath(); } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/AbstractClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/AbstractClassPathElement.java index 2982a5c26c4b4..2b5c55511dbd8 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/AbstractClassPathElement.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/AbstractClassPathElement.java @@ -9,28 +9,37 @@ public abstract class AbstractClassPathElement implements ClassPathElement { private static final Logger log = Logger.getLogger(AbstractClassPathElement.class); - private volatile Manifest manifest; - private volatile boolean initialized = false; + protected volatile Manifest manifest; + protected volatile boolean manifestInitialized = false; @Override public Manifest getManifest() { - if (initialized) { + if (manifestInitialized) { return manifest; } synchronized (this) { - if (initialized) { + if (manifestInitialized) { return manifest; } - ClassPathResource mf = getResource("META-INF/MANIFEST.MF"); - if (mf != null) { - try { - manifest = new Manifest(new ByteArrayInputStream(mf.getData())); - } catch (IOException e) { - log.warnf("Failed to parse manifest for %s", toString()); - } + initManifest(readManifest()); + } + return manifest; + } + + protected void initManifest(Manifest m) { + manifest = m; + manifestInitialized = true; + } + + protected Manifest readManifest() { + final ClassPathResource mf = getResource("META-INF/MANIFEST.MF"); + if (mf != null) { + try { + return new Manifest(new ByteArrayInputStream(mf.getData())); + } catch (IOException e) { + log.warnf("Failed to parse manifest for %s", toString()); } - initialized = true; - return manifest; } + return null; } } diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java new file mode 100644 index 0000000000000..bd2ca62097646 --- /dev/null +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java @@ -0,0 +1,286 @@ +package io.quarkus.bootstrap.classloading; + +import io.quarkus.paths.OpenPathTree; +import io.quarkus.paths.PathTree; +import io.quarkus.paths.PathVisit; +import io.quarkus.paths.PathVisitor; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; +import java.util.jar.Manifest; +import org.jboss.logging.Logger; + +public class PathTreeClassPathElement extends AbstractClassPathElement { + + public static final int JAVA_VERSION; + private static final Logger log = Logger.getLogger(PathTreeClassPathElement.class); + public static final String META_INF_VERSIONS = "META-INF/versions/"; + + static class ZipFileMayHaveChangedException extends RuntimeException { + public ZipFileMayHaveChangedException(Throwable cause) { + super(cause); + } + } + + static { + int version = 8; + try { + Method versionMethod = Runtime.class.getMethod("version"); + Object v = versionMethod.invoke(null); + List list = (List) v.getClass().getMethod("version").invoke(v); + version = list.get(0); + } catch (Exception e) { + //version 8 + } + JAVA_VERSION = version; + //force this class to be loaded + //if quarkus is recompiled it needs to have already + //been loaded + //this is just a convenience for quarkus devs that means exit + //should work properly if you recompile while quarkus is running + new ZipFileMayHaveChangedException(null); + } + + private final Lock readLock; + private final Lock writeLock; + private final OpenPathTree pathTree; + private volatile boolean closed; + private volatile Set resources; + private volatile Map multiReleasePathMap; + + public PathTreeClassPathElement(PathTree pathTree) { + this.pathTree = Objects.requireNonNull(pathTree, "Path tree is null").openTree(); + final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + this.readLock = readWriteLock.readLock(); + this.writeLock = readWriteLock.writeLock(); + } + + @Override + public Path getRoot() { + return pathTree.getRoots().iterator().next(); + } + + @Override + public ClassPathResource getResource(String name) { + if (!getProvidedResources().contains(name)) { + return null; + } + final String targetPath = multiReleasePathMap.getOrDefault(name, name); + return withOpenTree(tree -> { + final AtomicReference res = new AtomicReference<>(); + tree.visitIfExists(targetPath, new PathVisitor() { + @Override + public void visitPath(PathVisit visit) { + res.set(new ClassPathResource() { + + final boolean dir = Files.isDirectory(visit.getPath()); + final URL url = visit.getUrl(); + + @Override + public ClassPathElement getContainingElement() { + return PathTreeClassPathElement.this; + } + + @Override + public String getPath() { + return name; + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public byte[] getData() { + return withOpenTree(tree -> { + final AtomicReference bytes = new AtomicReference<>(); + tree.visitIfExists(targetPath, new PathVisitor() { + @Override + public void visitPath(PathVisit visit) { + try { + try { + bytes.set(Files.readAllBytes(visit.getPath())); + } catch (InterruptedIOException e) { + // if we are interrupted reading data we finish the op, then just + // re-interrupt the thread state + bytes.set(Files.readAllBytes(visit.getPath())); + Thread.currentThread().interrupt(); + } + } catch (IOException e) { + if (!closed) { + throw new ZipFileMayHaveChangedException(e); + } + throw new RuntimeException("Unable to read " + visit.getUrl(), e); + } + } + }); + return bytes.get(); + }); + } + + @Override + public boolean isDirectory() { + return dir; + } + }); + } + }); + return res.get(); + }); + } + + private T withOpenTree(Function func) { + readLock.lock(); + try { + if (closed) { + //we still need this to work if it is closed, so shutdown hooks work + //once it is closed it simply does not hold on to any resources + return func.apply(pathTree.getInitialTree()); + } else { + return func.apply(pathTree); + } + } finally { + readLock.unlock(); + } + } + + @Override + public Set getProvidedResources() { + if (resources == null) { + synchronized (this) { + if (resources != null) { + return resources; + } + this.resources = withOpenTree(tree -> { + final Manifest manifest = getManifest(tree); + final boolean multiReleaseJar = manifest != null + && Boolean.parseBoolean(manifest.getMainAttributes().getValue("Multi-Release")); + final List multiReleasePaths; + if (multiReleaseJar) { + multiReleasePaths = new ArrayList<>(); + multiReleasePathMap = new HashMap<>(); + } else { + multiReleasePaths = Collections.emptyList(); + multiReleasePathMap = Collections.emptyMap(); + } + + final Set resources = new HashSet<>(); + tree.visit(new PathVisitor() { + @Override + public void visitPath(PathVisit visit) { + final String relativePath = visit.getRelativePath("/"); + if (relativePath.isEmpty()) { + return; + } + resources.add(relativePath); + if (multiReleaseJar && relativePath.startsWith(META_INF_VERSIONS) + && !Files.isDirectory(visit.getPath())) { + final int slash = relativePath.indexOf('/', META_INF_VERSIONS.length() + 1); + if (slash == -1) { + return; + } + try { + final int ver = Integer.parseInt(relativePath.substring(META_INF_VERSIONS.length(), slash)); + if (ver <= JAVA_VERSION) { + final String mainPath = relativePath.substring(slash + 1); + multiReleasePaths.add(mainPath); + multiReleasePathMap.put(mainPath, relativePath); + } + } catch (NumberFormatException e) { + log.debug("Failed to parse META-INF/versions entry", e); + } + } + } + }); + resources.addAll(multiReleasePaths); + return resources; + }); + } + } + return resources; + } + + @Override + protected Manifest readManifest() { + return withOpenTree(tree -> { + return readManifestIfExists(tree); + }); + } + + private Manifest getManifest(PathTree tree) { + if (manifestInitialized) { + return manifest; + } + synchronized (this) { + if (manifestInitialized) { + return manifest; + } + initManifest(readManifestIfExists(tree)); + } + return manifest; + } + + private Manifest readManifestIfExists(PathTree tree) { + final AtomicReference ref = new AtomicReference<>(); + tree.visitIfExists("META-INF/MANIFEST.MF", new PathVisitor() { + @Override + public void visitPath(PathVisit visit) { + try (InputStream is = Files.newInputStream(visit.getPath())) { + ref.set(new Manifest(is)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + }); + return ref.get(); + } + + @Override + public ProtectionDomain getProtectionDomain(ClassLoader classLoader) { + URL url = null; + final Path root = getRoot(); + try { + url = root.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException("Unable to create protection domain for " + root, e); + } + CodeSource codesource = new CodeSource(url, (Certificate[]) null); + ProtectionDomain protectionDomain = new ProtectionDomain(codesource, null, classLoader, null); + return protectionDomain; + } + + @Override + public void close() throws IOException { + writeLock.lock(); + resources = null; + try { + pathTree.close(); + closed = true; + } finally { + writeLock.unlock(); + } + } +} diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java index c96b13143616a..388c6db0fda70 100644 --- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/devmode/DependenciesFilter.java @@ -1,7 +1,8 @@ package io.quarkus.bootstrap.devmode; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.maven.dependency.GACTV; +import io.quarkus.maven.dependency.ResolvedDependency; import java.util.ArrayList; import java.util.List; import org.jboss.logging.Logger; @@ -10,22 +11,29 @@ public class DependenciesFilter { private static final Logger log = Logger.getLogger(DependenciesFilter.class); - public static List getReloadableModules(ApplicationModel appModel) { - final List reloadable = new ArrayList<>(); + public static List getReloadableModules(ApplicationModel appModel) { + final List reloadable = new ArrayList<>(); if (appModel.getApplicationModule() != null) { - reloadable.add(appModel.getApplicationModule()); + reloadable.add(appModel.getAppArtifact()); } appModel.getDependencies().forEach(d -> { - final WorkspaceModule module = d.getWorkspaceModule(); - if (module != null) { - if (d.isReloadable()) { - reloadable.add(module); - } else { - //if this project also contains Quarkus extensions we do no want to include these in the discovery - //a bit of an edge case, but if you try and include a sample project with your extension you will - //run into problems without this - log.warn("Local Quarkus extension dependency " + module.getId() + " will not be hot-reloadable"); + if (d.isReloadable()) { + reloadable.add(d); + } else if (d.isWorkspaceModule()) { + //if this project also contains Quarkus extensions we do no want to include these in the discovery + //a bit of an edge case, but if you try and include a sample project with your extension you will + //run into problems without this + final StringBuilder msg = new StringBuilder(); + msg.append("Local Quarkus extension dependency "); + msg.append(d.getGroupId()).append(":").append(d.getArtifactId()).append(":"); + if (!d.getClassifier().isEmpty()) { + msg.append(d.getClassifier()).append(":"); } + if (!GACTV.TYPE_JAR.equals(d.getType())) { + msg.append(d.getType()).append(":"); + } + msg.append(d.getVersion()).append(" will not be hot-reloadable"); + log.warn(msg.toString()); } }); return reloadable; diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java index b573358a34c76..fd989688450f0 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java @@ -8,12 +8,11 @@ import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.resolver.maven.BuildDependencyGraphVisitor; import io.quarkus.bootstrap.resolver.maven.DeploymentInjectingDependencyVisitor; -import io.quarkus.bootstrap.resolver.maven.DeploymentInjectionException; import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.bootstrap.resolver.maven.SimpleDependencyGraphTransformationContext; -import io.quarkus.bootstrap.workspace.ProcessedSources; +import io.quarkus.bootstrap.workspace.ArtifactSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; -import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.ArtifactCoords; import io.quarkus.maven.dependency.ArtifactKey; import io.quarkus.maven.dependency.DependencyFlags; @@ -23,8 +22,6 @@ import io.quarkus.maven.dependency.ResolvedDependencyBuilder; import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathList; -import java.io.IOException; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -196,8 +193,6 @@ private ApplicationModel doResolveModel(ArtifactCoords coords, } final ResolvedDependency appArtifact = resolve(coords, mvnArtifact, managedRepos); - final boolean preferWorkspacePaths = !containsExtensionMetadata(appArtifact) && (devmode || test); - final ApplicationModelBuilder appBuilder = new ApplicationModelBuilder().setAppArtifact(appArtifact); if (appArtifact.getWorkspaceModule() != null) { appBuilder.addReloadableWorkspaceModule(new GACT(appArtifact.getGroupId(), appArtifact.getArtifactId())); @@ -234,7 +229,6 @@ private ApplicationModel doResolveModel(ArtifactCoords coords, final DeploymentInjectingDependencyVisitor deploymentInjector; try { deploymentInjector = new DeploymentInjectingDependencyVisitor(mvn, managedDeps, repos, appBuilder, - preferWorkspacePaths, collectReloadableDeps && reloadableModules.isEmpty()); deploymentInjector.injectDeploymentDependencies(resolvedDeps); } catch (BootstrapDependencyProcessingException e) { @@ -283,7 +277,7 @@ private ApplicationModel doResolveModel(ArtifactCoords coords, } } appBuilder.addDependency( - toAppArtifact(dep.getArtifact(), module, false) + toAppArtifact(dep.getArtifact(), module) .setScope(dep.getDependency().getScope()) .setFlags(flags).build()); } @@ -295,36 +289,6 @@ private ApplicationModel doResolveModel(ArtifactCoords coords, return appBuilder.build(); } - private static boolean containsExtensionMetadata(ResolvedDependency dep) { - if (!ArtifactCoords.TYPE_JAR.equals(dep.getType())) { - return false; - } - for (Path path : dep.getResolvedPaths()) { - if (!Files.exists(path)) { - continue; - } - if (Files.isDirectory(path)) { - if (containsExtensionMetadata(path)) { - return true; - } - } else { - try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) { - if (containsExtensionMetadata(artifactFs.getPath(""))) { - return true; - } - } catch (IOException e) { - throw new DeploymentInjectionException("Failed to read " + path, e); - } - } - } - return false; - } - - private static boolean containsExtensionMetadata(final Path path) { - return Files.exists(path.resolve(BootstrapConstants.BUILD_STEPS_PATH)) - || Files.exists(path.resolve(BootstrapConstants.DESCRIPTOR_PATH)); - } - private io.quarkus.maven.dependency.ResolvedDependency resolve(ArtifactCoords appArtifact, Artifact mvnArtifact, List managedRepos) throws BootstrapMavenException { @@ -344,26 +308,15 @@ private io.quarkus.maven.dependency.ResolvedDependency resolve(ArtifactCoords ap PathCollection resolvedPaths = null; if ((devmode || test) && resolvedModule != null) { - final PathList.Builder pathBuilder = PathList.builder(); - for (ProcessedSources src : resolvedModule.getMainSources()) { - if (src.getDestinationDir().exists()) { - final Path p = src.getDestinationDir().toPath(); - if (!pathBuilder.contains(p)) { - pathBuilder.add(p); - } - } - } - for (ProcessedSources src : resolvedModule.getMainResources()) { - if (src.getDestinationDir().exists()) { - final Path p = src.getDestinationDir().toPath(); - if (!pathBuilder.contains(p)) { - pathBuilder.add(p); - } + final ArtifactSources artifactSources = resolvedModule.getSources(appArtifact.getClassifier()); + if (artifactSources != null) { + final PathList.Builder pathBuilder = PathList.builder(); + collectSourceDirs(pathBuilder, artifactSources.getSourceDirs()); + collectSourceDirs(pathBuilder, artifactSources.getResourceDirs()); + if (!pathBuilder.isEmpty()) { + resolvedPaths = pathBuilder.build(); } } - if (!pathBuilder.isEmpty()) { - resolvedPaths = pathBuilder.build(); - } } if (resolvedPaths == null) { if (resolvedArtifact == null || resolvedArtifact.getResolvedPaths() == null) { @@ -376,6 +329,17 @@ private io.quarkus.maven.dependency.ResolvedDependency resolve(ArtifactCoords ap .setResolvedPaths(resolvedPaths).build(); } + private static void collectSourceDirs(final PathList.Builder pathBuilder, Collection resources) { + for (SourceDir src : resources) { + if (Files.exists(src.getOutputDir())) { + final Path p = src.getOutputDir(); + if (!pathBuilder.contains(p)) { + pathBuilder.add(p); + } + } + } + } + private void collectPlatformProperties(ApplicationModelBuilder appBuilder, List managedDeps) throws AppModelResolverException { final PlatformImportsImpl platformReleases = new PlatformImportsImpl(); @@ -504,11 +468,11 @@ private static Artifact toAetherArtifact(ArtifactCoords artifact) { } private ResolvedDependencyBuilder toAppArtifact(Artifact artifact) { - return toAppArtifact(artifact, null, false); + return toAppArtifact(artifact, null); } - private ResolvedDependencyBuilder toAppArtifact(Artifact artifact, WorkspaceModule module, boolean preferWorkspacePaths) { - return DeploymentInjectingDependencyVisitor.toAppArtifact(artifact, module, preferWorkspacePaths); + private ResolvedDependencyBuilder toAppArtifact(Artifact artifact, WorkspaceModule module) { + return DeploymentInjectingDependencyVisitor.toAppArtifact(artifact, module); } private static List toAetherDeps(Collection directDeps) { diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java index ac931a957e7a8..699cdfee1fa3b 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java @@ -7,7 +7,6 @@ import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.bootstrap.util.BootstrapUtils; import io.quarkus.bootstrap.util.DependencyNodeUtils; -import io.quarkus.bootstrap.workspace.ProcessedSources; import io.quarkus.bootstrap.workspace.WorkspaceModule; import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.ArtifactDependency; @@ -15,7 +14,6 @@ import io.quarkus.maven.dependency.DependencyFlags; import io.quarkus.maven.dependency.GACT; import io.quarkus.maven.dependency.ResolvedDependencyBuilder; -import io.quarkus.paths.PathCollection; import io.quarkus.paths.PathList; import java.io.BufferedReader; import java.io.IOException; @@ -68,7 +66,6 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { private final List managedDeps; private final List mainRepos; private final ApplicationModelBuilder appBuilder; - private final boolean preferWorkspacePaths; private final boolean collectReloadableModules; private boolean collectingTopExtensionRuntimeNodes = true; @@ -82,10 +79,9 @@ public static Artifact getRuntimeArtifact(DependencyNode dep) { public final Set allRuntimeDeps = new HashSet<>(); public DeploymentInjectingDependencyVisitor(MavenArtifactResolver resolver, List managedDeps, - List mainRepos, ApplicationModelBuilder appBuilder, boolean preferWorkspacePaths, + List mainRepos, ApplicationModelBuilder appBuilder, boolean collectReloadableModules) throws BootstrapDependencyProcessingException { - this.preferWorkspacePaths = preferWorkspacePaths; this.collectReloadableModules = collectReloadableModules; // we need to be able to take into account whether the deployment dependencies are on an optional dependency branch // for that we are going to use a custom dependency selector and re-initialize the resolver to use it @@ -197,13 +193,12 @@ private void visitRuntimeDependency(DependencyNode node) { module = resolver.getProjectModuleResolver().getProjectModule(artifact.getGroupId(), artifact.getArtifactId()); } - final ResolvedDependencyBuilder newRtDep = toAppArtifact(artifact, module, - preferWorkspacePaths && extDep == null && collectingTopExtensionRuntimeNodes) - .setRuntimeCp() - .setDeploymentCp() - .setOptional(node.getDependency().isOptional()) - .setScope(node.getDependency().getScope()) - .setDirect(collectingDirectDeps); + final ResolvedDependencyBuilder newRtDep = toAppArtifact(artifact, module) + .setRuntimeCp() + .setDeploymentCp() + .setOptional(node.getDependency().isOptional()) + .setScope(node.getDependency().getScope()) + .setDirect(collectingDirectDeps); if (module != null) { newRtDep.setWorkspaceModule().setReloadable(); if (collectReloadableModules) { @@ -363,7 +358,7 @@ private void clearReloadable(DependencyNode node) { clearReloadable(child); } final io.quarkus.maven.dependency.Dependency dep = appBuilder.getDependency(getKey(node.getArtifact())); - if (dep != null && dep.isWorkspacetModule()) { + if (dep != null && dep.isWorkspaceModule()) { ((ArtifactDependency) dep).clearFlag(DependencyFlags.RELOADABLE); } } @@ -625,8 +620,7 @@ public static GACT getKey(Artifact a) { return new GACT(a.getGroupId(), a.getArtifactId(), a.getClassifier(), a.getExtension()); } - public static ResolvedDependencyBuilder toAppArtifact(Artifact artifact, WorkspaceModule module, - boolean preferWorkspacePaths) { + public static ResolvedDependencyBuilder toAppArtifact(Artifact artifact, WorkspaceModule module) { return ResolvedDependencyBuilder.newInstance() .setWorkspaceModule(module) .setGroupId(artifact.getGroupId()) @@ -634,35 +628,7 @@ public static ResolvedDependencyBuilder toAppArtifact(Artifact artifact, Workspa .setClassifier(artifact.getClassifier()) .setType(artifact.getExtension()) .setVersion(artifact.getVersion()) - .setResolvedPaths(getResolvedPaths(artifact, module, preferWorkspacePaths)); - } - - public static PathCollection getResolvedPaths(Artifact artifact, WorkspaceModule module, boolean preferWorkspacePaths) { - if (preferWorkspacePaths && module != null) { - final PathList.Builder pathBuilder = PathList.builder(); - if ("tests".equals(artifact.getClassifier())) { - collectResolvedPaths(pathBuilder, module.getTestSources()); - collectResolvedPaths(pathBuilder, module.getTestResources()); - } else { - collectResolvedPaths(pathBuilder, module.getMainSources()); - collectResolvedPaths(pathBuilder, module.getMainResources()); - } - if (!pathBuilder.isEmpty()) { - return pathBuilder.build(); - } - } - return artifact.getFile() == null ? PathList.empty() : PathList.of(artifact.getFile().toPath()); - } - - private static void collectResolvedPaths(final PathList.Builder pathBuilder, Collection srcs) { - for (ProcessedSources src : srcs) { - if (src.getDestinationDir().exists()) { - final Path p = src.getDestinationDir().toPath(); - if (!pathBuilder.contains(p)) { - pathBuilder.add(p); - } - } - } + .setResolvedPaths(artifact.getFile() == null ? PathList.empty() : PathList.of(artifact.getFile().toPath())); } private static String toGactv(Artifact a) { diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java index 18030bb14e296..e70e1f01373c1 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java @@ -2,13 +2,17 @@ import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppArtifactKey; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; -import io.quarkus.bootstrap.workspace.DefaultProcessedSources; +import io.quarkus.bootstrap.workspace.DefaultArtifactSources; +import io.quarkus.bootstrap.workspace.DefaultSourceDir; import io.quarkus.bootstrap.workspace.DefaultWorkspaceModule; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.maven.dependency.GACTV; import io.quarkus.maven.dependency.GAV; +import io.quarkus.paths.DirectoryPathTree; +import io.quarkus.paths.PathCollection; +import io.quarkus.paths.PathFilter; import io.quarkus.paths.PathList; import java.io.IOException; import java.nio.file.Files; @@ -23,8 +27,11 @@ import org.apache.maven.model.Build; 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.Resource; import org.apache.maven.model.building.ModelBuildingResult; +import org.codehaus.plexus.util.xml.Xpp3Dom; /** * @@ -128,6 +135,7 @@ static Path locateCurrentProjectPom(Path path, boolean required) throws Bootstra final List modules = new ArrayList<>(0); private AppArtifactKey key; private final ModelBuildingResult modelBuildingResult; + private WorkspaceModule module; LocalProject(ModelBuildingResult modelBuildingResult, LocalWorkspace workspace) { this.rawModel = modelBuildingResult.getRawModel(); @@ -237,25 +245,25 @@ public Path getSourcesDir() { return getSourcesSourcesDir().getParent(); } - public PathsCollection getResourcesSourcesDirs() { + public PathCollection getResourcesSourcesDirs() { final List resources = rawModel.getBuild() == null ? Collections.emptyList() : rawModel.getBuild().getResources(); if (resources.isEmpty()) { - return PathsCollection.of(resolveRelativeToBaseDir(null, "src/main/resources")); + return PathList.of(resolveRelativeToBaseDir(null, "src/main/resources")); } - return PathsCollection.from(resources.stream() + return PathList.from(resources.stream() .map(Resource::getDirectory) .map(resourcesDir -> resolveRelativeToBaseDir(resourcesDir, "src/main/resources")) .collect(Collectors.toCollection(LinkedHashSet::new))); } - public PathsCollection getTestResourcesSourcesDirs() { + public PathCollection getTestResourcesSourcesDirs() { final List resources = rawModel.getBuild() == null ? Collections.emptyList() : rawModel.getBuild().getTestResources(); if (resources.isEmpty()) { - return PathsCollection.of(resolveRelativeToBaseDir(null, "src/test/resources")); + return PathList.of(resolveRelativeToBaseDir(null, "src/test/resources")); } - return PathsCollection.from(resources.stream() + return PathList.from(resources.stream() .map(Resource::getDirectory) .map(resourcesDir -> resolveRelativeToBaseDir(resourcesDir, "src/test/resources")) .collect(Collectors.toCollection(LinkedHashSet::new))); @@ -317,48 +325,135 @@ private static String configuredBuildDir(LocalProject project, Function list = new ArrayList<>(includeElements.length); + for (Xpp3Dom include : includeElements) { + list.add(include.getValue()); + } + filter = PathFilter.forIncludes(list); + } else { + final Xpp3Dom excludes = dom.getChild("excludes"); + if (excludes != null) { + final Xpp3Dom[] excludeElements = excludes.getChildren(); + final List list = new ArrayList<>(excludeElements.length); + for (Xpp3Dom exclude : excludes.getChildren()) { + list.add(exclude.getValue()); + } + filter = PathFilter.forExcludes(list); + } + } + if (filter != null) { + final String classifier = getClassifier(dom); + final DefaultArtifactSources src = new DefaultArtifactSources(classifier); + src.addSources( + new DefaultSourceDir(new DirectoryPathTree(test ? getTestSourcesSourcesDir() : getSourcesSourcesDir()), + new DirectoryPathTree(test ? getTestClassesDir() : getClassesDir(), filter), + Collections.emptyMap())); + if (test) { + addTestResources(src, filter); + } else { + addMainResources(src, filter); + } + return src; + } + return null; + } + + private static String getClassifier(Xpp3Dom dom) { + final Xpp3Dom classifier = dom.getChild("classifier"); + return classifier == null ? "" : classifier.getValue(); } - private void addMainResources(DefaultWorkspaceModule module) { + private void addMainResources(DefaultArtifactSources module, PathFilter filter) { final List resources = rawModel.getBuild() == null ? Collections.emptyList() : rawModel.getBuild().getResources(); if (resources.isEmpty()) { - module.addMainResources(new DefaultProcessedSources( - resolveRelativeToBaseDir(null, "src/main/resources").toFile(), getClassesDir().toFile())); + module.addResources(new DefaultSourceDir( + new DirectoryPathTree(resolveRelativeToBaseDir(null, "src/main/resources")), + new DirectoryPathTree(getClassesDir(), filter), Collections.emptyMap())); } else { for (Resource r : resources) { - module.addMainResources( - new DefaultProcessedSources(resolveRelativeToBaseDir(r.getDirectory(), "src/main/resources").toFile(), - (r.getTargetPath() == null ? getClassesDir() + module.addResources( + new DefaultSourceDir( + new DirectoryPathTree(resolveRelativeToBaseDir(r.getDirectory(), "src/main/resources")), + new DirectoryPathTree((r.getTargetPath() == null ? getClassesDir() : getClassesDir() - .resolve(stripProjectBasedirPrefix(r.getTargetPath(), PROJECT_OUTPUT_DIR))) - .toFile())); + .resolve(stripProjectBasedirPrefix(r.getTargetPath(), PROJECT_OUTPUT_DIR))), + filter), + Collections.emptyMap())); } } } - private void addTestResources(DefaultWorkspaceModule module) { + private void addTestResources(DefaultArtifactSources module, PathFilter filter) { final List resources = rawModel.getBuild() == null ? Collections.emptyList() : rawModel.getBuild().getTestResources(); if (resources.isEmpty()) { - module.addTestResources(new DefaultProcessedSources( - resolveRelativeToBaseDir(null, "src/test/resources").toFile(), getTestClassesDir().toFile())); + module.addResources(new DefaultSourceDir( + new DirectoryPathTree(resolveRelativeToBaseDir(null, "src/test/resources")), + new DirectoryPathTree(getTestClassesDir(), filter), Collections.emptyMap())); } else { for (Resource r : resources) { - module.addTestResources( - new DefaultProcessedSources(resolveRelativeToBaseDir(r.getDirectory(), "src/test/resources").toFile(), - (r.getTargetPath() == null ? getTestClassesDir() + module.addResources( + new DefaultSourceDir( + new DirectoryPathTree(resolveRelativeToBaseDir(r.getDirectory(), "src/test/resources")), + new DirectoryPathTree((r.getTargetPath() == null ? getTestClassesDir() : getTestClassesDir() - .resolve(stripProjectBasedirPrefix(r.getTargetPath(), PROJECT_OUTPUT_DIR))) - .toFile())); + .resolve(stripProjectBasedirPrefix(r.getTargetPath(), PROJECT_OUTPUT_DIR))), + filter), + Collections.emptyMap())); } } } diff --git a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java index 0e23ded7abfc1..937e1adfc08f1 100644 --- a/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java +++ b/independent-projects/bootstrap/maven-resolver/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java @@ -1,10 +1,10 @@ package io.quarkus.bootstrap.resolver.maven.workspace; -import io.quarkus.bootstrap.model.AppArtifactCoords; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext; import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.maven.dependency.ArtifactCoords; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -104,51 +104,48 @@ public File findArtifact(Artifact artifact) { && lp.getVersion().equals(resolvedVersion))) { return null; } + + if (ArtifactCoords.TYPE_POM.equals(artifact.getExtension())) { + final File pom = lp.getRawModel().getPomFile(); + // if the pom exists we should also check whether the main artifact can also be resolved from the workspace + if (pom.exists() && ("pom".equals(lp.getRawModel().getPackaging()) + || mvnCtx != null && mvnCtx.isPreferPomsFromWorkspace() + || Files.exists(lp.getOutputDir()) + || emptyJarOutput(lp, artifact) != null)) { + return pom; + } + } + + // Check whether the artifact exists in the project's output dir. + // It could also be a project with no sources/resources, in which case Maven will create an empty JAR + // if it has previously been packaged we can use it + Path path = lp.getOutputDir().resolve(getFileName(artifact)); + if (Files.exists(path)) { + return path.toFile(); + } + if (!Objects.equals(artifact.getClassifier(), lp.getAppArtifact().getClassifier())) { if ("tests".equals(artifact.getClassifier())) { //special classifier used for test jars - final Path path = lp.getTestClassesDir(); + path = lp.getTestClassesDir(); if (Files.exists(path)) { return path.toFile(); } } + // otherwise, this artifact hasn't been built yet return null; } - final String type = artifact.getExtension(); - if (type.equals(AppArtifactCoords.TYPE_JAR)) { - Path path = lp.getClassesDir(); - if (Files.exists(path)) { - return path.toFile(); - } - // it could be a project with no sources/resources, in which case Maven will create an empty JAR - // if it has previously been packaged we can return it - path = lp.getOutputDir().resolve(getFileName(artifact)); + if (ArtifactCoords.TYPE_JAR.equals(artifact.getExtension())) { + path = lp.getClassesDir(); if (Files.exists(path)) { return path.toFile(); } - path = emptyJarOutput(lp, artifact); if (path != null) { return path.toFile(); } - // otherwise, this project hasn't been built yet - } else if (type.equals(AppArtifactCoords.TYPE_POM)) { - final File pom = lp.getRawModel().getPomFile(); - // if the pom exists we should also check whether the main artifact can also be resolved from the workspace - if (pom.exists() && ("pom".equals(lp.getRawModel().getPackaging()) - || mvnCtx != null && mvnCtx.isPreferPomsFromWorkspace() - || Files.exists(lp.getOutputDir()) - || emptyJarOutput(lp, artifact) != null)) { - return pom; - } - } else { - // check whether the artifact exists in the project's output dir - final Path path = lp.getOutputDir().resolve(getFileName(artifact)); - if (Files.exists(path)) { - return path.toFile(); - } } return null; } @@ -162,7 +159,7 @@ private Path emptyJarOutput(LocalProject lp, Artifact artifact) { // so the Maven resolver will succeed resolving it from the repo. // If the artifact does not exist in the local repo, we are creating an empty classes directory in the target directory. if (!Files.exists(lp.getSourcesSourcesDir()) - && lp.getResourcesSourcesDirs().toList().stream().noneMatch(Files::exists) + && lp.getResourcesSourcesDirs().stream().noneMatch(Files::exists) && !isFoundInLocalRepo(artifact)) { try { final Path classesDir = lp.getClassesDir(); diff --git a/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java b/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java index e3d3045021bdd..fd2dc1a8504e9 100644 --- a/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java +++ b/independent-projects/bootstrap/maven-resolver/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java @@ -14,8 +14,9 @@ import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace; import io.quarkus.bootstrap.util.IoUtils; -import io.quarkus.bootstrap.workspace.ProcessedSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.paths.PathTree; import java.io.File; import java.net.URL; import java.nio.file.Files; @@ -577,12 +578,13 @@ private void testMavenCiFriendlyVersion(String placeholder, String testResourceD final WorkspaceModule wsModule = module1.toWorkspaceModule(); Assertions.assertThat(wsModule.getModuleDir()).isEqualTo(module1Dir.toFile()); Assertions.assertThat(wsModule.getBuildDir()).isEqualTo(module1Dir.resolve("target").toFile()); - Collection c = wsModule.getMainResources(); - Assertions.assertThat(c).hasSize(1); - final ProcessedSources src = c.iterator().next(); - Assertions.assertThat(src.getSourceDir()).isEqualTo(module1Dir.resolve("build").toFile()); - Assertions.assertThat(src.getDestinationDir()) - .isEqualTo(module1Dir.resolve("target/classes/META-INF/resources").toFile()); + SourceDir src = wsModule.getMainSources().getResourceDirs().iterator().next(); + PathTree sourceTree = src.getSourceTree(); + Assertions.assertThat(sourceTree).isNotNull(); + Collection roots = sourceTree.getRoots(); + Assertions.assertThat(roots).hasSize(1); + Assertions.assertThat(roots.iterator().next()).isEqualTo(module1Dir.resolve("build")); + Assertions.assertThat(src.getOutputDir()).isEqualTo(module1Dir.resolve("target/classes/META-INF/resources")); } private void assertCompleteWorkspace(final LocalProject project) { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java index aa30735417aa6..5b2e2e81559c6 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java @@ -19,8 +19,9 @@ import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.resolver.QuarkusGradleModelFactory; -import io.quarkus.bootstrap.workspace.ProcessedSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.paths.PathTree; class QuarkusModelBuilderTest { @@ -57,29 +58,40 @@ private void assertProjectModule(WorkspaceModule projectModule, File projectDir, assertEquals(projectDir, projectModule.getModuleDir()); assertEquals(new File(projectDir, "build"), projectModule.getBuildDir()); - ProcessedSources src = projectModule.getMainSources().iterator().next(); + SourceDir src = projectModule.getMainSources().getSourceDirs().iterator().next(); assertNotNull(src); - assertThat(src.getDestinationDir()).isEqualTo(new File(projectDir, "build/classes/java/main")); - assertThat(src.getSourceDir()).isEqualTo(new File(projectDir, "src/main/java")); + assertThat(src.getOutputDir()).isEqualTo(projectDir.toPath().resolve("build/classes/java/main")); + PathTree sourceTree = src.getSourceTree(); + assertThat(sourceTree).isNotNull(); + assertThat(sourceTree.getRoots()).hasSize(1); + assertThat(sourceTree.getRoots().iterator().next()).isEqualTo(projectDir.toPath().resolve("src/main/java")); - src = projectModule.getMainResources().iterator().next(); + src = projectModule.getMainSources().getResourceDirs().iterator().next(); assertNotNull(src); - assertThat(src.getDestinationDir()).isEqualTo(new File(projectDir, "build/resources/main")); - assertThat(src.getSourceDir()).isEqualTo(new File(projectDir, "src/main/resources")); + assertThat(src.getOutputDir()).isEqualTo(projectDir.toPath().resolve("build/resources/main")); + sourceTree = src.getSourceTree(); + assertThat(sourceTree).isNotNull(); + assertThat(sourceTree.getRoots()).hasSize(1); + assertThat(sourceTree.getRoots().iterator().next()).isEqualTo(projectDir.toPath().resolve("src/main/resources")); if (withTests) { - src = projectModule.getTestSources().iterator().next(); + src = projectModule.getTestSources().getSourceDirs().iterator().next(); assertNotNull(src); - assertThat(src.getDestinationDir()).isEqualTo(new File(projectDir, "build/classes/java/test")); - assertThat(src.getSourceDir()).isEqualTo(new File(projectDir, "src/test/java")); + assertThat(src.getOutputDir()).isEqualTo(projectDir.toPath().resolve("build/classes/java/test")); + sourceTree = src.getSourceTree(); + assertThat(sourceTree).isNotNull(); + assertThat(sourceTree.getRoots()).hasSize(1); + assertThat(sourceTree.getRoots().iterator().next()).isEqualTo(projectDir.toPath().resolve("src/test/java")); - src = projectModule.getTestResources().iterator().next(); + src = projectModule.getTestSources().getResourceDirs().iterator().next(); assertNotNull(src); - assertThat(src.getDestinationDir()).isEqualTo(new File(projectDir, "build/resources/test")); - assertThat(src.getSourceDir()).isEqualTo(new File(projectDir, "src/test/resources")); + assertThat(src.getOutputDir()).isEqualTo(projectDir.toPath().resolve("build/resources/test")); + sourceTree = src.getSourceTree(); + assertThat(sourceTree).isNotNull(); + assertThat(sourceTree.getRoots()).hasSize(1); + assertThat(sourceTree.getRoots().iterator().next()).isEqualTo(projectDir.toPath().resolve("src/test/resources")); } else { - assertThat(projectModule.getTestSources()).isEmpty(); - assertThat(projectModule.getTestResources()).isEmpty(); + assertThat(projectModule.getTestSources()).isNull(); } } diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java index 6e673fe0bd668..b2b5bda86106d 100644 --- a/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java @@ -1183,4 +1183,33 @@ public void testPropertyExpansion() throws IOException, MavenInvocationException assertThat(DevModeTestUtils.getHttpResponse("/app/hello/")).isEqualTo("hello"); assertThat(DevModeTestUtils.getHttpResponse("/app/hello/applicationName")).isEqualTo("myapp"); } + + @Test + public void testMultiJarModuleDevMode() throws MavenInvocationException, IOException { + testDir = initProject("projects/multijar-module"); + run(false, "clean", "package", "-DskipTests"); + + String greeting = DevModeTestUtils.getHttpResponse("/hello"); + assertThat(greeting).contains("acme other TestBean"); + + // Update AcmeBean + File resource = new File(testDir, "beans/src/main/java/org/acme/AcmeBean.java"); + filter(resource, Collections.singletonMap("return \"acme\";", "return \"acme!\";")); + + // Wait until we get "uuid" + await() + .pollDelay(100, TimeUnit.MILLISECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> DevModeTestUtils.getHttpResponse("/hello").contains("acme! other TestBean")); + + // Update Other bean + resource = new File(testDir, "beans/src/main/java/org/acme/Other.java"); + filter(resource, Collections.singletonMap("return \"other\";", "return \"other!\";")); + + // Wait until we get "uuid" + await() + .pollDelay(300, TimeUnit.MILLISECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> DevModeTestUtils.getHttpResponse("/hello").contains("acme! other! TestBean")); + } } diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/pom.xml b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/pom.xml new file mode 100644 index 0000000000000..e3df7d77f4a95 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + org.acme + acme-parent + 1.0-SNAPSHOT + + + acme-beans + + + + io.quarkus + quarkus-arc + + + + + + + maven-jar-plugin + 3.2.0 + + + default-jar + + jar + + + + org/acme/Other.class + + + + + other-jar + + jar + + + other + + META-INF/beans.xml + org/acme/Other.class + + + + + mocks-jar + + test-jar + + + mocks + + META-INF/beans.xml + org/acme/TestBean.class + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/AcmeBean.java b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/AcmeBean.java new file mode 100644 index 0000000000000..584659014dabb --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/AcmeBean.java @@ -0,0 +1,12 @@ +package org.acme; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class AcmeBean implements Named { + + @Override + public String getName() { + return "acme"; + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Named.java b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Named.java new file mode 100644 index 0000000000000..0d5f529f08a83 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Named.java @@ -0,0 +1,6 @@ +package org.acme; + +public interface Named { + + String getName(); +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Other.java b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Other.java new file mode 100644 index 0000000000000..ada2f2240fe2b --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/java/org/acme/Other.java @@ -0,0 +1,12 @@ +package org.acme; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class Other implements Named { + + @Override + public String getName() { + return "other"; + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/resources/META-INF/beans.xml b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/java/org/acme/TestBean.java b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/java/org/acme/TestBean.java new file mode 100644 index 0000000000000..c2751c9699355 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/java/org/acme/TestBean.java @@ -0,0 +1,12 @@ +package org.acme; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TestBean { + + @Override + public String toString() { + return "TestBean"; + } +} \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/resources/META-INF/beans.xml b/integration-tests/maven/src/test/resources/projects/multijar-module/beans/src/test/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/pom.xml b/integration-tests/maven/src/test/resources/projects/multijar-module/pom.xml new file mode 100644 index 0000000000000..54e7a2d7c4685 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + org.acme + acme-parent + 1.0-SNAPSHOT + pom + + + io.quarkus + quarkus-bom + @project.version@ + 3.0.0-M5 + UTF-8 + 11 + 11 + + + beans + runner + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + + + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + org.acme + acme-beans + 1.0-SNAPSHOT + + + org.acme + acme-beans + other + 1.0-SNAPSHOT + + + org.acme + acme-beans + mocks + 1.0-SNAPSHOT + + + org.acme + acme-runner + 1.0-SNAPSHOT + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/runner/pom.xml b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/pom.xml new file mode 100644 index 0000000000000..e546fbb3b7a67 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + org.acme + acme-parent + 1.0-SNAPSHOT + + acme-runner + + + + io.quarkus + quarkus-resteasy + + + org.acme + acme-beans + + + org.acme + acme-beans + other + + + org.acme + acme-beans + mocks + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + + + + build + + + + + + + + + + + native + + + native + + + + native + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${skipTests} + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/java/org/acme/HelloResource.java b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..b853f62b462d0 --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,31 @@ +package org.acme; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; + +@Path("/hello") +public class HelloResource { + + @Inject + AcmeBean acme; + + @Inject + Other other; + + @Inject + TestBean test; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return acme.getName() + " " + other.getName() + " " + test; + } +} diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/resources/application.properties b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/resources/application.properties new file mode 100644 index 0000000000000..32455f36cde2a --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/main/resources/application.properties @@ -0,0 +1,3 @@ +#quarkus.test.continuous-testing=enabled +#quarkus.test.basic-console=true +quarkus.live-reload.instrumentation=false \ No newline at end of file diff --git a/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/test/java/com/acme/ResourceTest.java b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/test/java/com/acme/ResourceTest.java new file mode 100644 index 0000000000000..9c5fe65d23f2d --- /dev/null +++ b/integration-tests/maven/src/test/resources/projects/multijar-module/runner/src/test/java/com/acme/ResourceTest.java @@ -0,0 +1,23 @@ +package com.acme; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ResourceTest { + + @Test + public void testHelloEndpoint() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(is("acme other TestBean")); + } +} diff --git a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java index 6bdd6abf9b20b..631df156e1785 100644 --- a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java +++ b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.HashSet; @@ -17,8 +18,7 @@ import io.quarkus.bootstrap.model.ApplicationModel; import io.quarkus.bootstrap.utils.BuildToolHelper; -import io.quarkus.bootstrap.workspace.ProcessedSources; -import io.quarkus.bootstrap.workspace.WorkspaceModule; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.IsTest; import io.quarkus.deployment.annotations.BuildProducer; @@ -118,11 +118,11 @@ public byte[] apply(String className, byte[] bytes) { } if (model.getApplicationModule() != null) { - addProjectModule(model.getApplicationModule(), config, info, includes, excludes, classes, sources); + addProjectModule(model.getAppArtifact(), config, info, includes, excludes, classes, sources); } for (ResolvedDependency d : model.getDependencies()) { - if (d.isRuntimeCp() && d.isWorkspacetModule()) { - addProjectModule(d.getWorkspaceModule(), config, info, includes, excludes, classes, sources); + if (d.isRuntimeCp() && d.isWorkspaceModule()) { + addProjectModule(d, config, info, includes, excludes, classes, sources); } } @@ -132,13 +132,15 @@ public byte[] apply(String className, byte[] bytes) { } } - private void addProjectModule(WorkspaceModule module, JacocoConfig config, ReportInfo info, String includes, + private void addProjectModule(ResolvedDependency module, JacocoConfig config, ReportInfo info, String includes, String excludes, Set classes, Set sources) throws Exception { - info.savedData.add(new File(module.getBuildDir(), config.dataFile).getAbsolutePath()); - for (ProcessedSources src : module.getMainSources()) { - sources.add(src.getSourceDir().getAbsolutePath()); - if (src.getDestinationDir().isDirectory()) { - for (final File file : FileUtils.getFiles(src.getDestinationDir(), includes, excludes, + info.savedData.add(new File(module.getWorkspaceModule().getBuildDir(), config.dataFile).getAbsolutePath()); + for (SourceDir src : module.getSources().getSourceDirs()) { + for (Path p : src.getSourceTree().getRoots()) { + sources.add(p.toAbsolutePath().toString()); + } + if (Files.isDirectory(src.getOutputDir())) { + for (final File file : FileUtils.getFiles(src.getOutputDir().toFile(), includes, excludes, true)) { if (file.getName().endsWith(".class")) { classes.add(file.getAbsolutePath()); diff --git a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java index 0a24231b861a8..9d8bdcbb801d6 100644 --- a/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java +++ b/test-framework/junit5-internal/src/main/java/io/quarkus/test/QuarkusDevModeTest.java @@ -48,7 +48,6 @@ import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstantiationException; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.deployment.dev.CompilationProvider; import io.quarkus.deployment.dev.DevModeContext; import io.quarkus.deployment.dev.DevModeMain; @@ -57,6 +56,7 @@ import io.quarkus.dev.testing.TestScanningLock; import io.quarkus.fs.util.ZipUtils; import io.quarkus.maven.dependency.GACT; +import io.quarkus.paths.PathList; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.configuration.ProfileManager; import io.quarkus.runtime.util.ClassPathUtils; @@ -394,11 +394,11 @@ private DevModeContext exportArchive(Path deploymentDir, Path testSourceDir, Pat .setArtifactKey(GACT.fromString("io.quarkus.test:app-under-test")) .setName("default") .setProjectDirectory(deploymentDir.toAbsolutePath().toString()) - .setSourcePaths(PathsCollection.of(deploymentSourcePath.toAbsolutePath())) + .setSourcePaths(PathList.of(deploymentSourcePath.toAbsolutePath())) .setClassesPath(classes.toAbsolutePath().toString()) - .setResourcePaths(PathsCollection.of(deploymentResourcePath.toAbsolutePath())) + .setResourcePaths(PathList.of(deploymentResourcePath.toAbsolutePath())) .setResourcesOutputPath(classes.toAbsolutePath().toString()) - .setSourceParents(PathsCollection.of(deploymentSourceParentPath.toAbsolutePath())) + .setSourceParents(PathList.of(deploymentSourceParentPath.toAbsolutePath())) .setPreBuildOutputDir(targetDir.resolve("generated-sources").toAbsolutePath().toString()) .setTargetDir(targetDir.toAbsolutePath().toString()); @@ -443,9 +443,9 @@ private DevModeContext exportArchive(Path deploymentDir, Path testSourceDir, Pat }); } moduleBuilder - .setTestSourcePaths(PathsCollection.of(deploymentTestSourcePath.toAbsolutePath())) + .setTestSourcePaths(PathList.of(deploymentTestSourcePath.toAbsolutePath())) .setTestClassesPath(testClasses.toAbsolutePath().toString()) - .setTestResourcePaths(PathsCollection.of(deploymentTestResourcePath.toAbsolutePath())) + .setTestResourcePaths(PathList.of(deploymentTestResourcePath.toAbsolutePath())) .setTestResourcesOutputPath(testClasses.toAbsolutePath().toString()); } @@ -834,7 +834,7 @@ private void copyFromSource(Path projectSourcesDir, Path deploymentSourcesDir, P private Path copySourceFilesForClass(Path projectSourcesDir, Path deploymentSourcesDir, Path classesDir, Path classFile) { for (CompilationProvider provider : compilationProviders) { Path source = provider.getSourcePath(classFile, - PathsCollection.of(projectSourcesDir.toAbsolutePath()), + PathList.of(projectSourcesDir.toAbsolutePath()), classesDir.toAbsolutePath().toString()); if (source != null) { String relative = projectSourcesDir.relativize(source).toString(); @@ -856,7 +856,7 @@ private Path findTargetSourceFilesForPath(Path projectSourcesDir, Path deploymen Path classFile) { for (CompilationProvider provider : compilationProviders) { Path source = provider.getSourcePath(classFile, - PathsCollection.of(projectSourcesDir.toAbsolutePath()), + PathList.of(projectSourcesDir.toAbsolutePath()), classesDir.toAbsolutePath().toString()); if (source != null) { String relative = projectSourcesDir.relativize(source).toString(); diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java index b29378e583965..89756decef6d5 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java @@ -24,10 +24,12 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.runner.Timing; import io.quarkus.bootstrap.utils.BuildToolHelper; +import io.quarkus.bootstrap.workspace.ArtifactSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.deployment.dev.testing.CurrentTestApplication; +import io.quarkus.paths.PathList; import io.quarkus.runtime.configuration.ProfileManager; import io.quarkus.test.common.PathTestHelper; import io.quarkus.test.common.QuarkusTestResource; @@ -55,7 +57,7 @@ protected PrepareResult createAugmentor(ExtensionContext context, Class { - if (src.getDestinationDir().exists()) { - final Path classesDir = src.getDestinationDir().toPath(); - if (!rootBuilder.contains(classesDir)) { - rootBuilder.add(classesDir); + final ArtifactSources artifactSrc = model.getApplicationModule().getTestSources(); + if (artifactSrc != null) { + for (SourceDir src : artifactSrc.getSourceDirs()) { + if (Files.exists(src.getOutputDir())) { + final Path classesDir = src.getOutputDir(); + if (!rootBuilder.contains(classesDir)) { + rootBuilder.add(classesDir); + } } } - }); - model.getApplicationModule().getMainSources().forEach(src -> { - if (src.getDestinationDir().exists()) { - final Path classesDir = src.getDestinationDir().toPath(); + } + for (SourceDir src : model.getApplicationModule().getMainSources().getSourceDirs()) { + if (Files.exists(src.getOutputDir())) { + final Path classesDir = src.getOutputDir(); if (!rootBuilder.contains(classesDir)) { rootBuilder.add(classesDir); } } - }); + } } } else if (System.getProperty(BootstrapConstants.OUTPUT_SOURCES_DIR) != null) { final String[] sourceDirectories = System.getProperty(BootstrapConstants.OUTPUT_SOURCES_DIR).split(","); diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java index b253d9d039cdb..b7249b5b111eb 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/IntegrationTestUtil.java @@ -42,9 +42,11 @@ import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; import io.quarkus.bootstrap.model.ApplicationModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.utils.BuildToolHelper; +import io.quarkus.bootstrap.workspace.ArtifactSources; +import io.quarkus.bootstrap.workspace.SourceDir; import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem; +import io.quarkus.paths.PathList; import io.quarkus.runtime.configuration.ProfileManager; import io.quarkus.runtime.logging.LoggingSetupRecorder; import io.quarkus.test.common.ArtifactLauncher; @@ -199,7 +201,7 @@ static ArtifactLauncher.InitContext.DevServicesLaunchResult handleDevServices(Ex Path testClassLocation = getTestClassesLocation(requiredTestClass); final Path appClassLocation = getAppClassLocationForTestLocation(testClassLocation.toString()); - PathsCollection.Builder rootBuilder = PathsCollection.builder(); + final PathList.Builder rootBuilder = PathList.builder(); if (!appClassLocation.equals(testClassLocation)) { rootBuilder.add(testClassLocation); @@ -213,7 +215,6 @@ static ArtifactLauncher.InitContext.DevServicesLaunchResult handleDevServices(Ex final QuarkusBootstrap.Builder runnerBuilder = QuarkusBootstrap.builder() .setIsolateDeployment(true) .setMode(QuarkusBootstrap.Mode.TEST); - QuarkusTestProfile profileInstance = null; final Path projectRoot = Paths.get("").normalize().toAbsolutePath(); runnerBuilder.setProjectRoot(projectRoot); @@ -237,22 +238,25 @@ static ArtifactLauncher.InitContext.DevServicesLaunchResult handleDevServices(Ex if (System.getProperty(BootstrapConstants.SERIALIZED_TEST_APP_MODEL) == null) { ApplicationModel model = BuildToolHelper.enableGradleAppModelForTest(projectRoot); if (model != null && model.getApplicationModule() != null) { - model.getApplicationModule().getTestSources().forEach(src -> { - if (!src.getDestinationDir().exists()) { - final Path classes = src.getDestinationDir().toPath(); - if (!rootBuilder.contains(classes)) { - rootBuilder.add(classes); + final ArtifactSources testSources = model.getApplicationModule().getTestSources(); + if (testSources != null) { + for (SourceDir src : testSources.getSourceDirs()) { + if (!Files.exists(src.getOutputDir())) { + final Path classes = src.getOutputDir(); + if (!rootBuilder.contains(classes)) { + rootBuilder.add(classes); + } } } - }); - model.getApplicationModule().getMainSources().forEach(src -> { - if (!src.getDestinationDir().exists()) { - final Path classes = src.getDestinationDir().toPath(); + } + for (SourceDir src : model.getApplicationModule().getMainSources().getSourceDirs()) { + if (!Files.exists(src.getOutputDir())) { + final Path classes = src.getOutputDir(); if (!rootBuilder.contains(classes)) { rootBuilder.add(classes); } } - }); + } } } else if (System.getProperty(BootstrapConstants.OUTPUT_SOURCES_DIR) != null) { final String[] sourceDirectories = System.getProperty(BootstrapConstants.OUTPUT_SOURCES_DIR).split(",");