diff --git a/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java new file mode 100644 index 0000000000000..05ecc15b89efe --- /dev/null +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateJBangMojo.java @@ -0,0 +1,118 @@ +package io.quarkus.maven; + +import static org.fusesource.jansi.Ansi.ansi; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.repository.RemoteRepository; + +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.devtools.commands.CreateJBangProject; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; + +@Mojo(name = "create-jbang", requiresProject = false) +public class CreateJBangMojo extends AbstractMojo { + + @Parameter(property = "noJBangWrapper", defaultValue = "false") + private boolean noJBangWrapper; + + /** + * Group ID of the target platform BOM + */ + @Parameter(property = "platformGroupId", required = false) + private String bomGroupId; + + /** + * Artifact ID of the target platform BOM + */ + @Parameter(property = "platformArtifactId", required = false) + private String bomArtifactId; + + /** + * Version of the target platform BOM + */ + @Parameter(property = "platformVersion", required = false) + private String bomVersion; + + @Parameter(property = "extensions") + private Set extensions; + + @Parameter(property = "outputDirectory", defaultValue = "${basedir}/jbang-with-quarkus") + private File outputDirectory; + + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) + private List repos; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + + @Component + private RepositorySystem repoSystem; + + @Component + RemoteRepositoryManager remoteRepoManager; + + @Override + public void execute() throws MojoExecutionException { + try { + Files.createDirectories(outputDirectory.toPath()); + } catch (IOException e) { + throw new MojoExecutionException("Could not create directory " + outputDirectory, e); + } + + File projectRoot = outputDirectory; + final Path projectDirPath = projectRoot.toPath(); + + final MavenArtifactResolver mvn; + try { + mvn = MavenArtifactResolver.builder() + .setRepositorySystem(repoSystem) + .setRepositorySystemSession(repoSession) + .setRemoteRepositories(repos) + .setRemoteRepositoryManager(remoteRepoManager) + .build(); + } catch (Exception e) { + throw new MojoExecutionException("Failed to initialize Maven artifact resolver", e); + } + + final QuarkusPlatformDescriptor platform = CreateUtils.resolvePlatformDescriptor(bomGroupId, bomArtifactId, bomVersion, + mvn, getLog()); + + final CreateJBangProject createJBangProject = new CreateJBangProject(projectDirPath, platform) + .extensions(extensions) + .setValue("noJBangWrapper", noJBangWrapper); + + boolean success; + + try { + success = createJBangProject.execute().isSuccess(); + } catch (QuarkusCommandException e) { + throw new MojoExecutionException("Failed to generate JBang Quarkus project", e); + } + + if (success) { + getLog().info(""); + getLog().info("========================================================================"); + getLog().warn(ansi().a("Quarkus JBang project is an experimental feature.").toString()); + getLog().info("========================================================================"); + getLog().info(""); + } else { + throw new MojoExecutionException( + "Failed to generate JBang Quarkus project"); + } + } +} diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/java/src/{resource.class-name}.tpl.qute.java b/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/java/src/{resource.class-name}.tpl.qute.java index 03f1d5adaa194..6216124d66098 100644 --- a/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/java/src/{resource.class-name}.tpl.qute.java +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/code/jbang-resteasy-code/java/src/{resource.class-name}.tpl.qute.java @@ -1,11 +1,9 @@ //usr/bin/env jbang "$0" "$@" ; exit $? -//REPOS xamdk=https://xam.dk/maven {#for dep in dependencies} //DEPS {dep.formatted-ga}:{quarkus.version} {/for} -//JAVA_OPTIONS -Djava.util.logging.manager=org.jboss.logmanager.LogManager -//Q:CONFIG quarkus.swagger-ui.always-include=true +//JAVAC_OPTIONS -parameters import io.quarkus.runtime.Quarkus; import javax.enterprise.context.ApplicationScoped; diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang b/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang index 22fc978fa0fcd..adf596c88e062 100755 --- a/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang @@ -39,14 +39,14 @@ download() { fi } -abs_jbang_path=/usr/local/Cellar/jbang/0.51.1/libexec/bin/jbang.jar +abs_jbang_path=$(resolve_symlink $(absolute_path $0)) case "$(uname -s)" in Linux*) os=linux;; Darwin*) os=mac;; - CYGWIN*|MINGW*) + CYGWIN*|MINGW*|MSYS*) os=windows;; *) echo "Unsupported Operating System: $(uname -s)" 1>&2; exit 1;; esac @@ -58,6 +58,8 @@ case "$(uname -m)" in arch=x64;; aarch64) arch=aarch64;; + armv7l) + arch=arm;; *) echo "Unsupported Architecture: $(uname -m)" 1>&2; exit 1;; esac @@ -87,8 +89,9 @@ else rm -rf "$TDIR/urls/jbang" tar xf "$TDIR/urls/jbang.tar" -C "$TDIR/urls" if [ $retval -ne 0 ]; then echo "Error installing JBang" 1>&2; exit $retval; fi - rm -rf "$JBDIR/bin" - mv "$TDIR/urls/jbang/bin" "$JBDIR" + mkdir -p "$JBDIR/bin" + rm -f "$JBDIR/bin/jbang" "$JBDIR/bin"/jbang.* + cp -f "$TDIR/urls/jbang/bin"/* "$JBDIR/bin" fi eval "exec $JBDIR/bin/jbang $*" fi @@ -152,7 +155,9 @@ output=$(CLICOLOR_FORCE=1 ${JAVA_EXEC} ${JBANG_JAVA_OPTIONS} -classpath ${jarPat err=$? if [ $err -eq 255 ]; then eval "exec $output" -else +elif [ ! -z "$output" ]; then echo "$output" exit $err +else + exit $err fi diff --git a/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang.cmd b/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang.cmd index 3bb58d87be1a1..a1e5a74337a18 100644 --- a/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang.cmd +++ b/devtools/platform-descriptor-json/src/main/resources/codestarts/quarkus-jbang/tooling/jbang-wrapper/base/jbang.cmd @@ -29,8 +29,9 @@ if exist "%~dp0jbang.jar" ( if exist "%TDIR%\urls\jbang" ( rd /s /q "%TDIR%\urls\jbang" > nul 2>&1 ) powershell -NoProfile -ExecutionPolicy Bypass -NonInteractive -Command "$ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path %TDIR%\urls\jbang.zip -DestinationPath %TDIR%\urls" if !ERRORLEVEL! NEQ 0 ( echo Error installing JBang 1>&2 & exit /b %ERRORLEVEL% ) - if exist "%JBDIR%\bin" ( rd /s /q "%JBDIR%\bin" > nul 2>&1 ) - move "%TDIR%\urls\jbang\bin" "%JBDIR%" > nul 2>&1 + if not exist "%JBDIR%\bin" ( mkdir "%JBDIR%\bin" ) + del /f /q "%JBDIR%\bin\jbang" "%JBDIR%\bin\jbang.*" + copy /y "%TDIR%\urls\jbang\bin\*" "%JBDIR%\bin" > nul 2>&1 ) call "%JBDIR%\bin\jbang.cmd" %* exit /b %ERRORLEVEL% @@ -79,13 +80,14 @@ if "!JAVA_EXEC!"=="" ( ren "%TDIR%\jdks\%javaVersion%.tmp" "%javaVersion%" ) # Set the current JDK - !JAVA_EXEC! -classpath ${jarPath} dev.jbang.Main jdk default "%javaVersion%" + !JAVA_EXEC! -classpath "%jarPath%" dev.jbang.Main jdk default "%javaVersion%" ) ) if not exist "%TDIR%" ( mkdir "%TDIR%" ) set tmpfile=%TDIR%\%RANDOM%.jbang.tmp rem execute jbang and pipe to temporary random file +set JBANG_USES_POWERSHELL= set "CMD=!JAVA_EXEC!" SETLOCAL DISABLEDELAYEDEXPANSION %CMD% > "%tmpfile%" %JBANG_JAVA_OPTIONS% -classpath "%jarPath%" dev.jbang.Main %* @@ -99,11 +101,11 @@ if %ERROR% EQU 255 ( goto :break ) :break - del "%tmpfile%" + del /f /q "%tmpfile%" %OUTPUT% exit /b %ERRORLEVEL% ) else ( type "%tmpfile%" - del "%tmpfile%" + del /f /q "%tmpfile%" exit /b %ERROR% ) diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java new file mode 100644 index 0000000000000..8843f4f55cece --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/CreateJBangProject.java @@ -0,0 +1,55 @@ +package io.quarkus.devtools.commands; + +import static io.quarkus.devtools.project.codegen.ProjectGenerator.EXTENSIONS; +import static java.util.Objects.requireNonNull; + +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.commands.handlers.CreateJBangProjectCommandHandler; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProject; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class CreateJBangProject { + public static final String NAME = "create-jbang"; + + private final Path projectDirPath; + private final QuarkusPlatformDescriptor platformDescr; + private BuildTool buildTool = BuildTool.MAVEN; + + private Set extensions = new HashSet<>(); + private Map values = new HashMap<>(); + + public CreateJBangProject(Path projectDirPath, QuarkusPlatformDescriptor platformDescr) { + this.projectDirPath = requireNonNull(projectDirPath, "projectDirPath is required"); + this.platformDescr = requireNonNull(platformDescr, "platformDescr is required"); + } + + public CreateJBangProject extensions(Set extensions) { + if (extensions == null) { + return this; + } + this.extensions.addAll(extensions); + return this; + } + + public CreateJBangProject setValue(String name, Object value) { + if (value != null) { + values.put(name, value); + } + return this; + } + + public QuarkusCommandOutcome execute() throws QuarkusCommandException { + setValue(EXTENSIONS, extensions); + final QuarkusProject quarkusProject = QuarkusProject.of(projectDirPath, platformDescr, buildTool); + final QuarkusCommandInvocation invocation = new QuarkusCommandInvocation(quarkusProject, values); + return new CreateJBangProjectCommandHandler().execute(invocation); + } +} diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java new file mode 100644 index 0000000000000..34685da82dfc0 --- /dev/null +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/CreateJBangProjectCommandHandler.java @@ -0,0 +1,58 @@ +package io.quarkus.devtools.commands.handlers; + +import static io.quarkus.devtools.commands.handlers.QuarkusCommandHandlers.computeCoordsFromQuery; + +import io.quarkus.bootstrap.model.AppArtifactCoords; +import io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartCatalog; +import io.quarkus.devtools.codestarts.jbang.QuarkusJBangCodestartProjectInput; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandInvocation; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; +import io.quarkus.devtools.messagewriter.MessageIcons; +import io.quarkus.devtools.project.codegen.ProjectGenerator; +import io.quarkus.platform.descriptor.QuarkusPlatformDescriptor; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class CreateJBangProjectCommandHandler implements QuarkusCommandHandler { + @Override + public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException { + final Set extensionsQuery = invocation.getValue(ProjectGenerator.EXTENSIONS, Collections.emptySet()); + final List extensionsToAdd = computeCoordsFromQuery(invocation, extensionsQuery); + if (extensionsToAdd == null) { + throw new QuarkusCommandException("Failed to create project because of invalid extensions"); + } + + final QuarkusJBangCodestartProjectInput input = QuarkusJBangCodestartProjectInput.builder() + .addExtensions(extensionsToAdd) + .setNoJBangWrapper(invocation.getBooleanValue("noJBangWrapper")) + .putData("quarkus.version", invocation.getPlatformDescriptor().getBomVersion()) + .build(); + + final Path projectDir = invocation.getQuarkusProject().getProjectDirPath(); + try { + invocation.log().info("-----------"); + if (!extensionsToAdd.isEmpty()) { + invocation.log().info("selected extensions: \n" + + extensionsToAdd.stream().map(e -> "- " + e.getGroupId() + ":" + e.getArtifactId() + "\n") + .collect(Collectors.joining())); + } + getCatalog(invocation.getPlatformDescriptor()).createProject(input).generate(projectDir); + invocation.log() + .info("\n-----------\n" + MessageIcons.NOOP_ICON + + " jbang project has been successfully generated in:\n--> " + + invocation.getQuarkusProject().getProjectDirPath().toString() + "\n-----------"); + } catch (IOException e) { + throw new QuarkusCommandException("Failed to create JBang project", e); + } + return QuarkusCommandOutcome.success(); + } + + private QuarkusJBangCodestartCatalog getCatalog(QuarkusPlatformDescriptor platformDescriptor) throws IOException { + return QuarkusJBangCodestartCatalog.fromQuarkusPlatformDescriptor(platformDescriptor); + } +} diff --git a/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java new file mode 100644 index 0000000000000..805584083d5a7 --- /dev/null +++ b/integration-tests/devtools/src/test/java/io/quarkus/devtools/commands/CreateJBangProjectTest.java @@ -0,0 +1,83 @@ +package io.quarkus.devtools.commands; + +import static io.quarkus.devtools.ProjectTestUtil.checkContains; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import io.quarkus.devtools.PlatformAwareTestBase; +import io.quarkus.devtools.ProjectTestUtil; +import io.quarkus.devtools.commands.data.QuarkusCommandException; +import io.quarkus.devtools.commands.data.QuarkusCommandOutcome; + +public class CreateJBangProjectTest extends PlatformAwareTestBase { + @Test + public void createRESTEasy() throws Exception { + final File file = new File("target/jbang-resteasy"); + final Path projectDir = file.toPath(); + ProjectTestUtil.delete(file); + assertCreateJBangProject(newCreateJBangProject(projectDir) + .setValue("noJBangWrapper", false)); + + assertThat(projectDir.resolve("jbang")).exists(); + + assertThat(projectDir.resolve("src/GreetingResource.java")) + .exists() + .satisfies(checkContains("//usr/bin/env jbang \"$0\" \"$@\" ; exit $?")) + .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy")); + } + + @Test + public void createRESTEasyWithNoJBangWrapper() throws Exception { + final File file = new File("target/jbang-resteasy"); + final Path projectDir = file.toPath(); + ProjectTestUtil.delete(file); + assertCreateJBangProject(newCreateJBangProject(projectDir) + .setValue("noJBangWrapper", true)); + + assertThat(projectDir.resolve("jbang")).doesNotExist(); + + assertThat(projectDir.resolve("src/GreetingResource.java")) + .exists() + .satisfies(checkContains("//usr/bin/env jbang \"$0\" \"$@\" ; exit $?")) + .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy")); + } + + @Test + public void createRESTEasyWithExtensions() throws Exception { + final File file = new File("target/jbang-resteasy"); + final Path projectDir = file.toPath(); + ProjectTestUtil.delete(file); + Set extensions = new HashSet<>(); + extensions.add("resteasy-jsonb"); + + assertCreateJBangProject(newCreateJBangProject(projectDir) + .extensions(extensions) + .setValue("noJBangWrapper", false)); + + assertThat(projectDir.resolve("jbang")).exists(); + + assertThat(projectDir.resolve("src/GreetingResource.java")) + .exists() + .satisfies(checkContains("//usr/bin/env jbang \"$0\" \"$@\" ; exit $?")) + .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy")) + .satisfies(checkContains("//DEPS io.quarkus:quarkus-resteasy-jsonb")); + } + + private CreateJBangProject newCreateJBangProject(Path dir) { + return new CreateJBangProject(dir, getPlatformDescriptor()); + } + + private void assertCreateJBangProject(CreateJBangProject createJBangProjectProject) + throws QuarkusCommandException { + final QuarkusCommandOutcome result = createJBangProjectProject + .execute(); + assertTrue(result.isSuccess()); + } +} diff --git a/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java new file mode 100644 index 0000000000000..90421d68ada50 --- /dev/null +++ b/integration-tests/maven/src/test/java/io/quarkus/maven/it/CreateJBangProjectMojoIT.java @@ -0,0 +1,62 @@ +package io.quarkus.maven.it; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.*; +import java.util.Collections; +import java.util.Properties; + +import org.apache.maven.shared.invoker.DefaultInvocationRequest; +import org.apache.maven.shared.invoker.InvocationRequest; +import org.apache.maven.shared.invoker.InvocationResult; +import org.apache.maven.shared.invoker.Invoker; +import org.apache.maven.shared.invoker.InvokerLogger; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.apache.maven.shared.invoker.PrintStreamLogger; +import org.junit.jupiter.api.Test; + +import io.quarkus.maven.it.verifier.RunningInvoker; +import io.quarkus.platform.tools.ToolsConstants; + +@DisableForNative +public class CreateJBangProjectMojoIT extends QuarkusPlatformAwareMojoTestBase { + + private Invoker invoker; + private RunningInvoker running; + private File testDir; + + @Test + public void testProjectGeneration() throws MavenInvocationException, IOException { + testDir = initEmptyProject("projects/project-generation"); + assertThat(testDir).isDirectory(); + invoker = initInvoker(testDir); + + Properties properties = new Properties(); + properties.put("outputDirectory", "jbang"); + InvocationResult result = setup(properties); + + assertThat(result.getExitCode()).isZero(); + } + + private InvocationResult setup(Properties params) + throws MavenInvocationException, FileNotFoundException, UnsupportedEncodingException { + + params.setProperty("platformGroupId", ToolsConstants.IO_QUARKUS); + params.setProperty("platformArtifactId", "quarkus-bom"); + params.setProperty("platformVersion", getQuarkusCoreVersion()); + + InvocationRequest request = new DefaultInvocationRequest(); + request.setBatchMode(true); + request.setGoals(Collections.singletonList( + getMavenPluginGroupId() + ":" + getMavenPluginArtifactId() + ":" + getMavenPluginVersion() + ":create-jbang")); + request.setDebug(false); + request.setShowErrors(true); + request.setProperties(params); + getEnv().forEach(request::addShellEnvironment); + File log = new File(testDir, "build-create-" + testDir.getName() + ".log"); + PrintStreamLogger logger = new PrintStreamLogger(new PrintStream(new FileOutputStream(log), false, "UTF-8"), + InvokerLogger.DEBUG); + invoker.setLogger(logger); + return invoker.execute(request); + } +}