Skip to content

Commit

Permalink
feat: allow setting build/run options for app install (#1754)
Browse files Browse the repository at this point in the history
Fixes #1737
  • Loading branch information
quintesse committed Feb 22, 2024
1 parent b9f8412 commit 7380851
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 45 deletions.
115 changes: 75 additions & 40 deletions src/main/java/dev/jbang/cli/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -54,25 +51,43 @@ public static void deleteCommandFiles(String name) {
class AppInstall extends BaseCommand {
private static final String jbangUrl = "https://www.jbang.dev/releases/latest/download/jbang.zip";

@CommandLine.Option(names = {
"--native" }, description = "Enable native build/run")
boolean benative;

@CommandLine.Option(names = {
"--force" }, description = "Force re-installation")
boolean force;

@CommandLine.Option(names = { "--name" }, description = "A name for the command")
String name;

@CommandLine.Parameters(paramLabel = "scriptRef", description = "A file or URL to a Java code file or an alias")
String scriptRef;
@CommandLine.Mixin
ScriptMixin scriptMixin;

@CommandLine.Mixin
BuildMixin buildMixin;

@CommandLine.Mixin
DependencyInfoMixin dependencyInfoMixin;

@CommandLine.Mixin
NativeMixin nativeMixin;

@CommandLine.Mixin
JdkProvidersMixin jdkProvidersMixin;

@CommandLine.Mixin
RunMixin runMixin;

@CommandLine.Option(names = { "--enable-preview" }, description = "Activate Java preview features")
Boolean enablePreviewRequested;

@CommandLine.Parameters(index = "1..*", arity = "0..*", description = "Parameters to pass on to the script")
public List<String> userParams = new ArrayList<>();

@Override
public Integer doCall() {
scriptMixin.validate();
boolean installed = false;
try {
if (scriptRef.equals("jbang")) {
if (scriptMixin.scriptOrFile.equals("jbang")) {
if (name != null && !"jbang".equals(name)) {
throw new IllegalArgumentException(
"It's not possible to install jbang with a different name");
Expand All @@ -85,7 +100,8 @@ public Integer doCall() {
if (name != null && !CatalogUtil.isValidName(name)) {
throw new IllegalArgumentException("Not a valid command name: '" + name + "'");
}
installed = install(name, scriptRef, force, benative);
List<String> runOpts = collectRunOptions();
installed = install(name, scriptMixin.scriptOrFile, force, runOpts, userParams);
}
if (installed) {
if (AppSetup.needsSetup()) {
Expand All @@ -98,7 +114,22 @@ public Integer doCall() {
return EXIT_OK;
}

public static boolean install(String name, String scriptRef, boolean force, boolean benative) throws IOException {
private List<String> collectRunOptions() {
List<String> opts = new ArrayList<>();
opts.addAll(scriptMixin.opts());
opts.addAll(buildMixin.opts());
opts.addAll(dependencyInfoMixin.opts());
opts.addAll(nativeMixin.opts());
opts.addAll(jdkProvidersMixin.opts());
opts.addAll(runMixin.opts());
if (Boolean.TRUE.equals(enablePreviewRequested)) {
opts.add("--enable-preview");
}
return opts;
}

public static boolean install(String name, String scriptRef, boolean force, List<String> runOpts,
List<String> runArgs) throws IOException {
Path binDir = Settings.getConfigBinDir();
if (!force && name != null && existScripts(binDir, name)) {
Util.infoMsg("A script with name '" + name + "' already exists, use '--force' to install anyway.");
Expand All @@ -118,7 +149,7 @@ public static boolean install(String name, String scriptRef, boolean force, bool
scriptRef = prj.getResourceRef().getFile().toAbsolutePath().toString();
}
prj.codeBuilder().build();
installScripts(name, scriptRef, benative);
installScripts(name, scriptRef, runOpts, runArgs);
Util.infoMsg("Command installed: " + name);
return true;
}
Expand All @@ -128,27 +159,29 @@ private static boolean existScripts(Path binDir, String name) {
|| Files.exists(binDir.resolve(name + ".ps1"));
}

private static void installScripts(String name, String scriptRef, boolean benative) throws IOException {
private static void installScripts(String name, String scriptRef, List<String> runOpts, List<String> runArgs)
throws IOException {
Path binDir = Settings.getConfigBinDir();
binDir.toFile().mkdirs();
if (Util.isWindows()) {
installCmdScript(binDir.resolve(name + ".cmd"), scriptRef, benative);
installPSScript(binDir.resolve(name + ".ps1"), scriptRef, benative);
installCmdScript(binDir.resolve(name + ".cmd"), scriptRef, runOpts, runArgs);
installPSScript(binDir.resolve(name + ".ps1"), scriptRef, runOpts, runArgs);
// Script references on Linux/Mac should never contain backslashes
String nixRef = scriptRef.replace('\\', '/');
installShellScript(binDir.resolve(name), nixRef, benative);
installShellScript(binDir.resolve(name), nixRef, runOpts, runArgs);
} else {
installShellScript(binDir.resolve(name), scriptRef, benative);
installShellScript(binDir.resolve(name), scriptRef, runOpts, runArgs);
}
}

private static void installShellScript(Path file, String scriptRef, boolean benative) throws IOException {
CommandBuffer cb;
if (benative) {
cb = CommandBuffer.of("exec", "jbang", "run", "--native", scriptRef);
} else {
cb = CommandBuffer.of("exec", "jbang", "run", scriptRef);
}
private static void installShellScript(Path file, String scriptRef, List<String> runOpts, List<String> runArgs)
throws IOException {
List<String> cmd = new ArrayList<>();
cmd.addAll(Arrays.asList("exec", "jbang", "run"));
cmd.addAll(runOpts);
cmd.add(scriptRef);
cmd.addAll(runArgs);
CommandBuffer cb = CommandBuffer.of(cmd);
List<String> lines = Arrays.asList("#!/bin/sh", cb.asCommandLine(Util.Shell.bash) + " \"$@\"");
Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
if (!Util.isWindows()) {
Expand All @@ -168,24 +201,26 @@ private static void setExecutable(Path file) {
}
}

private static void installCmdScript(Path file, String scriptRef, boolean benative) throws IOException {
CommandBuffer cb;
if (benative) {
cb = CommandBuffer.of("jbang", "run", "--native", scriptRef);
} else {
cb = CommandBuffer.of("jbang", "run", scriptRef);
}
private static void installCmdScript(Path file, String scriptRef, List<String> runOpts, List<String> runArgs)
throws IOException {
List<String> cmd = new ArrayList<>();
cmd.addAll(Arrays.asList("jbang", "run"));
cmd.addAll(runOpts);
cmd.add(scriptRef);
cmd.addAll(runArgs);
CommandBuffer cb = CommandBuffer.of(cmd);
List<String> lines = Arrays.asList("@echo off", cb.asCommandLine(Util.Shell.cmd) + " %*");
Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
}

private static void installPSScript(Path file, String scriptRef, boolean benative) throws IOException {
CommandBuffer cb;
if (benative) {
cb = CommandBuffer.of("jbang", "run", "--native", scriptRef);
} else {
cb = CommandBuffer.of("jbang", "run", scriptRef);
}
private static void installPSScript(Path file, String scriptRef, List<String> runOpts, List<String> runArgs)
throws IOException {
List<String> cmd = new ArrayList<>();
cmd.addAll(Arrays.asList("jbang", "run"));
cmd.addAll(runOpts);
cmd.add(scriptRef);
cmd.addAll(runArgs);
CommandBuffer cb = CommandBuffer.of(cmd);
List<String> lines = Collections.singletonList(cb.asCommandLine(Util.Shell.powershell) + " @args");
Files.write(file, lines, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
}
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/dev/jbang/cli/BuildMixin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.jbang.cli;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -31,4 +32,33 @@ void setJavaVersion(String javaVersion) {

@CommandLine.Option(names = { "--manifest" }, parameterConsumer = KeyValueConsumer.class)
public Map<String, String> manifestOptions;

public List<String> opts() {
List<String> opts = new ArrayList<>();
if (javaVersion != null) {
opts.add("--java");
opts.add(javaVersion);
}
if (main != null) {
opts.add("--main");
opts.add(main);
}
if (module != null) {
opts.add("--module");
opts.add(module);
}
if (compileOptions != null) {
for (String c : compileOptions) {
opts.add("-C");
opts.add(c);
}
}
if (manifestOptions != null) {
for (Map.Entry<String, String> e : manifestOptions.entrySet()) {
opts.add("--manifest");
opts.add(e.getKey() + "=" + e.getValue());
}
}
return opts;
}
}
29 changes: 29 additions & 0 deletions src/main/java/dev/jbang/cli/DependencyInfoMixin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.jbang.cli;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -33,4 +34,32 @@ public Map<String, String> getProperties() {
return properties;
}

public List<String> opts() {
List<String> opts = new ArrayList<>();
if (properties != null) {
for (Map.Entry<String, String> e : properties.entrySet()) {
opts.add("-D");
opts.add(e.getKey() + "=" + e.getValue());
}
}
if (dependencies != null) {
for (String d : dependencies) {
opts.add("--deps");
opts.add(d);
}
}
if (repositories != null) {
for (String r : repositories) {
opts.add("--repos");
opts.add(r);
}
}
if (classpaths != null) {
for (String c : classpaths) {
opts.add("--cp");
opts.add(c);
}
}
return opts;
}
}
13 changes: 8 additions & 5 deletions src/main/java/dev/jbang/cli/JBang.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,15 @@ public String defaultValue(CommandLine.Model.ArgSpec argSpec) {
if (argSpec.isOption()
&& argSpec.defaultValue() == null
&& Util.isNullOrEmptyString(((CommandLine.Model.OptionSpec) argSpec).fallbackValue())) {
// First we check the full name, eg "app.list.format"
String key = argSpecKey(argSpec);
val = getValue(key);
if (val == null) {
// Finally we check the option name only, eg "format"
val = getValue(argOptName(argSpec));
// We skip all "app install" options
if (!key.startsWith("app.install.")) {
// First we check the full name, eg "app.list.format"
val = getValue(key);
if (val == null) {
// Finally we check the option name only, eg "format"
val = getValue(argOptName(argSpec));
}
}
}
return val;
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/dev/jbang/cli/JdkProvidersMixin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.jbang.cli;

import java.util.ArrayList;
import java.util.List;

import dev.jbang.net.JdkManager;
Expand All @@ -17,4 +18,15 @@ protected void initJdkProviders() {
JdkManager.initProvidersByName(jdkProviders);
}
}

public List<String> opts() {
List<String> opts = new ArrayList<>();
if (jdkProviders != null) {
for (String p : jdkProviders) {
opts.add("--jdk-providers");
opts.add(p);
}
}
return opts;
}
}
14 changes: 14 additions & 0 deletions src/main/java/dev/jbang/cli/NativeMixin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.jbang.cli;

import java.util.ArrayList;
import java.util.List;

import picocli.CommandLine;
Expand All @@ -12,4 +13,17 @@ public class NativeMixin {
@CommandLine.Option(names = { "-N", "--native-option" }, description = "Options to pass to the native image tool")
public List<String> nativeOptions;

public List<String> opts() {
List<String> opts = new ArrayList<>();
if (Boolean.TRUE.equals(nativeImage)) {
opts.add("--native");
}
if (nativeOptions != null) {
for (String n : nativeOptions) {
opts.add("-N");
opts.add(n);
}
}
return opts;
}
}
39 changes: 39 additions & 0 deletions src/main/java/dev/jbang/cli/RunMixin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.jbang.cli;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -38,4 +39,42 @@ public class RunMixin {
@CommandLine.Option(names = { "-i", "--interactive" }, description = "Activate interactive mode")
public Boolean interactive;

public List<String> opts() {
List<String> opts = new ArrayList<>();
if (javaRuntimeOptions != null) {
for (String r : javaRuntimeOptions) {
opts.add("-R");
opts.add(r);
}
}
if (flightRecorderString != null) {
opts.add("--jfr");
opts.add(flightRecorderString);
}
if (debugString != null) {
for (Map.Entry<String, String> e : debugString.entrySet()) {
opts.add("-d");
opts.add(e.getKey() + "=" + e.getValue());
}
}
if (Boolean.TRUE.equals(enableAssertions)) {
opts.add("--enableassertions");
}
if (Boolean.TRUE.equals(enableSystemAssertions)) {
opts.add("--enablesystemassertions");
}
if (javaAgentSlots != null) {
for (Map.Entry<String, String> e : javaAgentSlots.entrySet()) {
opts.add("--javaagent");
opts.add(e.getKey() + "=" + e.getValue());
}
}
if (Boolean.TRUE.equals(cds)) {
opts.add("--cds");
}
if (Boolean.TRUE.equals(interactive)) {
opts.add("--interactive");
}
return opts;
}
}

0 comments on commit 7380851

Please sign in to comment.