diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 47074ffe492b2..9449d62396e07 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -4408,6 +4408,11 @@ quarkus-bootstrap-maven-resolver ${project.version} + + io.quarkus + quarkus-bootstrap-gradle-resolver + ${project.version} + io.quarkus quarkus-bootstrap-core 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 803406b18dd8f..154e76d3cf1bb 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 @@ -1,16 +1,25 @@ package io.quarkus.deployment.dev; +import java.io.File; import java.nio.file.Path; import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.function.BiConsumer; import org.jboss.logging.Logger; +import io.quarkus.bootstrap.BootstrapGradleException; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.model.AppArtifactKey; -import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.resolver.QuarkusGradleModelFactory; import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; +import io.quarkus.bootstrap.util.QuarkusModelHelper; +import io.quarkus.bootstrap.utils.BuildToolHelper; @SuppressWarnings("unused") public class IDEDevModeMain implements BiConsumer> { @@ -23,16 +32,32 @@ public void accept(CuratedApplication curatedApplication, Map st DevModeContext devModeContext = new DevModeContext(); devModeContext.setArgs((String[]) stringObjectMap.get("args")); try { - LocalProject project = LocalProject.loadWorkspace(appClasses); - DevModeContext.ModuleInfo root = toModule(project); - devModeContext.setApplicationRoot(root); - for (Map.Entry module : project.getWorkspace().getProjects().entrySet()) { - if (module.getKey().equals(project.getKey())) { - continue; + if (BuildToolHelper.isMavenProject(appClasses)) { + LocalProject project = LocalProject.loadWorkspace(appClasses); + DevModeContext.ModuleInfo root = toModule(project); + devModeContext.setApplicationRoot(root); + for (Map.Entry module : project.getWorkspace().getProjects().entrySet()) { + if (module.getKey().equals(project.getKey())) { + continue; + } + devModeContext.getAdditionalModules().add(toModule(module.getValue())); + } + } else { + // TODO find a way to reuse the previously model instead of building a new one. + QuarkusModel quarkusModel = QuarkusGradleModelFactory.createForTasks( + BuildToolHelper.getBuildFile(appClasses, BuildToolHelper.BuildTool.GRADLE).toFile(), + QuarkusModelHelper.DEVMODE_REQUIRED_TASKS); + final WorkspaceModule launchingModule = quarkusModel.getWorkspace().getMainModule(); + DevModeContext.ModuleInfo root = toModule(quarkusModel.getWorkspace().getMainModule()); + devModeContext.setApplicationRoot(root); + for (WorkspaceModule additionalModule : quarkusModel.getWorkspace().getAllModules()) { + if (!additionalModule.getArtifactCoords().equals(launchingModule.getArtifactCoords())) { + devModeContext.getAdditionalModules().add(toModule(additionalModule)); + } } - devModeContext.getAdditionalModules().add(toModule(module.getValue())); } - } catch (BootstrapMavenException e) { + + } catch (AppModelResolverException e) { log.error("Failed to load workspace, hot reload will not be available", e); } @@ -40,13 +65,37 @@ public void accept(CuratedApplication curatedApplication, Map st Collections.singletonMap(DevModeContext.class.getName(), devModeContext)); } + private DevModeContext.ModuleInfo toModule(WorkspaceModule module) throws BootstrapGradleException { + AppArtifactKey key = new AppArtifactKey(module.getArtifactCoords().getGroupId(), + module.getArtifactCoords().getArtifactId(), module.getArtifactCoords().getClassifier()); + + Set sourceDirectories = new HashSet<>(); + Set sourceParents = new HashSet<>(); + for (File srcDir : module.getSourceSourceSet().getSourceDirectories()) { + sourceDirectories.add(srcDir.getPath()); + sourceParents.add(srcDir.getParent()); + } + + return new DevModeContext.ModuleInfo(key, + module.getArtifactCoords().getArtifactId(), + module.getProjectRoot().getPath(), + sourceDirectories, + QuarkusModelHelper.getClassPath(module).toAbsolutePath().toString(), + module.getSourceSourceSet().getResourceDirectory().toString(), + module.getSourceSet().getResourceDirectory().getPath(), + sourceParents, + module.getBuildDir().toPath().resolve("generated-sources").toAbsolutePath().toString(), + module.getBuildDir().toString()); + } + private DevModeContext.ModuleInfo toModule(LocalProject project) { return new DevModeContext.ModuleInfo(project.getKey(), project.getArtifactId(), project.getDir().toAbsolutePath().toString(), Collections.singleton(project.getSourcesSourcesDir().toAbsolutePath().toString()), project.getClassesDir().toAbsolutePath().toString(), project.getResourcesSourcesDir().toAbsolutePath().toString(), - project.getSourcesDir().toString(), project.getCodeGenOutputDir().toString(), + project.getSourcesDir().toString(), + project.getCodeGenOutputDir().toString(), project.getOutputDir().toString()); } } diff --git a/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java b/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java index 53157f63633a9..d62c8154b75c8 100644 --- a/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java +++ b/core/launcher/src/main/java/io/quarkus/launcher/QuarkusLauncher.java @@ -11,6 +11,8 @@ import java.util.Map; import java.util.function.Consumer; +import io.quarkus.bootstrap.BootstrapConstants; + /** * IDE entry point. *

@@ -24,7 +26,6 @@ public class QuarkusLauncher { public static void launch(String callingClass, String quarkusApplication, Consumer exitHandler, String... args) { - try { String classResource = callingClass.replace(".", "/") + ".class"; URL resource = Thread.currentThread().getContextClassLoader().getResource(classResource); @@ -43,11 +44,14 @@ public static void launch(String callingClass, String quarkusApplication, Consum IDEClassLoader loader = new IDEClassLoader(QuarkusLauncher.class.getClassLoader()); Thread.currentThread().setContextClassLoader(loader); + Class launcher = loader.loadClass("io.quarkus.bootstrap.IDELauncherImpl"); launcher.getDeclaredMethod("launch", Path.class, Map.class).invoke(null, appClasses, context); } catch (Exception e) { throw new RuntimeException(e); + } finally { + System.clearProperty(BootstrapConstants.SERIALIZED_APP_MODEL); } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java index 2ca0c1dada2bd..501932d1fd348 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java @@ -1,66 +1,35 @@ package io.quarkus.gradle; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -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.Properties; import java.util.Set; -import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.artifacts.ModuleIdentifier; -import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedConfiguration; -import org.gradle.api.artifacts.ResolvedDependency; -import org.gradle.api.artifacts.component.ProjectComponentIdentifier; -import org.gradle.api.attributes.Category; -import org.gradle.api.file.RegularFile; -import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact; import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency; -import org.gradle.api.plugins.Convention; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginConvention; -import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.SourceSet; -import org.gradle.jvm.tasks.Jar; -import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppArtifactKey; import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.model.AppModel; -import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.resolver.AppModelResolver; import io.quarkus.bootstrap.resolver.AppModelResolverException; -import io.quarkus.gradle.tasks.QuarkusGradleUtils; -import io.quarkus.runtime.LaunchMode; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.util.QuarkusModelHelper; public class AppModelGradleResolver implements AppModelResolver { private AppModel appModel; - private final Project project; - private final LaunchMode launchMode; + private final QuarkusModel model; - public AppModelGradleResolver(Project project, LaunchMode mode) { + public AppModelGradleResolver(Project project, QuarkusModel model) { + this.model = model; this.project = project; - this.launchMode = mode; } @Override @@ -95,7 +64,6 @@ public void relink(AppArtifact appArtifact, Path localPath) throws AppModelResol @Override public Path resolve(AppArtifact appArtifact) throws AppModelResolverException { if (!appArtifact.isResolved()) { - final DefaultDependencyArtifact dep = new DefaultDependencyArtifact(); dep.setExtension(appArtifact.getType()); dep.setType(appArtifact.getType()); @@ -137,224 +105,16 @@ public List resolveUserDependencies(AppArtifact appArtifact, List @Override public AppModel resolveModel(AppArtifact appArtifact) throws AppModelResolverException { - AppModel.Builder appBuilder = new AppModel.Builder(); - if (appModel != null && appModel.getAppArtifact().equals(appArtifact)) { - return appModel; - } - final List directExtensionDeps = new ArrayList<>(); - - // collect enforced platforms - final Configuration impl = project.getConfigurations().getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME); - for (Dependency d : impl.getAllDependencies()) { - if (!(d instanceof ModuleDependency)) { - continue; - } - final ModuleDependency module = (ModuleDependency) d; - final Category category = module.getAttributes().getAttribute(Category.CATEGORY_ATTRIBUTE); - if (category != null && Category.ENFORCED_PLATFORM.equals(category.getName())) { - directExtensionDeps.add(d); - } - } - - final List userDeps = new ArrayList<>(); - Map versionMap = new HashMap<>(); - Map userModules = new HashMap<>(); - - final String classpathConfigName = launchMode == LaunchMode.TEST ? JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME - : JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME; - - collectDependencies(project.getConfigurations().getByName(classpathConfigName), - appBuilder, directExtensionDeps, userDeps, - versionMap, userModules); - - if (launchMode == LaunchMode.DEVELOPMENT) { - collectDependencies(project.getConfigurations().getByName(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME), - appBuilder, directExtensionDeps, userDeps, - versionMap, userModules); - } - - final List deploymentDeps = new ArrayList<>(); - final List fullDeploymentDeps = new ArrayList<>(userDeps); - if (!directExtensionDeps.isEmpty()) { - final Configuration deploymentConfig = project.getConfigurations() - .detachedConfiguration(directExtensionDeps.toArray(new Dependency[0])); - final ResolvedConfiguration rc = deploymentConfig.getResolvedConfiguration(); - for (ResolvedArtifact a : rc.getResolvedArtifacts()) { - final ModuleVersionIdentifier userVersion = userModules.get(getModuleId(a)); - if (userVersion != null || !isDependency(a)) { - continue; - } - final AppDependency dependency = toAppDependency(a); - fullDeploymentDeps.add(dependency); - if (!userDeps.contains(dependency)) { - AppDependency deploymentDep = alignVersion(dependency, versionMap); - deploymentDeps.add(deploymentDep); - } - } - } - - if (!appArtifact.isResolved()) { - final Jar jarTask = (Jar) project.getTasks().findByName(JavaPlugin.JAR_TASK_NAME); - if (jarTask == null) { - throw new AppModelResolverException("Failed to locate task 'jar' in the project."); - } - if (jarTask.getDidWork()) { - final Provider jarProvider = jarTask.getArchiveFile(); - Path classesDir = null; - if (jarProvider.isPresent()) { - final File f = jarProvider.get().getAsFile(); - if (f.exists()) { - classesDir = f.toPath(); - } - } - if (classesDir == null) { - throw new AppModelResolverException("Failed to locate classes directory for " + appArtifact); - } - appArtifact.setPaths(PathsCollection.of(classesDir)); + if (appModel != null) { + if (appModel.getAppArtifact().equals(appArtifact)) { + return appModel; } else { - final Convention convention = project.getConvention(); - JavaPluginConvention javaConvention = convention.findPlugin(JavaPluginConvention.class); - if (javaConvention != null) { - final SourceSet mainSourceSet = javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); - PathsCollection.Builder paths = PathsCollection.builder(); - mainSourceSet.getOutput().filter(s -> s.exists()).forEach(f -> { - paths.add(f.toPath()); - }); - for (File resourcesDir : mainSourceSet.getResources().getSourceDirectories()) { - if (resourcesDir.exists()) { - paths.add(resourcesDir.toPath()); - } - } - appArtifact.setPaths(paths.build()); - } + throw new AppModelResolverException( + "Requested artifact : " + appArtifact + ", does not match loaded model " + appModel.getAppArtifact()); } } - - appBuilder.addRuntimeDeps(userDeps) - .addFullDeploymentDeps(fullDeploymentDeps) - .addDeploymentDeps(deploymentDeps) - .setAppArtifact(appArtifact); - return this.appModel = appBuilder.build(); - } - - private void collectDependencies(Configuration config, AppModel.Builder appBuilder, - final List directExtensionDeps, - final List userDeps, Map versionMap, - Map userModules) { - - final ResolvedConfiguration resolvedConfig = config.getResolvedConfiguration(); - for (ResolvedArtifact a : resolvedConfig.getResolvedArtifacts()) { - if (!isDependency(a)) { - continue; - } - userModules.put(getModuleId(a), a.getModuleVersion().getId()); - - final AppDependency dependency = toAppDependency(a); - final AppArtifactKey artifactGa = new AppArtifactKey(dependency.getArtifact().getGroupId(), - dependency.getArtifact().getArtifactId()); - - // If we are running in dev mode we prefer directories of classes and resources over the JARs - // for local project dependencies - if (LaunchMode.DEVELOPMENT.equals(launchMode) - && (a.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier)) { - final Project depProject = project.getRootProject() - .findProject(((ProjectComponentIdentifier) a.getId().getComponentIdentifier()).getProjectPath()); - final JavaPluginConvention javaConvention = depProject.getConvention().findPlugin(JavaPluginConvention.class); - if (javaConvention != null) { - SourceSet mainSourceSet = javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); - final PathsCollection.Builder paths = PathsCollection.builder(); - final Path classesDir = Paths - .get(QuarkusGradleUtils.getClassesDir(mainSourceSet, depProject.getBuildDir(), false)); - if (Files.exists(classesDir)) { - paths.add(classesDir); - } - for (File resourcesDir : mainSourceSet.getResources().getSourceDirectories()) { - if (resourcesDir.exists()) { - paths.add(resourcesDir.toPath()); - } - } - dependency.getArtifact().setPaths(paths.build()); - } - } - - if (!dependency.getArtifact().isResolved()) { - throw new IllegalStateException("Failed to resolve " + a.getId()); - } - - userDeps.add(dependency); - versionMap.put(artifactGa, dependency); - } - - collectExtensionDeps(resolvedConfig.getFirstLevelModuleDependencies(), versionMap, appBuilder, directExtensionDeps, - true, new HashSet<>()); - } - - private void collectExtensionDeps(Set resolvedDeps, - Map versionMap, - AppModel.Builder appBuilder, - List firstLevelExtensions, - boolean firstLevelExt, - Set visited) { - for (ResolvedDependency dep : resolvedDeps) { - final AppArtifactKey key = new AppArtifactKey(dep.getModuleGroup(), dep.getModuleName()); - if (!visited.add(key)) { - continue; - } - final AppDependency appDep = versionMap.get(key); - if (appDep == null) { - // not a jar - continue; - } - - Dependency extDep = null; - for (Path artifactPath : appDep.getArtifact().getPaths()) { - if (!Files.exists(artifactPath)) { - continue; - } - if (Files.isDirectory(artifactPath)) { - extDep = processQuarkusDir(appDep.getArtifact(), artifactPath.resolve(BootstrapConstants.META_INF), - appBuilder); - } else { - try (FileSystem artifactFs = FileSystems.newFileSystem(artifactPath, null)) { - extDep = processQuarkusDir(appDep.getArtifact(), artifactFs.getPath(BootstrapConstants.META_INF), - appBuilder); - } catch (IOException e) { - throw new GradleException("Failed to process " + artifactPath, e); - } - } - if (extDep != null) { - break; - } - } - - boolean addChildExtensions = firstLevelExt; - if (extDep != null && firstLevelExt) { - firstLevelExtensions.add(extDep); - addChildExtensions = false; - } - final Set resolvedChildren = dep.getChildren(); - if (!resolvedChildren.isEmpty()) { - collectExtensionDeps(resolvedChildren, versionMap, appBuilder, firstLevelExtensions, addChildExtensions, - visited); - } - } - } - - /** - * A {@link ResolvedArtifact} is valid if it's a JAR or a directory - */ - private static boolean isDependency(ResolvedArtifact a) { - return BootstrapConstants.JAR.equalsIgnoreCase(a.getExtension()) || "exe".equalsIgnoreCase(a.getExtension()) || - a.getFile().isDirectory(); - } - - private AppDependency alignVersion(AppDependency dependency, Map versionMap) { - AppArtifactKey appKey = new AppArtifactKey(dependency.getArtifact().getGroupId(), - dependency.getArtifact().getArtifactId()); - if (versionMap.containsKey(appKey)) { - return versionMap.get(appKey); - } - return dependency; + appModel = QuarkusModelHelper.convert(model, appArtifact); + return appModel; } @Override @@ -369,56 +129,4 @@ public AppModel resolveManagedModel(AppArtifact appArtifact, List return resolveModel(appArtifact); } - private static ModuleIdentifier getModuleId(ResolvedArtifact a) { - final String[] split = a.getModuleVersion().toString().split(":"); - return DefaultModuleIdentifier.newId(split[0], split[1]); - } - - static AppDependency toAppDependency(ResolvedArtifact a) { - return new AppDependency(toAppArtifact(a), "runtime"); - } - - public static AppArtifact toAppArtifact(ResolvedArtifact a) { - final String[] split = a.getModuleVersion().toString().split(":"); - final AppArtifact appArtifact = new AppArtifact(split[0], split[1], a.getClassifier(), a.getType(), - split.length > 2 ? split[2] : null); - if (a.getFile().exists()) { - appArtifact.setPath(a.getFile().toPath()); - } - return appArtifact; - } - - private Dependency processQuarkusDir(AppArtifact a, Path quarkusDir, AppModel.Builder appBuilder) { - if (!Files.exists(quarkusDir)) { - return null; - } - final Path quarkusDescr = quarkusDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); - if (!Files.exists(quarkusDescr)) { - return null; - } - final Properties extProps = resolveDescriptor(quarkusDescr); - if (extProps == null) { - return null; - } - appBuilder.handleExtensionProperties(extProps, a.toString()); - String value = extProps.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); - final String[] split = value.split(":"); - - return new DefaultExternalModuleDependency(split[0], split[1], split[2], null); - } - - private Properties resolveDescriptor(final Path path) { - final Properties rtProps; - if (!Files.exists(path)) { - // not a platform artifact - return null; - } - rtProps = new Properties(); - try (BufferedReader reader = Files.newBufferedReader(path)) { - rtProps.load(reader); - } catch (IOException e) { - throw new GradleException("Failed to load extension description " + path, e); - } - return rtProps; - } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java index beab9a593b18e..c4568f8c394df 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -8,6 +8,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import javax.inject.Inject; + import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; @@ -26,8 +28,10 @@ import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.compile.JavaCompile; import org.gradle.api.tasks.testing.Test; +import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry; import org.gradle.util.GradleVersion; +import io.quarkus.gradle.builder.QuarkusModelBuilder; import io.quarkus.gradle.tasks.QuarkusAddExtension; import io.quarkus.gradle.tasks.QuarkusBuild; import io.quarkus.gradle.tasks.QuarkusDev; @@ -67,9 +71,17 @@ public class QuarkusPlugin implements Plugin { public static final String NATIVE_TEST_IMPLEMENTATION_CONFIGURATION_NAME = "nativeTestImplementation"; public static final String NATIVE_TEST_RUNTIME_ONLY_CONFIGURATION_NAME = "nativeTestRuntimeOnly"; + private final ToolingModelBuilderRegistry registry; + + @Inject + public QuarkusPlugin(ToolingModelBuilderRegistry registry) { + this.registry = registry; + } + @Override public void apply(Project project) { verifyGradleVersion(); + registerModel(); // register extension final QuarkusPluginExtension quarkusExt = project.getExtensions().create(EXTENSION_NAME, QuarkusPluginExtension.class, project); @@ -185,6 +197,10 @@ private Set getSourcesParents(SourceSet mainSourceSet) { .collect(Collectors.toSet()); } + private void registerModel() { + registry.register(new QuarkusModelBuilder()); + } + private void verifyGradleVersion() { if (GradleVersion.current().compareTo(GradleVersion.version("5.0")) < 0) { throw new GradleException("Quarkus plugin requires Gradle 5.0 or later. Current version is: " + diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java index 8b711ad3865a7..900a2bffe3395 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -21,6 +21,10 @@ import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.bootstrap.model.AppModel; import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.model.ModelParameter; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.impl.ModelParameterImpl; +import io.quarkus.gradle.builder.QuarkusModelBuilder; import io.quarkus.gradle.tasks.QuarkusGradleUtils; import io.quarkus.runtime.LaunchMode; @@ -163,7 +167,22 @@ public AppModelResolver getAppModelResolver() { } public AppModelResolver getAppModelResolver(LaunchMode mode) { - return new AppModelGradleResolver(project, mode); + return new AppModelGradleResolver(project, getQuarkusModel(mode)); + } + + public QuarkusModel getQuarkusModel() { + return getQuarkusModel(LaunchMode.NORMAL); + } + + public QuarkusModel getQuarkusModel(LaunchMode mode) { + return create(project, mode); + } + + private QuarkusModel create(Project project, LaunchMode mode) { + QuarkusModelBuilder builder = new QuarkusModelBuilder(); + ModelParameter params = new ModelParameterImpl(); + params.setMode(mode.toString()); + return (QuarkusModel) builder.buildAll(QuarkusModel.class.getName(), params, project); } /** @@ -179,4 +198,5 @@ private File getLastFile(FileCollection fileCollection) { } return result; } + } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/builder/QuarkusModelBuilder.java b/devtools/gradle/src/main/java/io/quarkus/gradle/builder/QuarkusModelBuilder.java new file mode 100644 index 0000000000000..60f202b688aec --- /dev/null +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/builder/QuarkusModelBuilder.java @@ -0,0 +1,299 @@ +package io.quarkus.gradle.builder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.artifacts.ResolvedConfiguration; +import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.artifacts.component.ProjectComponentIdentifier; +import org.gradle.api.attributes.Category; +import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency; +import org.gradle.api.plugins.Convention; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.SourceSet; +import org.gradle.tooling.provider.model.ParameterizedToolingModelBuilder; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.resolver.model.ArtifactCoords; +import io.quarkus.bootstrap.resolver.model.Dependency; +import io.quarkus.bootstrap.resolver.model.ModelParameter; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; +import io.quarkus.bootstrap.resolver.model.impl.ArtifactCoordsImpl; +import io.quarkus.bootstrap.resolver.model.impl.DependencyImpl; +import io.quarkus.bootstrap.resolver.model.impl.ModelParameterImpl; +import io.quarkus.bootstrap.resolver.model.impl.QuarkusModelImpl; +import io.quarkus.bootstrap.resolver.model.impl.SourceSetImpl; +import io.quarkus.bootstrap.resolver.model.impl.WorkspaceImpl; +import io.quarkus.bootstrap.resolver.model.impl.WorkspaceModuleImpl; +import io.quarkus.bootstrap.util.QuarkusModelHelper; +import io.quarkus.gradle.tasks.QuarkusGradleUtils; +import io.quarkus.runtime.LaunchMode; + +public class QuarkusModelBuilder implements ParameterizedToolingModelBuilder { + + private static final List scannedConfigurations = new LinkedList(); + + @Override + public boolean canBuild(String modelName) { + return modelName.equals(QuarkusModel.class.getName()); + } + + @Override + public Class getParameterType() { + return ModelParameter.class; + } + + @Override + public Object buildAll(String modelName, Project project) { + final ModelParameterImpl modelParameter = new ModelParameterImpl(); + modelParameter.setMode(LaunchMode.DEVELOPMENT.toString()); + return buildAll(modelName, modelParameter, project); + } + + @Override + public Object buildAll(String modelName, Object parameter, Project project) { + LaunchMode mode = LaunchMode.valueOf(((ModelParameter) parameter).getMode()); + + if (LaunchMode.TEST.equals(mode)) { + scannedConfigurations.add(JavaPlugin.TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); + } else { + scannedConfigurations.add(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME); + } + + if (LaunchMode.DEVELOPMENT.equals(mode)) { + scannedConfigurations.add(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); + } + + final Collection directExtensionDependencies = getEnforcedPlatforms(project); + + final Map appDependencies = new HashMap<>(); + for (String configurationName : scannedConfigurations) { + final ResolvedConfiguration configuration = project.getConfigurations().getByName(configurationName) + .getResolvedConfiguration(); + appDependencies.putAll(collectDependencies(configuration, configurationName, mode, project)); + directExtensionDependencies + .addAll(getDirectExtensionDependencies(configuration.getFirstLevelModuleDependencies(), appDependencies, + new HashSet<>())); + } + + final Set extensionDependencies = collectExtensionDependencies(project, directExtensionDependencies); + + ArtifactCoords appArtifactCoords = new ArtifactCoordsImpl(project.getGroup().toString(), project.getName(), + project.getVersion().toString()); + + return new QuarkusModelImpl(new WorkspaceImpl(appArtifactCoords, getWorkspace(project.getRootProject())), + new HashSet<>(appDependencies.values()), + extensionDependencies); + } + + public Set getWorkspace(Project project) { + Set modules = new HashSet<>(); + for (Project subproject : project.getAllprojects()) { + final Convention convention = subproject.getConvention(); + JavaPluginConvention javaConvention = convention.findPlugin(JavaPluginConvention.class); + if (javaConvention == null) { + continue; + } + modules.add(getWorkspaceModule(subproject)); + } + return modules; + } + + private WorkspaceModule getWorkspaceModule(Project project) { + ArtifactCoords appArtifactCoords = new ArtifactCoordsImpl(project.getGroup().toString(), project.getName(), + project.getVersion().toString()); + final SourceSet mainSourceSet = QuarkusGradleUtils.getSourceSet(project, SourceSet.MAIN_SOURCE_SET_NAME); + + return new WorkspaceModuleImpl(appArtifactCoords, project.getProjectDir().getAbsoluteFile(), + project.getBuildDir().getAbsoluteFile(), getSourceSourceSet(mainSourceSet), convert(mainSourceSet)); + } + + private Set getEnforcedPlatforms(Project project) { + final Set directExtension = new HashSet<>(); + // collect enforced platforms + final Configuration impl = project.getConfigurations() + .getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME); + for (org.gradle.api.artifacts.Dependency d : impl.getAllDependencies()) { + if (!(d instanceof ModuleDependency)) { + continue; + } + final ModuleDependency module = (ModuleDependency) d; + final Category category = module.getAttributes().getAttribute(Category.CATEGORY_ATTRIBUTE); + if (category != null && Category.ENFORCED_PLATFORM.equals(category.getName())) { + directExtension.add(d); + } + } + return directExtension; + } + + private Set getDirectExtensionDependencies(Set dependencies, + Map appDependencies, Set visited) { + Set extensions = new HashSet<>(); + for (ResolvedDependency d : dependencies) { + ArtifactCoords key = new ArtifactCoordsImpl(d.getModuleGroup(), d.getModuleName(), ""); + if (!visited.add(key)) { + continue; + } + + Dependency appDep = appDependencies.get(key); + if (appDep == null) { + continue; + } + final org.gradle.api.artifacts.Dependency deploymentArtifact = getDeploymentArtifact(appDep); + + boolean addChildExtension = true; + if (deploymentArtifact != null && addChildExtension) { + extensions.add(deploymentArtifact); + addChildExtension = false; + } + + final Set resolvedChildren = d.getChildren(); + if (addChildExtension && !resolvedChildren.isEmpty()) { + extensions + .addAll(getDirectExtensionDependencies(resolvedChildren, appDependencies, visited)); + } + } + return extensions; + } + + private org.gradle.api.artifacts.Dependency getDeploymentArtifact(Dependency dependency) { + for (File file : dependency.getPaths()) { + if (!file.exists()) { + continue; + } + Properties depsProperties; + if (file.isDirectory()) { + Path quarkusDescr = file.toPath() + .resolve(BootstrapConstants.META_INF) + .resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); + if (!Files.exists(quarkusDescr)) { + continue; + } + depsProperties = QuarkusModelHelper.resolveDescriptor(quarkusDescr); + } else { + try (FileSystem artifactFs = FileSystems.newFileSystem(file.toPath(), getClass().getClassLoader())) { + Path quarkusDescr = artifactFs.getPath(BootstrapConstants.META_INF) + .resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); + if (!Files.exists(quarkusDescr)) { + continue; + } + depsProperties = QuarkusModelHelper.resolveDescriptor(quarkusDescr); + } catch (IOException e) { + throw new GradleException("Failed to process " + file, e); + } + } + String value = depsProperties.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + String[] split = value.split(":"); + return new DefaultExternalModuleDependency(split[0], split[1], split[2], null); + } + return null; + } + + private Set collectExtensionDependencies(Project project, + Collection extensions) { + final Set platformDependencies = new HashSet<>(); + + final Configuration deploymentConfig = project.getConfigurations() + .detachedConfiguration(extensions.toArray(new org.gradle.api.artifacts.Dependency[0])); + final ResolvedConfiguration rc = deploymentConfig.getResolvedConfiguration(); + for (ResolvedArtifact a : rc.getResolvedArtifacts()) { + if (!isDependency(a)) { + continue; + } + + final Dependency dependency = toDependency(a, deploymentConfig.getName()); + platformDependencies.add(dependency); + } + + return platformDependencies; + } + + private Map collectDependencies(ResolvedConfiguration configuration, String configurationName, + LaunchMode mode, Project project) { + Map modelDependencies = new HashMap<>(); + for (ResolvedArtifact a : configuration.getResolvedArtifacts()) { + if (!isDependency(a)) { + continue; + } + Dependency dep; + if (LaunchMode.DEVELOPMENT.equals(mode) && + a.getId().getComponentIdentifier() instanceof ProjectComponentIdentifier) { + Project projectDep = project.getRootProject() + .findProject(((ProjectComponentIdentifier) a.getId().getComponentIdentifier()).getProjectPath()); + + dep = toDependency(a, configurationName, projectDep); + } else { + dep = toDependency(a, configurationName); + } + ArtifactCoords gaKey = new ArtifactCoordsImpl(dep.getGroupId(), dep.getName(), ""); + modelDependencies.put(gaKey, dep); + } + + return modelDependencies; + } + + private SourceSetImpl convert(SourceSet sourceSet) { + return new SourceSetImpl( + sourceSet.getOutput().getClassesDirs().getFiles(), + sourceSet.getOutput().getResourcesDir()); + } + + private io.quarkus.bootstrap.resolver.model.SourceSet getSourceSourceSet(SourceSet sourceSet) { + return new SourceSetImpl(sourceSet.getAllJava().getSrcDirs(), + sourceSet.getResources().getSourceDirectories().getSingleFile()); + } + + private static boolean isDependency(ResolvedArtifact a) { + return BootstrapConstants.JAR.equalsIgnoreCase(a.getExtension()) || "exe".equalsIgnoreCase(a.getExtension()) || + a.getFile().isDirectory(); + } + + private DependencyImpl toDependency(ResolvedArtifact a, String configuration, Project project) { + final String[] split = a.getModuleVersion().toString().split(":"); + final DependencyImpl dependency = new DependencyImpl(split[1], split[0], split.length > 2 ? split[2] : null, + configuration, a.getType(), a.getClassifier()); + final JavaPluginConvention javaConvention = project.getConvention().findPlugin(JavaPluginConvention.class); + if (javaConvention != null) { + SourceSet mainSourceSet = javaConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); + final File classesDir = new File(QuarkusGradleUtils.getClassesDir(mainSourceSet, project.getBuildDir(), false)); + if (classesDir.exists()) { + dependency.addPath(classesDir); + } + for (File resourcesDir : mainSourceSet.getResources().getSourceDirectories()) { + if (resourcesDir.exists()) { + dependency.addPath(resourcesDir); + } + } + } + return dependency; + } + + static DependencyImpl toDependency(ResolvedArtifact a, String configuration) { + final String[] split = a.getModuleVersion().toString().split(":"); + + final DependencyImpl dependency = new DependencyImpl(split[1], split[0], split.length > 2 ? split[2] : null, + configuration, a.getType(), a.getClassifier()); + dependency.addPath(a.getFile()); + return dependency; + } + +} diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java index 2e3d91aa40d97..a878eb3d76519 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/tasks/QuarkusDev.java @@ -8,10 +8,8 @@ import java.io.InputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; -import java.net.MalformedURLException; import java.net.Socket; import java.net.URI; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -463,6 +461,7 @@ private void addLocalProject(Project project, DevModeContext context, Set paths = new ArrayList<>(); generatedSources.getOutput() .filter(f -> f.getName().equals(generateSourcesDir)) @@ -75,22 +74,23 @@ public void prepareQuarkus() { getLogger().debug("Will trigger preparing sources for source directory: {} buildDir: {}", sourcesDirectories, getProject().getBuildDir().getAbsolutePath()); - QuarkusClassLoader deploymentClassLoader = appCreationContext.createDeploymentClassLoader(); + QuarkusClassLoader deploymentClassLoader = appCreationContext.createDeploymentClassLoader(); Class codeGenerator = deploymentClassLoader.loadClass(CodeGenerator.class.getName()); + Optional initAndRun = Arrays.stream(codeGenerator.getMethods()) .filter(m -> m.getName().equals(INIT_AND_RUN)) .findAny(); if (!initAndRun.isPresent()) { throw new GradleException("Failed to find " + INIT_AND_RUN + " method in " + CodeGenerator.class.getName()); } - initAndRun.get().invoke(null, deploymentClassLoader, sourcesDirectories, paths.iterator().next(), buildDir, sourceRegistrar, appCreationContext.getAppModel()); + } } catch (BootstrapException | IllegalAccessException | InvocationTargetException | ClassNotFoundException e) { throw new GradleException("Failed to generate sources in the QuarkusPrepare task", e); diff --git a/devtools/gradle/src/test/java/io/quarkus/gradle/AppModelGradleResolverTest.java b/devtools/gradle/src/test/java/io/quarkus/gradle/AppModelGradleResolverTest.java deleted file mode 100644 index a426fd90396c4..0000000000000 --- a/devtools/gradle/src/test/java/io/quarkus/gradle/AppModelGradleResolverTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.quarkus.gradle; - -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.assertj.core.util.Files; -import org.gradle.api.artifacts.ResolvedArtifact; -import org.gradle.api.artifacts.ResolvedModuleVersion; -import org.junit.jupiter.api.Test; - -class AppModelGradleResolverTest { - - @Test - void testToAppDependency() { - ResolvedArtifact artifact = mock(ResolvedArtifact.class); - ResolvedModuleVersion version = mock(ResolvedModuleVersion.class); - when(version.toString()).thenReturn(":commons-lang3-3.9:"); - when(artifact.getModuleVersion()).thenReturn(version); - when(artifact.getFile()).thenReturn(Files.currentFolder()); - assertThatCode(() -> AppModelGradleResolver.toAppDependency(artifact)).doesNotThrowAnyException(); - } -} diff --git a/devtools/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java b/devtools/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java new file mode 100644 index 0000000000000..df9bf9b9f3d6b --- /dev/null +++ b/devtools/gradle/src/test/java/io/quarkus/gradle/builder/QuarkusModelBuilderTest.java @@ -0,0 +1,112 @@ +package io.quarkus.gradle.builder; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +import org.assertj.core.util.Files; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.artifacts.ResolvedModuleVersion; +import org.junit.jupiter.api.Test; + +import io.quarkus.bootstrap.resolver.QuarkusGradleModelFactory; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.SourceSet; +import io.quarkus.bootstrap.resolver.model.Workspace; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; + +class QuarkusModelBuilderTest { + + @Test + void testToAppDependency() { + ResolvedArtifact artifact = mock(ResolvedArtifact.class); + ResolvedModuleVersion version = mock(ResolvedModuleVersion.class); + when(version.toString()).thenReturn(":commons-lang3-3.9:"); + when(artifact.getModuleVersion()).thenReturn(version); + when(artifact.getFile()).thenReturn(Files.currentFolder()); + assertThatCode(() -> QuarkusModelBuilder.toDependency(artifact, "implementation")).doesNotThrowAnyException(); + } + + @Test + public void shouldLoadSimpleModuleModel() throws URISyntaxException, IOException { + File projectDir = getResourcesProject("simple-module-project"); + final QuarkusModel quarkusModel = QuarkusGradleModelFactory.create(projectDir, "TEST"); + + assertNotNull(quarkusModel); + Workspace workspace = quarkusModel.getWorkspace(); + assertWorkspace(workspace.getMainModule(), projectDir); + assertEquals(1, quarkusModel.getWorkspace().getAllModules().size()); + } + + @Test + public void shouldLoadMultiModuleModel() throws URISyntaxException, IOException { + File projectDir = getResourcesProject("multi-module-project"); + final QuarkusModel quarkusModel = QuarkusGradleModelFactory.create(new File(projectDir, "application"), "TEST"); + + assertNotNull(quarkusModel); + assertEquals(2, quarkusModel.getWorkspace().getAllModules().size()); + + for (WorkspaceModule module : quarkusModel.getWorkspace().getAllModules()) { + assertWorkspace(module, new File(projectDir, module.getArtifactCoords().getArtifactId())); + } + } + + private void assertWorkspace(WorkspaceModule workspaceModule, File projectDir) { + assertNotNull(workspaceModule); + assertEquals(projectDir, workspaceModule.getProjectRoot()); + assertEquals(new File(projectDir, "build"), workspaceModule.getBuildDir()); + final SourceSet sourceSet = workspaceModule.getSourceSet(); + assertNotNull(sourceSet); + assertEquals(new File(projectDir, "build/resources/main"), sourceSet.getResourceDirectory()); + assertEquals(1, sourceSet.getSourceDirectories().size()); + assertEquals(new File(projectDir, "build/classes/java/main"), sourceSet.getSourceDirectories().iterator().next()); + final SourceSet sourceSourceSet = workspaceModule.getSourceSourceSet(); + assertEquals(new File(projectDir, "src/main/resources"), sourceSourceSet.getResourceDirectory()); + assertEquals(1, sourceSourceSet.getSourceDirectories().size()); + assertEquals(new File(projectDir, "src/main/java"), sourceSourceSet.getSourceDirectories().iterator().next()); + } + + private File getResourcesProject(String projectName) throws URISyntaxException, IOException { + final URL basedirUrl = Thread.currentThread().getContextClassLoader().getResource(projectName); + assertNotNull(basedirUrl); + + final File projectDir = new File(basedirUrl.toURI()); + + final File projectProps = new File(projectDir, "gradle.properties"); + final Properties props = new Properties(); + final String quarkusVersion = getQuarkusVersion(); + props.setProperty("quarkusPlatformVersion", quarkusVersion); + props.setProperty("quarkusPluginVersion", quarkusVersion); + try (OutputStream os = new FileOutputStream(projectProps)) { + props.store(os, "Quarkus Gradle TS"); + } + return projectDir; + } + + protected String getQuarkusVersion() throws IOException { + final Path curDir = Paths.get("").toAbsolutePath().normalize(); + final Path gradlePropsFile = curDir.resolve("gradle.properties"); + Properties props = new Properties(); + try (InputStream is = java.nio.file.Files.newInputStream(gradlePropsFile)) { + props.load(is); + } + final String quarkusVersion = props.getProperty("version"); + if (quarkusVersion == null) { + throw new IllegalStateException("Failed to locate Quarkus version in " + gradlePropsFile); + } + return quarkusVersion; + } + +} diff --git a/devtools/gradle/src/test/resources/multi-module-project/application/build.gradle b/devtools/gradle/src/test/resources/multi-module-project/application/build.gradle new file mode 100644 index 0000000000000..9a9126404f827 --- /dev/null +++ b/devtools/gradle/src/test/resources/multi-module-project/application/build.gradle @@ -0,0 +1,7 @@ +plugins { + id 'io.quarkus' +} + +dependencies { + implementation project(":common") +} \ No newline at end of file diff --git a/devtools/gradle/src/test/resources/multi-module-project/build.gradle b/devtools/gradle/src/test/resources/multi-module-project/build.gradle new file mode 100644 index 0000000000000..c154c33071814 --- /dev/null +++ b/devtools/gradle/src/test/resources/multi-module-project/build.gradle @@ -0,0 +1,19 @@ + +subprojects { + apply plugin: 'java' + + repositories { + mavenLocal() + mavenCentral() + } + + dependencies { + implementation enforcedPlatform("io.quarkus:quarkus-bom:999-SNAPSHOT") + implementation 'io.quarkus:quarkus-resteasy' + testImplementation 'io.quarkus:quarkus-junit5' + } + +} + +group 'org.acme' +version '1.0-SNAPSHOT' diff --git a/devtools/gradle/src/test/resources/multi-module-project/common/build.gradle b/devtools/gradle/src/test/resources/multi-module-project/common/build.gradle new file mode 100644 index 0000000000000..2bb603cfb457a --- /dev/null +++ b/devtools/gradle/src/test/resources/multi-module-project/common/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'io.quarkus' + id 'java-library' +} + +dependencies { + +} \ No newline at end of file diff --git a/devtools/gradle/src/test/resources/multi-module-project/settings.gradle b/devtools/gradle/src/test/resources/multi-module-project/settings.gradle new file mode 100644 index 0000000000000..60c19d80c56b0 --- /dev/null +++ b/devtools/gradle/src/test/resources/multi-module-project/settings.gradle @@ -0,0 +1,13 @@ +pluginManagement { + repositories { + mavenLocal() + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } +} +rootProject.name='mutli-module-project' + +include 'common', 'application' \ No newline at end of file diff --git a/devtools/gradle/src/test/resources/simple-module-project/build.gradle b/devtools/gradle/src/test/resources/simple-module-project/build.gradle new file mode 100644 index 0000000000000..3aed0a675a9b6 --- /dev/null +++ b/devtools/gradle/src/test/resources/simple-module-project/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' + id 'io.quarkus' +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation enforcedPlatform("io.quarkus:quarkus-bom:999-SNAPSHOT") + implementation 'io.quarkus:quarkus-resteasy' +} + +group 'org.acme' +version '1.0-SNAPSHOT' + + +compileTestJava { + options.encoding = 'UTF-8' +} diff --git a/devtools/gradle/src/test/resources/simple-module-project/settings.gradle b/devtools/gradle/src/test/resources/simple-module-project/settings.gradle new file mode 100644 index 0000000000000..775892b4374e6 --- /dev/null +++ b/devtools/gradle/src/test/resources/simple-module-project/settings.gradle @@ -0,0 +1,11 @@ +pluginManagement { + repositories { + mavenLocal() + mavenCentral() + gradlePluginPortal() + } + plugins { + id 'io.quarkus' version "${quarkusPluginVersion}" + } +} +rootProject.name='simple-module-project' \ No newline at end of file diff --git a/independent-projects/bootstrap/app-model/pom.xml b/independent-projects/bootstrap/app-model/pom.xml index 0dc319ac188ea..425c62d6b7565 100644 --- a/independent-projects/bootstrap/app-model/pom.xml +++ b/independent-projects/bootstrap/app-model/pom.xml @@ -24,5 +24,4 @@ runtime - diff --git a/independent-projects/bootstrap/core/pom.xml b/independent-projects/bootstrap/core/pom.xml index 90c07519d839f..9c6639a993f6a 100644 --- a/independent-projects/bootstrap/core/pom.xml +++ b/independent-projects/bootstrap/core/pom.xml @@ -22,10 +22,14 @@ io.quarkus quarkus-bootstrap-maven-resolver + + io.quarkus + quarkus-bootstrap-gradle-resolver + io.smallrye.common smallrye-common-io - + org.junit.jupiter junit-jupiter 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 c55cd008b51fa..5dcd7803c8237 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 @@ -1,7 +1,12 @@ package io.quarkus.bootstrap; +import io.quarkus.bootstrap.app.AdditionalDependency; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; +import io.quarkus.bootstrap.util.QuarkusModelHelper; +import io.quarkus.bootstrap.utils.BuildToolHelper; import java.nio.file.Path; import java.util.Map; @@ -19,12 +24,36 @@ public static void launch(Path projectRoot, Map context) { try { //todo : proper support for everything - CuratedApplication app = QuarkusBootstrap.builder(projectRoot) + final QuarkusBootstrap.Builder builder = QuarkusBootstrap.builder() + .setApplicationRoot(projectRoot) .setBaseClassLoader(IDELauncherImpl.class.getClassLoader()) .setProjectRoot(projectRoot) .setIsolateDeployment(true) - .setMode(QuarkusBootstrap.Mode.DEV) + .setMode(QuarkusBootstrap.Mode.DEV); + + if (!BuildToolHelper.isMavenProject(projectRoot)) { + final QuarkusModel quarkusModel = BuildToolHelper.enableGradleAppModelForDevMode(projectRoot); + // Gradle uses a different output directory for classes, we override the one used by the IDE + final WorkspaceModule launchingModule = quarkusModel.getWorkspace().getMainModule(); + Path launchingModulePath = QuarkusModelHelper.getClassPath(launchingModule); + + builder.setProjectRoot(launchingModulePath) + .setApplicationRoot(launchingModulePath); + + for (WorkspaceModule additionalModule : quarkusModel.getWorkspace().getAllModules()) { + if (!additionalModule.getArtifactCoords().equals(launchingModule.getArtifactCoords())) { + builder.addAdditionalApplicationArchive(new AdditionalDependency( + QuarkusModelHelper.toPathsCollection(additionalModule.getSourceSet().getSourceDirectories()), + true, false)); + builder.addAdditionalApplicationArchive(new AdditionalDependency( + additionalModule.getSourceSet().getResourceDirectory().toPath(), true, false)); + } + } + } + + CuratedApplication app = builder .build().bootstrap(); + app.runInAugmentClassLoader("io.quarkus.deployment.dev.IDEDevModeMain", context); } catch (Exception e) { throw new RuntimeException(e); diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java new file mode 100644 index 0000000000000..babc69bd05341 --- /dev/null +++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/utils/BuildToolHelper.java @@ -0,0 +1,83 @@ +package io.quarkus.bootstrap.utils; + +import static io.quarkus.bootstrap.util.QuarkusModelHelper.DEVMODE_REQUIRED_TASKS; + +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.resolver.QuarkusGradleModelFactory; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.util.QuarkusModelHelper; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Helper class used to expose build tool used by the project + */ +public class BuildToolHelper { + + public enum BuildTool { + MAVEN("pom.xml"), + GRADLE("build.gradle"); + + private final String buildFile; + + BuildTool(String buildFile) { + this.buildFile = buildFile; + } + + public String getBuildFile() { + return buildFile; + } + } + + private BuildToolHelper() { + + } + + public static boolean isMavenProject(Path project) { + Path currentPath = project; + while (currentPath != null) { + if (Files.exists(currentPath.resolve(BuildTool.MAVEN.getBuildFile()))) { + return true; + } + if (Files.exists(currentPath.resolve(BuildTool.GRADLE.getBuildFile()))) { + return false; + } + currentPath = currentPath.getParent(); + } + return false; + } + + public static Path getBuildFile(Path project, BuildTool tool) { + Path currentPath = project; + while (currentPath != null) { + if (Files.exists(currentPath.resolve(tool.getBuildFile()))) { + return currentPath; + } + currentPath = currentPath.getParent(); + } + return null; + } + + public static QuarkusModel enableGradleAppModel(Path projectRoot, String mode) + throws IOException, AppModelResolverException { + if (isMavenProject(projectRoot)) { + return null; + } + final QuarkusModel model = QuarkusGradleModelFactory.create(getBuildFile(projectRoot, BuildTool.GRADLE).toFile(), + mode); + QuarkusModelHelper.exportModel(model); + return model; + } + + public static QuarkusModel enableGradleAppModelForDevMode(Path projectRoot) throws IOException, AppModelResolverException { + if (isMavenProject(projectRoot)) { + return null; + } + final QuarkusModel model = QuarkusGradleModelFactory + .createForTasks(getBuildFile(projectRoot, BuildTool.GRADLE).toFile(), DEVMODE_REQUIRED_TASKS); + QuarkusModelHelper.exportModel(model); + return model; + } + +} diff --git a/independent-projects/bootstrap/gradle-resolver/pom.xml b/independent-projects/bootstrap/gradle-resolver/pom.xml new file mode 100644 index 0000000000000..288d1ffe8bad0 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/pom.xml @@ -0,0 +1,35 @@ + + + + quarkus-bootstrap-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-bootstrap-gradle-resolver + Quarkus - Bootstrap - Gradle Resolver + + + + io.quarkus + quarkus-bootstrap-app-model + + + org.gradle + gradle-tooling-api + + + org.jboss.slf4j + slf4j-jboss-logging + + + org.junit.jupiter + junit-jupiter + test + + + \ No newline at end of file diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/BootstrapGradleException.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/BootstrapGradleException.java new file mode 100644 index 0000000000000..967f445ad8248 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/BootstrapGradleException.java @@ -0,0 +1,14 @@ +package io.quarkus.bootstrap; + +import io.quarkus.bootstrap.resolver.AppModelResolverException; + +public class BootstrapGradleException extends AppModelResolverException { + + public BootstrapGradleException(String message, Throwable cause) { + super(message, cause); + } + + public BootstrapGradleException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/QuarkusGradleModelFactory.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/QuarkusGradleModelFactory.java new file mode 100644 index 0000000000000..8f025f98f159f --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/QuarkusGradleModelFactory.java @@ -0,0 +1,29 @@ +package io.quarkus.bootstrap.resolver; + +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import java.io.File; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ModelBuilder; +import org.gradle.tooling.ProjectConnection; + +public class QuarkusGradleModelFactory { + + public static QuarkusModel create(File projectDir, String mode) { + try (ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(projectDir) + .connect()) { + return connection.action(new QuarkusModelBuildAction(mode)).run(); + } + } + + public static QuarkusModel createForTasks(File projectDir, String... tasks) { + try (ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(projectDir) + .connect()) { + final ModelBuilder modelBuilder = connection.model(QuarkusModel.class); + modelBuilder.forTasks(tasks); + return modelBuilder.get(); + } + } + +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/QuarkusModelBuildAction.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/QuarkusModelBuildAction.java new file mode 100644 index 0000000000000..b021b1b1c5230 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/QuarkusModelBuildAction.java @@ -0,0 +1,20 @@ +package io.quarkus.bootstrap.resolver; + +import io.quarkus.bootstrap.resolver.model.ModelParameter; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import java.io.Serializable; +import org.gradle.tooling.BuildAction; +import org.gradle.tooling.BuildController; + +public class QuarkusModelBuildAction implements BuildAction, Serializable { + private final String mode; + + public QuarkusModelBuildAction(String mode) { + this.mode = mode; + } + + @Override + public QuarkusModel execute(BuildController controller) { + return controller.getModel(QuarkusModel.class, ModelParameter.class, p -> p.setMode(mode)); + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/ArtifactCoords.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/ArtifactCoords.java new file mode 100644 index 0000000000000..2435025b8e94f --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/ArtifactCoords.java @@ -0,0 +1,14 @@ +package io.quarkus.bootstrap.resolver.model; + +public interface ArtifactCoords { + + String getGroupId(); + + String getArtifactId(); + + String getClassifier(); + + String getVersion(); + + String getType(); +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/Dependency.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/Dependency.java new file mode 100644 index 0000000000000..ac58deec4eefa --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/Dependency.java @@ -0,0 +1,22 @@ +package io.quarkus.bootstrap.resolver.model; + +import java.io.File; +import java.util.Set; + +public interface Dependency { + + String getName(); + + String getGroupId(); + + String getVersion(); + + String getClassifier(); + + Set getPaths(); + + String getType(); + + String getScope(); + +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/ModelParameter.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/ModelParameter.java new file mode 100644 index 0000000000000..8d6dc75e8284d --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/ModelParameter.java @@ -0,0 +1,8 @@ +package io.quarkus.bootstrap.resolver.model; + +public interface ModelParameter { + + String getMode(); + + void setMode(String mode); +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/QuarkusModel.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/QuarkusModel.java new file mode 100644 index 0000000000000..735a74cec8a2e --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/QuarkusModel.java @@ -0,0 +1,13 @@ +package io.quarkus.bootstrap.resolver.model; + +import java.util.Set; + +public interface QuarkusModel { + + Workspace getWorkspace(); + + Set getAppDependencies(); + + Set getExtensionDependencies(); + +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/SourceSet.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/SourceSet.java new file mode 100644 index 0000000000000..c90d35a06f23e --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/SourceSet.java @@ -0,0 +1,11 @@ +package io.quarkus.bootstrap.resolver.model; + +import java.io.File; +import java.util.Set; + +public interface SourceSet { + + Set getSourceDirectories(); + + File getResourceDirectory(); +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/Workspace.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/Workspace.java new file mode 100644 index 0000000000000..89888c31aadb4 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/Workspace.java @@ -0,0 +1,13 @@ +package io.quarkus.bootstrap.resolver.model; + +import java.util.Collection; + +public interface Workspace { + + WorkspaceModule getMainModule(); + + Collection getAllModules(); + + WorkspaceModule getModule(ArtifactCoords key); + +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/WorkspaceModule.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/WorkspaceModule.java new file mode 100644 index 0000000000000..4b27f8c24d6e6 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/WorkspaceModule.java @@ -0,0 +1,16 @@ +package io.quarkus.bootstrap.resolver.model; + +import java.io.File; + +public interface WorkspaceModule { + + ArtifactCoords getArtifactCoords(); + + File getProjectRoot(); + + File getBuildDir(); + + SourceSet getSourceSet(); + + SourceSet getSourceSourceSet(); +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/ArtifactCoordsImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/ArtifactCoordsImpl.java new file mode 100644 index 0000000000000..f18feb15d744a --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/ArtifactCoordsImpl.java @@ -0,0 +1,72 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.ArtifactCoords; +import java.io.Serializable; +import java.util.Objects; + +public class ArtifactCoordsImpl implements ArtifactCoords, Serializable { + + public static final String TYPE_JAR = "jar"; + + private final String groupId; + private final String artifactId; + private final String classifier; + private final String version; + private final String type; + + public ArtifactCoordsImpl(String groupId, String artifactId, String version) { + this(groupId, artifactId, "", version, TYPE_JAR); + } + + public ArtifactCoordsImpl(String groupId, String artifactId, String classifier, String version, String type) { + this.groupId = groupId; + this.artifactId = artifactId; + this.classifier = classifier; + this.version = version; + this.type = type; + } + + @Override + public String getGroupId() { + return groupId; + } + + @Override + public String getArtifactId() { + return artifactId; + } + + @Override + public String getClassifier() { + return classifier; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public String getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ArtifactCoordsImpl that = (ArtifactCoordsImpl) o; + return Objects.equals(groupId, that.groupId) && + Objects.equals(artifactId, that.artifactId) && + Objects.equals(classifier, that.classifier) && + Objects.equals(version, that.version) && + Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(groupId, artifactId, classifier, version, type); + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/DependencyImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/DependencyImpl.java new file mode 100644 index 0000000000000..3b75e0a14bcf5 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/DependencyImpl.java @@ -0,0 +1,106 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.Dependency; +import java.io.File; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class DependencyImpl implements Dependency, Serializable { + + private final String name; + private final String groupId; + private final String version; + private final String classifier; + private final Set paths = new HashSet<>(); + private final String scope; + private final String type; + + public DependencyImpl(String name, String groupId, String version, File path, String scope, String type, + String classifier) { + this(name, groupId, version, scope, type, classifier); + this.paths.add(path); + } + + public DependencyImpl(String name, String groupId, String version, String scope, String type, String classifier) { + this.name = name; + this.groupId = groupId; + this.version = version; + this.scope = scope; + this.type = type; + this.classifier = classifier; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getGroupId() { + return groupId; + } + + @Override + public String getVersion() { + return version; + } + + @Override + public String getType() { + return type; + } + + @Override + public String getClassifier() { + return classifier; + } + + @Override + public Set getPaths() { + return paths; + } + + public void addPath(File path) { + this.paths.add(path); + } + + @Override + public String getScope() { + return scope; + } + + @Override + public String toString() { + return "DependencyImpl{" + + "name='" + name + '\'' + + ", groupId='" + groupId + '\'' + + ", version='" + version + '\'' + + ", type='" + type + '\'' + + ", path=" + paths + + ", classifier= " + classifier + + ", scope='" + scope + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + DependencyImpl that = (DependencyImpl) o; + return name.equals(that.name) && + groupId.equals(that.groupId) && + version.equals(that.version) && + paths.equals(that.paths) && + scope.equals(that.scope) && + type.equals(that.type); + } + + @Override + public int hashCode() { + return Objects.hash(name, groupId, version, paths, scope, type); + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/ModelParameterImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/ModelParameterImpl.java new file mode 100644 index 0000000000000..085680bcfb618 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/ModelParameterImpl.java @@ -0,0 +1,19 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.ModelParameter; +import java.io.Serializable; + +public class ModelParameterImpl implements ModelParameter, Serializable { + + private String mode; + + @Override + public String getMode() { + return mode; + } + + @Override + public void setMode(String mode) { + this.mode = mode; + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/QuarkusModelImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/QuarkusModelImpl.java new file mode 100644 index 0000000000000..3dd21461a4da0 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/QuarkusModelImpl.java @@ -0,0 +1,37 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.Dependency; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.Workspace; +import java.io.Serializable; +import java.util.Set; + +public class QuarkusModelImpl implements QuarkusModel, Serializable { + + private final Workspace workspace; + private final Set appDependencies; + private final Set extensionDependencies; + + public QuarkusModelImpl(Workspace workspace, + Set appDependencies, + Set extensionDependencies) { + this.workspace = workspace; + this.appDependencies = appDependencies; + this.extensionDependencies = extensionDependencies; + } + + @Override + public Workspace getWorkspace() { + return workspace; + } + + @Override + public Set getAppDependencies() { + return appDependencies; + } + + @Override + public Set getExtensionDependencies() { + return extensionDependencies; + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/SourceSetImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/SourceSetImpl.java new file mode 100644 index 0000000000000..d8a052accb667 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/SourceSetImpl.java @@ -0,0 +1,36 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.SourceSet; +import java.io.File; +import java.io.Serializable; +import java.util.Set; +import java.util.stream.Collectors; + +public class SourceSetImpl implements SourceSet, Serializable { + + private final Set sourceDirectories; + private final File resourceDirectory; + + public SourceSetImpl(Set sourceDirectories, File resourceDirectory) { + this.sourceDirectories = sourceDirectories; + this.resourceDirectory = resourceDirectory; + } + + @Override + public Set getSourceDirectories() { + return sourceDirectories; + } + + @Override + public File getResourceDirectory() { + return resourceDirectory; + } + + @Override + public String toString() { + return "SourceSetImpl{" + + "sourceDirectories=" + sourceDirectories.stream().map(File::getPath).collect(Collectors.joining(":")) + + ", resourceDirectory=" + resourceDirectory + + '}'; + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/WorkspaceImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/WorkspaceImpl.java new file mode 100644 index 0000000000000..bba576057fc4a --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/WorkspaceImpl.java @@ -0,0 +1,38 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.ArtifactCoords; +import io.quarkus.bootstrap.resolver.model.Workspace; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class WorkspaceImpl implements Workspace, Serializable { + + public ArtifactCoords mainModuleKey; + public Map modules = new HashMap<>(); + + public WorkspaceImpl(ArtifactCoords mainModuleKey, Set workspaceModules) { + this.mainModuleKey = mainModuleKey; + for (WorkspaceModule module : workspaceModules) { + modules.put(module.getArtifactCoords(), module); + } + } + + @Override + public WorkspaceModule getMainModule() { + return modules.get(mainModuleKey); + } + + @Override + public Collection getAllModules() { + return modules.values(); + } + + @Override + public WorkspaceModule getModule(ArtifactCoords key) { + return modules.get(key); + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/WorkspaceModuleImpl.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/WorkspaceModuleImpl.java new file mode 100644 index 0000000000000..8827a6fd56687 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/resolver/model/impl/WorkspaceModuleImpl.java @@ -0,0 +1,70 @@ +package io.quarkus.bootstrap.resolver.model.impl; + +import io.quarkus.bootstrap.resolver.model.ArtifactCoords; +import io.quarkus.bootstrap.resolver.model.SourceSet; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; +import java.io.File; +import java.io.Serializable; +import java.util.Objects; + +public class WorkspaceModuleImpl implements WorkspaceModule, Serializable { + + private final ArtifactCoords artifactCoords; + private final File projectRoot; + private final File buildDir; + private final SourceSet sourceSourceSet; + private final SourceSet sourceSet; + + public WorkspaceModuleImpl(ArtifactCoords artifactCoords, File projectRoot, File buildDir, SourceSet sourceSourceSet, + SourceSet sourceSet) { + this.artifactCoords = artifactCoords; + this.projectRoot = projectRoot; + this.buildDir = buildDir; + this.sourceSourceSet = sourceSourceSet; + this.sourceSet = sourceSet; + } + + @Override + public ArtifactCoords getArtifactCoords() { + return artifactCoords; + } + + @Override + public File getProjectRoot() { + return projectRoot; + } + + @Override + public File getBuildDir() { + return buildDir; + } + + @Override + public SourceSet getSourceSet() { + return sourceSet; + } + + @Override + public SourceSet getSourceSourceSet() { + return sourceSourceSet; + } + + public boolean equals(Object object) { + if (this == object) + return true; + if (object == null || getClass() != object.getClass()) + return false; + if (!super.equals(object)) + return false; + WorkspaceModuleImpl that = (WorkspaceModuleImpl) object; + return java.util.Objects.equals(artifactCoords, that.artifactCoords) && + java.util.Objects.equals(projectRoot, that.projectRoot) && + java.util.Objects.equals(buildDir, that.buildDir) && + java.util.Objects.equals(sourceSourceSet, that.sourceSourceSet) && + java.util.Objects.equals(sourceSet, that.sourceSet); + } + + public int hashCode() { + return Objects.hash(super.hashCode(), artifactCoords, projectRoot, buildDir, sourceSourceSet, sourceSet); + } +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/util/QuarkusModelHelper.java b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/util/QuarkusModelHelper.java new file mode 100644 index 0000000000000..c90a7d07ef4c8 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/main/java/io/quarkus/bootstrap/util/QuarkusModelHelper.java @@ -0,0 +1,179 @@ +package io.quarkus.bootstrap.util; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.BootstrapGradleException; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.model.PathsCollection; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.resolver.model.ArtifactCoords; +import io.quarkus.bootstrap.resolver.model.Dependency; +import io.quarkus.bootstrap.resolver.model.QuarkusModel; +import io.quarkus.bootstrap.resolver.model.WorkspaceModule; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import org.gradle.api.GradleException; + +public class QuarkusModelHelper { + + private QuarkusModelHelper() { + + } + + public final static String[] DEVMODE_REQUIRED_TASKS = new String[] { "classes" }; + + public static void exportModel(QuarkusModel model) throws AppModelResolverException, IOException { + Path serializedModel = QuarkusModelHelper + .serializeAppModel(model); + System.setProperty(BootstrapConstants.SERIALIZED_APP_MODEL, serializedModel.toString()); + } + + public static Path serializeAppModel(QuarkusModel model) throws AppModelResolverException, IOException { + final Path serializedModel = File.createTempFile("quarkus-app-model", ".dat").toPath(); + final ArtifactCoords artifactCoords = model.getWorkspace().getMainModule().getArtifactCoords(); + AppArtifact appArtifact = new AppArtifact(artifactCoords.getGroupId(), + artifactCoords.getArtifactId(), + artifactCoords.getVersion()); + try (ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(serializedModel))) { + out.writeObject(QuarkusModelHelper.convert(model, appArtifact)); + } + return serializedModel; + } + + public static Path getClassPath(WorkspaceModule model) throws BootstrapGradleException { + // TODO handle multiple class directory + final Optional classDir = model.getSourceSet().getSourceDirectories().stream().filter(File::exists) + .map(File::toPath).findFirst(); + if (!classDir.isPresent()) { + throw new BootstrapGradleException("Failed to locate class directory"); + } + return classDir.get(); + } + + public static AppModel convert(QuarkusModel model, AppArtifact appArtifact) throws AppModelResolverException { + AppModel.Builder appBuilder = new AppModel.Builder(); + + final List userDeps = new ArrayList<>(); + Map versionMap = new HashMap<>(); + model.getAppDependencies().stream().map(QuarkusModelHelper::toAppDependency).forEach(appDependency -> { + userDeps.add(appDependency); + versionMap.put(appDependency.getArtifact().getKey(), appDependency); + }); + + final List deploymentDeps = new ArrayList<>(); + for (Dependency extensionDependency : model.getExtensionDependencies()) { + AppDependency appDep = toAppDependency(extensionDependency); + for (Path artifactPath : appDep.getArtifact().getPaths()) { + if (!Files.exists(artifactPath) || !extensionDependency.getType().equals("jar")) { + continue; + } + if (Files.isDirectory(artifactPath)) { + processQuarkusDir(appDep.getArtifact(), artifactPath.resolve(BootstrapConstants.META_INF), + appBuilder); + } else { + try (FileSystem artifactFs = FileSystems.newFileSystem(artifactPath, + QuarkusModelHelper.class.getClassLoader())) { + processQuarkusDir(appDep.getArtifact(), artifactFs.getPath(BootstrapConstants.META_INF), + appBuilder); + } catch (IOException e) { + throw new AppModelResolverException("Failed to process " + artifactPath, e); + } + } + } + if (!userDeps.contains(appDep)) { + AppDependency deploymentDep = alignVersion(appDep, versionMap); + deploymentDeps.add(deploymentDep); + } + } + + final List fullDeploymentDeps = new ArrayList<>(userDeps); + fullDeploymentDeps.addAll(deploymentDeps); + + if (!appArtifact.isResolved()) { + PathsCollection.Builder paths = PathsCollection.builder(); + WorkspaceModule module = model.getWorkspace().getMainModule(); + module.getSourceSet().getSourceDirectories().stream().filter(File::exists).map(File::toPath) + .forEach(paths::add); + if (module.getSourceSet().getResourceDirectory().exists()) { + paths.add(module.getSourceSet().getResourceDirectory().toPath()); + } + appArtifact.setPaths(paths.build()); + } + + appBuilder.addRuntimeDeps(userDeps) + .addFullDeploymentDeps(fullDeploymentDeps) + .addDeploymentDeps(deploymentDeps) + .setAppArtifact(appArtifact); + return appBuilder.build(); + } + + public static AppDependency toAppDependency(Dependency dependency) { + AppArtifact artifact = new AppArtifact(dependency.getGroupId(), dependency.getName(), dependency.getClassifier(), + dependency.getType(), dependency.getVersion()); + artifact.setPaths(QuarkusModelHelper.toPathsCollection(dependency.getPaths())); + return new AppDependency(artifact, "runtime"); + } + + public static PathsCollection toPathsCollection(Collection files) { + PathsCollection.Builder paths = PathsCollection.builder(); + for (File f : files) { + paths.add(f.toPath()); + } + return paths.build(); + } + + public static Properties resolveDescriptor(final Path path) { + final Properties rtProps; + if (!Files.exists(path)) { + // not a platform artifact + return null; + } + rtProps = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(path)) { + rtProps.load(reader); + } catch (IOException e) { + throw new GradleException("Failed to load extension description " + path, e); + } + return rtProps; + } + + private static void processQuarkusDir(AppArtifact a, Path quarkusDir, AppModel.Builder appBuilder) { + if (!Files.exists(quarkusDir)) { + return; + } + final Path quarkusDescr = quarkusDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); + if (!Files.exists(quarkusDescr)) { + return; + } + final Properties extProps = QuarkusModelHelper.resolveDescriptor(quarkusDescr); + if (extProps == null) { + return; + } + appBuilder.handleExtensionProperties(extProps, a.toString()); + } + + static AppDependency alignVersion(AppDependency dependency, Map versionMap) { + AppArtifactKey appKey = new AppArtifactKey(dependency.getArtifact().getGroupId(), + dependency.getArtifact().getArtifactId()); + if (versionMap.containsKey(appKey)) { + return versionMap.get(appKey); + } + return dependency; + } + +} diff --git a/independent-projects/bootstrap/gradle-resolver/src/test/java/io/quarkus/bootstrap/util/QuarkusModelHelperTest.java b/independent-projects/bootstrap/gradle-resolver/src/test/java/io/quarkus/bootstrap/util/QuarkusModelHelperTest.java new file mode 100644 index 0000000000000..7626776874559 --- /dev/null +++ b/independent-projects/bootstrap/gradle-resolver/src/test/java/io/quarkus/bootstrap/util/QuarkusModelHelperTest.java @@ -0,0 +1,39 @@ +package io.quarkus.bootstrap.util; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.bootstrap.model.AppDependency; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class QuarkusModelHelperTest { + + @Test + public void shouldKeepExtensionDependencyVersion() { + Map userDependencies = new HashMap<>(); + + final AppArtifact appArtifact = new AppArtifact("org.acme", "common", "0.0.1-SNAPSHOT"); + AppDependency extensionDependency = new AppDependency(appArtifact, "runtime", false); + + final AppDependency dependency = QuarkusModelHelper.alignVersion(extensionDependency, userDependencies); + + Assertions.assertEquals(extensionDependency, dependency); + } + + @Test + public void shouldUseUserDependencyVersion() { + Map userDependencies = new HashMap<>(); + final AppArtifact userAppArtifact = new AppArtifact("org.acme", "common", "1.0.0-SNAPSHOT"); + final AppDependency userDependency = new AppDependency(userAppArtifact, "runtime", false); + userDependencies.put(new AppArtifactKey("org.acme", "common"), userDependency); + + final AppArtifact appArtifact = new AppArtifact("org.acme", "common", "0.0.1-SNAPSHOT"); + AppDependency extensionDependency = new AppDependency(appArtifact, "runtime", false); + + final AppDependency dependency = QuarkusModelHelper.alignVersion(extensionDependency, userDependencies); + + Assertions.assertEquals(userDependency, dependency); + } +} diff --git a/independent-projects/bootstrap/maven-resolver/pom.xml b/independent-projects/bootstrap/maven-resolver/pom.xml index fe2b7c931c8ef..52e96826928a5 100644 --- a/independent-projects/bootstrap/maven-resolver/pom.xml +++ b/independent-projects/bootstrap/maven-resolver/pom.xml @@ -18,6 +18,10 @@ io.quarkus quarkus-bootstrap-app-model + + org.jboss.slf4j + slf4j-jboss-logging + org.ow2.asm asm diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index cf0a68049ef45..49fc1342851ed 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -44,10 +44,12 @@ 8.0.1 1.2.6 1.0.4 + 1.2.0.Final 20.1.0 2.6.0 3.0.0-M5 1.1.0 + 6.5 app-model @@ -55,6 +57,7 @@ core maven-plugin runner + gradle-resolver @@ -100,6 +103,11 @@ quarkus-bootstrap-maven-plugin ${project.version} + + io.quarkus + quarkus-bootstrap-gradle-resolver + ${project.version} + org.apache.maven maven-plugin-api @@ -164,9 +172,23 @@ org.checkerframework checker-qual + + org.slf4j + slf4j-api + + + + + org.gradle + gradle-tooling-api + ${gradle-tooling.version} + + + org.slf4j + slf4j-api + - jakarta.annotation jakarta.annotation-api @@ -306,6 +328,11 @@ commons-logging-jboss-logging ${commons-logging-jboss-logging.version} + + org.jboss.slf4j + slf4j-jboss-logging + ${slf4j-jboss-logging.version} + org.codehaus.plexus plexus-classworlds @@ -490,6 +517,14 @@ + + + gradle-dependencies + Gradle releases repository + https://repo.gradle.org/gradle/libs-releases + + + quick-build diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java index feb7e3cd9a3c7..eeaca45d3c0d3 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusGradleWrapperTestBase.java @@ -1,7 +1,9 @@ package io.quarkus.gradle; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Paths; import java.util.Arrays; import java.util.LinkedList; @@ -19,13 +21,23 @@ public BuildResult runGradleWrapper(File projectDir, String... args) throws IOEx List command = new LinkedList<>(); command.add(getGradleWrapperCommand()); command.add(GRADLE_NO_DAEMON); + command.add("--stacktrace"); command.addAll(Arrays.asList(args)); + + File logOutput = new File(projectDir, "command-output.log"); + Process p = new ProcessBuilder() .directory(projectDir) .command(command) + .redirectInput(ProcessBuilder.Redirect.INHERIT) + .redirectOutput(logOutput) .start(); + p.waitFor(5, TimeUnit.MINUTES); - return BuildResult.of(p.getInputStream()); + + try (InputStream is = new FileInputStream(logOutput)) { + return BuildResult.of(is); + } } private String getGradleWrapperCommand() { diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java index 7702f9e1bca6d..4a7f71eb10c1a 100644 --- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java +++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginFunctionalTest.java @@ -134,6 +134,15 @@ public void canDetectOutputChangeWhenBuilding() throws IOException, InterruptedE assertThat(runnerJar).exists(); } + @Test + public void canRunTest() throws IOException, InterruptedException { + createProject(SourceType.JAVA); + + BuildResult buildResult = runGradleWrapper(projectRoot, "test", "--stacktrace"); + + assertThat(buildResult.getTasks().get(":test")).isEqualTo(BuildResult.SUCCESS_OUTCOME); + } + private void createProject(SourceType sourceType) throws IOException { Map context = new HashMap<>(); context.put("path", "/greeting"); diff --git a/integration-tests/gradle/src/test/resources/multi-module-kotlin-project/build.gradle b/integration-tests/gradle/src/test/resources/multi-module-kotlin-project/build.gradle index b6d57ccb49aba..4152ab9cf293c 100644 --- a/integration-tests/gradle/src/test/resources/multi-module-kotlin-project/build.gradle +++ b/integration-tests/gradle/src/test/resources/multi-module-kotlin-project/build.gradle @@ -4,7 +4,6 @@ plugins { id 'io.quarkus' } - dependencies { implementation(project(":port")) implementation(project(":domain")) diff --git a/pom.xml b/pom.xml index 3ebd96e594c75..aa8a3bb3b491e 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,11 @@ Maven Repository Switchboard https://repo.maven.apache.org/maven2 + + gradle-dependencies + Gradle releases repository + https://repo.gradle.org/gradle/libs-releases + diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java b/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java index 228684cdaf1c5..ded07182a3925 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java @@ -22,6 +22,10 @@ public final class PathTestHelper { TEST_TO_MAIN_DIR_FRAGMENTS.put( "bin" + File.separator + "test", "bin" + File.separator + "main"); + // idea + TEST_TO_MAIN_DIR_FRAGMENTS.put( + "out" + File.separator + "test", + "out" + File.separator + "production"); // gradle TEST_TO_MAIN_DIR_FRAGMENTS.put( "classes" + File.separator + "java" + File.separator + "native-test", diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 13ec67201e3d6..8d1c2a2110fd0 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -48,6 +48,7 @@ import org.junit.jupiter.api.extension.TestInstantiationException; import org.opentest4j.TestAbortedException; +import io.quarkus.bootstrap.BootstrapConstants; import io.quarkus.bootstrap.app.AugmentAction; import io.quarkus.bootstrap.app.CuratedApplication; import io.quarkus.bootstrap.app.QuarkusBootstrap; @@ -55,6 +56,7 @@ import io.quarkus.bootstrap.app.StartupAction; import io.quarkus.bootstrap.model.PathsCollection; import io.quarkus.bootstrap.runner.Timing; +import io.quarkus.bootstrap.utils.BuildToolHelper; import io.quarkus.builder.BuildChainBuilder; import io.quarkus.builder.BuildContext; import io.quarkus.builder.BuildStep; @@ -168,6 +170,12 @@ private ExtensionState doJavaStart(ExtensionContext context, Class