diff --git a/src/main/java/rife/bld/BuildExecutor.java b/src/main/java/rife/bld/BuildExecutor.java index 30a3aa0..61872c1 100644 --- a/src/main/java/rife/bld/BuildExecutor.java +++ b/src/main/java/rife/bld/BuildExecutor.java @@ -9,11 +9,12 @@ import rife.bld.operations.HelpOperation; import rife.bld.operations.exceptions.ExitStatusException; import rife.ioc.HierarchicalProperties; +import rife.template.Template; +import rife.template.TemplateFactory; import rife.tools.ExceptionUtils; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; +import java.io.*; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; @@ -38,6 +39,7 @@ public class BuildExecutor { private static final String ARG_HELP3 = "-?"; private static final String ARG_STACKTRACE1 = "--stacktrace"; private static final String ARG_STACKTRACE2 = "-s"; + private static final String ARG_JSON = "--json"; private final HierarchicalProperties properties_; private List arguments_ = Collections.emptyList(); @@ -46,6 +48,7 @@ public class BuildExecutor { private final AtomicReference currentCommandName_ = new AtomicReference<>(); private final AtomicReference currentCommandDefinition_ = new AtomicReference<>(); private int exitStatus_ = 0; + private boolean outputJson_ = false; /** * Show the full Java stacktrace when exceptions occur, as opposed @@ -124,6 +127,17 @@ public static HierarchicalProperties setupProperties(File workDirectory) { return properties; } + /** + * Returns whether output should be in the JSON format. + * + * @return {@code true} if JSON output is enabled; + * or {@code false} otherwise + * @since 2.0.0 + */ + public boolean outputJson() { + return outputJson_; + } + /** * Returns the properties uses by this conversation. * @@ -213,9 +227,13 @@ public int exitStatus() { public int execute(String[] arguments) { arguments_ = new ArrayList<>(Arrays.asList(arguments)); + if (!arguments_.isEmpty() && arguments_.get(0).equals(ARG_JSON)) { + outputJson_ = true; + arguments_.remove(0); + } var show_help = false; show_help |= arguments_.removeAll(List.of(ARG_HELP1, ARG_HELP2, ARG_HELP3)); - showStacktrace |= arguments_.removeAll(List.of(ARG_STACKTRACE1, ARG_STACKTRACE2)); + showStacktrace = arguments_.removeAll(List.of(ARG_STACKTRACE1, ARG_STACKTRACE2)); show_help |= arguments_.isEmpty(); if (show_help) { @@ -223,41 +241,105 @@ public int execute(String[] arguments) { return exitStatus_; } + var first_command = true; + var json_template = TemplateFactory.JSON.get("bld.executor_execute"); + var exception_caught = false; while (!arguments_.isEmpty()) { var command = arguments_.remove(0); try { - if (!executeCommand(command)) { - break; + var orig_out = System.out; + var orig_err = System.err; + var out = new ByteArrayOutputStream(); + var err = new ByteArrayOutputStream(); + if (outputJson()) { + System.setOut(new PrintStream(out, true, StandardCharsets.UTF_8)); + System.setErr(new PrintStream(err, true, StandardCharsets.UTF_8)); + } + + try { + if (!executeCommand(command)) { + break; + } + } + finally { + if (outputJson()) { + if (first_command) { + json_template.blankValue("separator"); + } + else { + json_template.setValue("separator", ", "); + } + json_template.setValueEncoded("command", command); + json_template.setValueEncoded("out", out.toString(StandardCharsets.UTF_8)); + json_template.setValueEncoded("err", err.toString(StandardCharsets.UTF_8)); + json_template.appendBlock("commands", "command"); + + System.setOut(orig_out); + System.setErr(orig_err); + } + + first_command = false; } } catch (Throwable e) { - exitStatus(1); + exception_caught = true; - System.err.println(); + exitStatus(1); - if (showStacktrace) { - System.err.println(ExceptionUtils.getExceptionStackTrace(e)); - } else { - boolean first = true; - var e2 = e; - while (e2 != null) { - if (e2.getMessage() != null) { - if (!first) { - System.err.print("> "); + if (outputJson()) { + var t = TemplateFactory.JSON.get("bld.executor_error"); + if (showStacktrace) { + t.setValueEncoded("error-message", ExceptionUtils.getExceptionStackTrace(e)); + } + else { + boolean first_exception = true; + var e2 = e; + while (e2 != null) { + if (e2.getMessage() != null) { + t.setValueEncoded("error-message", e2.getMessage()); + first_exception = false; } - System.err.println(e2.getMessage()); - first = false; + e2 = e2.getCause(); + } + + if (first_exception) { + t.setValueEncoded("error-message", ExceptionUtils.getExceptionStackTrace(e)); } - e2 = e2.getCause(); } + System.out.println(t.getContent()); + } + else { + System.err.println(); - if (first) { + if (showStacktrace) { System.err.println(ExceptionUtils.getExceptionStackTrace(e)); + } else { + boolean first_exception = true; + var e2 = e; + while (e2 != null) { + if (e2.getMessage() != null) { + if (!first_exception) { + System.err.print("> "); + } + System.err.println(e2.getMessage()); + first_exception = false; + } + e2 = e2.getCause(); + } + + if (first_exception) { + System.err.println(ExceptionUtils.getExceptionStackTrace(e)); + } } } } } + if (!exception_caught) { + if (outputJson()) { + System.out.println(json_template.getContent()); + } + } return exitStatus_; } @@ -441,7 +523,9 @@ public boolean executeCommand(String command) // only proceed if exactly one match was found if (matches.size() == 1) { matched_command = matches.get(0); - System.out.println("Executing matched command: " + matched_command); + if (!outputJson()) { + System.out.println("Executing matched command: " + matched_command); + } definition = buildCommands().get(matched_command); } } @@ -460,9 +544,17 @@ public boolean executeCommand(String command) currentCommandName_.set(null); } } else { - new HelpOperation(this, arguments()).executePrintOverviewHelp(); - System.err.println(); - System.err.println("ERROR: unknown command '" + command + "'"); + var message = "Unknown command '" + command + "'"; + if (outputJson()) { + var t = TemplateFactory.JSON.get("bld.executor_error"); + t.setValueEncoded("error-message", message); + System.out.println(t.getContent()); + } + else { + new HelpOperation(this, arguments()).executePrintOverviewHelp(); + System.err.println(); + System.err.println("ERROR: " + message); + } exitStatus(1); return false; } diff --git a/src/main/java/rife/bld/operations/DownloadOperation.java b/src/main/java/rife/bld/operations/DownloadOperation.java index 42bd326..c9dfcbf 100644 --- a/src/main/java/rife/bld/operations/DownloadOperation.java +++ b/src/main/java/rife/bld/operations/DownloadOperation.java @@ -127,6 +127,7 @@ protected void executeDownloadDependencies(File destinationDirectory, Dependency * Configures a compile operation from a {@link BaseProject}. * * @param project the project to configure the compile operation from + * @return this operation instance * @since 1.5 */ public DownloadOperation fromProject(BaseProject project) { diff --git a/src/main/java/rife/bld/operations/HelpOperation.java b/src/main/java/rife/bld/operations/HelpOperation.java index 828430a..8da4c3f 100644 --- a/src/main/java/rife/bld/operations/HelpOperation.java +++ b/src/main/java/rife/bld/operations/HelpOperation.java @@ -6,6 +6,8 @@ import rife.bld.BldVersion; import rife.bld.BuildExecutor; +import rife.template.TemplateFactory; +import rife.tools.ExceptionUtils; import java.util.List; @@ -44,26 +46,37 @@ public void execute() { topic = arguments_.remove(0); } - System.err.println("Welcome to bld " + BldVersion.getVersion() + "."); - System.err.println(); + if (!executor_.outputJson()) { + System.err.println("Welcome to bld " + BldVersion.getVersion() + "."); + System.err.println(); + } boolean print_full_help = true; + Exception exception = null; try { var commands = executor_.buildCommands(); if (commands.containsKey(topic)) { var command = commands.get(topic); var help = command.getHelp().getDescription(topic); if (!help.isEmpty()) { - System.err.println(help); + if (executor_.outputJson()) { + var t = TemplateFactory.JSON.get("bld.help_description"); + t.setValueEncoded("command", topic); + t.setValueEncoded("description", help); + System.out.print(t.getContent()); + } + else { + System.err.println(help); + } print_full_help = false; } } } catch (Exception e) { - e.printStackTrace(); + exception = e; } if (print_full_help) { - executePrintOverviewHelp(); + executePrintOverviewHelp(exception); } } @@ -74,32 +87,67 @@ public void execute() { * @since 1.5 */ public void executePrintOverviewHelp() { + executePrintOverviewHelp(null); + } + + private void executePrintOverviewHelp(Exception exception) { var commands = executor_.buildCommands(); - System.err.println(""" - The bld CLI provides its features through a series of commands that - perform specific tasks. The help command provides more information about - the other commands. - - Usage: help [command] - - The following commands are supported. - """); - - var command_length = commands.keySet().stream().max(comparingInt(String::length)).get().length() + 2; - for (var command : commands.entrySet()) { - System.err.print(" "); - System.err.printf("%-" + command_length + "s", command.getKey()); - var build_help = command.getValue().getHelp(); - System.err.print(build_help.getSummary()); - System.err.println(); + if (executor_.outputJson()) { + var t = TemplateFactory.JSON.get("bld.help_commands"); + + if (exception != null) { + t.setValueEncoded("error-message", ExceptionUtils.getExceptionStackTrace(exception)); + } + + boolean first = true; + for (var command : commands.entrySet()) { + if (first) { + first = false; + t.blankValue("separator"); + } + else { + t.setValue("separator", ", "); + } + t.setValueEncoded("command", command.getKey()); + var build_help = command.getValue().getHelp(); + t.setValueEncoded("summary", build_help.getSummary()); + t.appendBlock("commands", "command"); + } + System.out.print(t.getContent()); + } + else { + if (exception != null) { + exception.printStackTrace(); + } - System.err.println(""" - - -?, -h, --help Shows this help message - -D= Set a JVM system property - -s, --stacktrace Print out the stacktrace for exceptions - """); + System.err.println(""" + The bld CLI provides its features through a series of commands that + perform specific tasks. The help command provides more information about + the other commands. + + Usage: help [command] + + The following commands are supported. + """); + + var command_length = commands.keySet().stream().max(comparingInt(String::length)).get().length() + 2; + for (var command : commands.entrySet()) { + System.err.print(" "); + System.err.printf("%-" + command_length + "s", command.getKey()); + var build_help = command.getValue().getHelp(); + System.err.print(build_help.getSummary()); + System.err.println(); + } + + System.err.println(""" + + -?, -h, --help Shows this help message + -D= Set a JVM system property + -s, --stacktrace Print out the stacktrace for exceptions + --json Output in JSON format (only as first argument) + """); + } } } \ No newline at end of file diff --git a/src/main/java/rife/bld/operations/PublishOperation.java b/src/main/java/rife/bld/operations/PublishOperation.java index 0acde72..5d65872 100644 --- a/src/main/java/rife/bld/operations/PublishOperation.java +++ b/src/main/java/rife/bld/operations/PublishOperation.java @@ -604,7 +604,7 @@ public PublishOperation dependencies(DependencyScopes dependencies) { * * @param properties the publication properties * @return this operation instance - * @since 1.9.2 + * @since 2.0.0 */ public PublishOperation properties(PublishProperties properties) { properties_ = properties; @@ -691,7 +691,7 @@ public DependencyScopes dependencies() { * This is a modifiable structure that can be retrieved and changed. * * @return the publication properties - * @since 1.9.2 + * @since 2.0.0 */ public PublishProperties properties() { return properties_; diff --git a/src/main/java/rife/bld/operations/VersionOperation.java b/src/main/java/rife/bld/operations/VersionOperation.java index 0923f31..1195a30 100644 --- a/src/main/java/rife/bld/operations/VersionOperation.java +++ b/src/main/java/rife/bld/operations/VersionOperation.java @@ -20,14 +20,6 @@ public class VersionOperation extends AbstractOperation { */ public void execute() { if (!silent()) { - System.out.println(""" - _ _ _ - | | | | | | - | |__ | | __| | - | '_ \\| |/ _` | - | |_) | | (_| | - |_.__/|_|\\__,_| - """); System.out.println("bld " + BldVersion.getVersion()); } } diff --git a/src/main/java/rife/bld/publish/PomBuilder.java b/src/main/java/rife/bld/publish/PomBuilder.java index c03269a..1cca52f 100644 --- a/src/main/java/rife/bld/publish/PomBuilder.java +++ b/src/main/java/rife/bld/publish/PomBuilder.java @@ -52,7 +52,7 @@ public PublishInfo info() { * * @param properties the properties to use * @return this {@code PomBuilder} instance - * @since 1.9.2 + * @since 2.0.0 */ public PomBuilder properties(PublishProperties properties) { properties_ = properties; @@ -63,7 +63,7 @@ public PomBuilder properties(PublishProperties properties) { * Retrieves the properties to build the POM with. * * @return the properties to use - * @since 1.9.2 + * @since 2.0.0 */ public PublishProperties properties() { return properties_; diff --git a/src/main/java/rife/bld/publish/PublishProperties.java b/src/main/java/rife/bld/publish/PublishProperties.java index bc6fd6d..a97f404 100644 --- a/src/main/java/rife/bld/publish/PublishProperties.java +++ b/src/main/java/rife/bld/publish/PublishProperties.java @@ -10,7 +10,7 @@ * Provides the properties information for publication. * * @author Geert Bevin (gbevin[remove] at uwyn dot com) - * @since 1.9.2 + * @since 2.0.0 */ public class PublishProperties extends LinkedHashMap { private static final String MAVEN_COMPILER_SOURCE = "maven.compiler.source"; @@ -21,7 +21,7 @@ public class PublishProperties extends LinkedHashMap { * * @param value the value to be set for the 'maven.compiler.source' property * @return this {@code PomProperties} instance - * @since 1.9.2 + * @since 2.0.0 */ public PublishProperties mavenCompilerSource(Integer value) { if (value == null) { @@ -37,7 +37,7 @@ public PublishProperties mavenCompilerSource(Integer value) { * Retrieves the value of the 'maven.compiler.source' property. * * @return the value of the 'maven.compiler.source' property - * @since 1.9.2 + * @since 2.0.0 */ public Integer mavenCompilerSource() { var value = get(MAVEN_COMPILER_SOURCE); @@ -52,7 +52,7 @@ public Integer mavenCompilerSource() { * * @param value the value to be set for the 'maven.compiler.target' property * @return this {@code PomProperties} instance - * @since 1.9.2 + * @since 2.0.0 */ public PublishProperties mavenCompilerTarget(Integer value) { if (value == null) { @@ -68,7 +68,7 @@ public PublishProperties mavenCompilerTarget(Integer value) { * Retrieves the value of the 'maven.compiler.target' property. * * @return the value of the 'maven.compiler.target' property - * @since 1.9.2 + * @since 2.0.0 */ public Integer mavenCompilerTarget() { var value = get(MAVEN_COMPILER_TARGET); diff --git a/src/main/java/rife/bld/wrapper/Wrapper.java b/src/main/java/rife/bld/wrapper/Wrapper.java index aa79eca..be5941f 100644 --- a/src/main/java/rife/bld/wrapper/Wrapper.java +++ b/src/main/java/rife/bld/wrapper/Wrapper.java @@ -33,7 +33,14 @@ * @since 1.5 */ public class Wrapper { + private enum LaunchMode { + Cli, + Build, + Json + } + public static final String BUILD_ARGUMENT = "--build"; + public static final String JSON_ARGUMENT = "--json"; static final String MAVEN_CENTRAL = "https://repo1.maven.org/maven2/"; static final String SONATYPE_SNAPSHOTS = "https://s01.oss.sonatype.org/content/repositories/snapshots/"; @@ -59,10 +66,12 @@ public class Wrapper { static final String PROPERTY_JAVA_OPTIONS = "bld.javaOptions"; static final File BLD_USER_DIR = new File(System.getProperty("user.home"), ".bld"); static final File DISTRIBUTIONS_DIR = new File(BLD_USER_DIR, "dist"); + static final Pattern META_DATA_LOCAL_COPY = Pattern.compile("\\s*true\\s*", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); static final Pattern META_DATA_SNAPSHOT_VERSION = Pattern.compile(".*?([^<]+)", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); static final Pattern OPTIONS_PATTERN = Pattern.compile("\"[^\"]+\"|\\S+"); private File currentDir_ = new File(System.getProperty("user.dir")); + private LaunchMode launchMode_ = LaunchMode.Cli; private final Properties wrapperProperties_ = new Properties(); private File wrapperPropertiesFile_ = null; @@ -198,6 +207,7 @@ private void createWrapperJar(File destinationDirectory) try (var jar = new JarOutputStream(new FileOutputStream(new File(destinationDirectory, WRAPPER_JAR)), manifest)) { addClassToJar(jar, Wrapper.class); + addClassToJar(jar, Wrapper.LaunchMode.class); addClassToJar(jar, WrapperClassLoader.class); addClassToJar(jar, FileUtils.class); addClassToJar(jar, FileUtilsErrorException.class); @@ -264,7 +274,17 @@ private int installAndLaunch(List arguments) { throw new RuntimeException(e); } currentDir_ = new File(current_file.getParent()); + + if (BUILD_ARGUMENT.equals(arguments.get(0))) { + launchMode_ = LaunchMode.Build; + arguments.remove(0); + + if (arguments.size() >= 2 && JSON_ARGUMENT.equals(arguments.get(1))) { + launchMode_ = LaunchMode.Json; + } + } } + try { initWrapperProperties(getVersion()); File distribution; @@ -409,11 +429,27 @@ private File installDistribution() var download_version = version; var is_snapshot = isSnapshot(version); + var is_local = false; if (is_snapshot) { - var meta_data = readString(version, new URL(downloadUrl(version, "maven-metadata.xml"))); - var matcher = META_DATA_SNAPSHOT_VERSION.matcher(meta_data); - if (matcher.find()) { - download_version = matcher.group(1); + var meta_data = ""; + try { + meta_data = readString(version, new URL(downloadUrl(version, "maven-metadata.xml"))); + } + catch (IOException e) { + try { + meta_data = readString(version, new URL(downloadUrl(version, "maven-metadata-local.xml"))); + } + catch (IOException e2) { + throw e; + } + } + var local_matcher = META_DATA_LOCAL_COPY.matcher(meta_data); + is_local = local_matcher.find(); + if (!is_local) { + var version_matcher = META_DATA_SNAPSHOT_VERSION.matcher(meta_data); + if (version_matcher.find()) { + download_version = version_matcher.group(1); + } } } @@ -423,16 +459,26 @@ private File installDistribution() // if this is a snapshot and the distribution file exists, // ensure that it's the latest by comparing hashes if (is_snapshot && distribution_file.exists()) { - var download_md5 = readString(version, new URL(downloadUrl(version, bldFileName(download_version)) + ".md5")); - try { - var digest = MessageDigest.getInstance("MD5"); - digest.update(FileUtils.readBytes(distribution_file)); - if (!download_md5.equals(encodeHexLower(digest.digest()))) { - distribution_file.delete(); + boolean delete_distribution_files = is_local; + if (!delete_distribution_files) { + var download_md5 = readString(version, new URL(downloadUrl(version, bldFileName(download_version)) + ".md5")); + try { + var digest = MessageDigest.getInstance("MD5"); + digest.update(FileUtils.readBytes(distribution_file)); + if (!download_md5.equals(encodeHexLower(digest.digest()))) { + delete_distribution_files = true; + } + } catch (NoSuchAlgorithmException ignore) { + } + } + + if (delete_distribution_files) { + distribution_file.delete(); + if (distribution_sources_file.exists()) { distribution_sources_file.delete(); } - } catch (NoSuchAlgorithmException ignore) { } + } // download distribution jars if necessary @@ -457,15 +503,19 @@ private File installDistribution() private void downloadDistribution(File file, String downloadUrl) throws IOException { try { - System.out.print("Downloading: " + downloadUrl + " ... "); - System.out.flush(); + if (launchMode_ != LaunchMode.Json) { + System.out.print("Downloading: " + downloadUrl + " ... "); + System.out.flush(); + } var url = new URL(downloadUrl); var readableByteChannel = Channels.newChannel(url.openStream()); try (var fileOutputStream = new FileOutputStream(file)) { var fileChannel = fileOutputStream.getChannel(); fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE); - System.out.print("done"); + if (launchMode_ != LaunchMode.Json) { + System.out.print("done"); + } } } catch (FileNotFoundException e) { System.err.println("not found"); @@ -477,7 +527,9 @@ private void downloadDistribution(File file, String downloadUrl) Files.deleteIfExists(file.toPath()); throw e; } finally { - System.out.println(); + if (launchMode_ != LaunchMode.Json) { + System.out.println(); + } } } @@ -504,7 +556,7 @@ private void resolveExtensions() { private int launchMain(File jarFile, List arguments) throws IOException, InterruptedException, FileUtilsErrorException { - if (arguments.isEmpty() || !BUILD_ARGUMENT.equals(arguments.get(0))) { + if (launchMode_ == LaunchMode.Cli) { return launchMainCli(jarFile, arguments); } return launchMainBuild(jarFile, arguments); @@ -535,8 +587,6 @@ private int launchMainBuild(File jarFile, List arguments) throws IOException, InterruptedException { resolveExtensions(); - arguments.remove(0); - var build_bld_dir = buildBldDirectory(); if (build_bld_dir.exists()) { FileUtils.deleteDirectory(buildBldDirectory()); diff --git a/src/main/resources/BLD_VERSION b/src/main/resources/BLD_VERSION index 54d070c..155b9cb 100644 --- a/src/main/resources/BLD_VERSION +++ b/src/main/resources/BLD_VERSION @@ -1 +1 @@ -1.9.2-SNAPSHOT \ No newline at end of file +2.0.0-SNAPSHOT \ No newline at end of file diff --git a/src/main/resources/templates/bld/executor_error.json b/src/main/resources/templates/bld/executor_error.json new file mode 100644 index 0000000..3d62fa2 --- /dev/null +++ b/src/main/resources/templates/bld/executor_error.json @@ -0,0 +1,3 @@ +{ + "error": "{{v error-message/}}" +} diff --git a/src/main/resources/templates/bld/executor_execute.json b/src/main/resources/templates/bld/executor_execute.json new file mode 100644 index 0000000..5aaf68b --- /dev/null +++ b/src/main/resources/templates/bld/executor_execute.json @@ -0,0 +1,8 @@ +{ + "commands": {{{v commands}}{{/v}}{{b command}}{{v separator/}} + "{{v command/}}": { + "out": "{{v out/}}", + "err": "{{v err/}}" + }{{/b}} + } +} diff --git a/src/main/resources/templates/bld/help_commands.json b/src/main/resources/templates/bld/help_commands.json new file mode 100644 index 0000000..1ad3fc9 --- /dev/null +++ b/src/main/resources/templates/bld/help_commands.json @@ -0,0 +1,6 @@ +{ + {{v error}}{{/v}}{{b error}}"error": "{{v error-message/}}", + {{/b}}"commands": {{{v commands}}{{/v}}{{b command}}{{v separator/}} + "{{v command/}}": "{{v summary/}}"{{/b}} + } +} diff --git a/src/main/resources/templates/bld/help_description.json b/src/main/resources/templates/bld/help_description.json new file mode 100644 index 0000000..14ac6ab --- /dev/null +++ b/src/main/resources/templates/bld/help_description.json @@ -0,0 +1,5 @@ +{ + "description": { + "{{v command/}}": "{{v description/}}" + } +}