├── .bach ├── .gitignore ├── README.md ├── install.jshell └── src │ └── run.bach │ ├── module-info.java │ └── run │ ├── Build.java │ ├── Clean.java │ ├── Format.java │ ├── Prepare.java │ ├── Project.java │ ├── Start.java │ ├── Status.java │ ├── Test.java │ └── demo │ ├── GoogleJavaFormat.java │ ├── Greeter.java │ ├── JResolve.java │ ├── JResolveDemo.java │ ├── ModuleResolverDemo.java │ ├── ToolFinderDemo.java │ ├── ToolSpaceDemo.java │ └── ToolVersionsDemo.java ├── .github ├── EMPTY └── workflows │ └── ci.yml ├── .gitmodules ├── .idea ├── .gitignore ├── bach.iml ├── compiler.xml ├── encodings.xml ├── icon.svg ├── libraries │ └── lib.xml ├── misc.xml ├── modules.xml ├── project.iml ├── run.bach.iml ├── test.bach.iml ├── test.junit.iml └── vcs.xml ├── LICENSE ├── README.md ├── bach ├── build ├── doc ├── README.md └── installing.md ├── lib └── .gitignore └── src ├── README.md ├── bach.run ├── Bach.java ├── Hi.java ├── Ho.java ├── README.md └── install.jshell ├── test.bach └── test │ └── java │ ├── module-info.java │ └── test │ └── bach │ ├── Tests.java │ └── workflow │ └── WorkflowTests.java └── test.junit └── test └── java ├── module-info.java └── test └── junit ├── JUnitPioneerTests.java └── JUnitTests.java /.bach/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /out/ 3 | /tmp/ 4 | /var/ 5 | 6 | *.jfr 7 | -------------------------------------------------------------------------------- /.bach/README.md: -------------------------------------------------------------------------------- 1 | # Directory `.bach` 2 | 3 | The `.bach` directory contains all Bach-related assets. 4 | -------------------------------------------------------------------------------- /.bach/install.jshell: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Christian Stein 3 | * Licensed under the Universal Permissive License v 1.0 -> https://opensource.org/license/upl 4 | */ 5 | 6 | System.out.printf( 7 | """ 8 | ___ ___ ___ ___ 9 | /\\ \\ /\\ \\ /\\ \\ /\\__\\ 10 | /::\\ \\ /::\\ \\ /::\\ \\ /:/__/_ 11 | /::\\:\\__\\/::\\:\\__\\/:/\\:\\__\\/::\\/\\__\\ 12 | \\:\\::/ /\\/\\::/ /\\:\\ \\/__/\\/\\::/ / Java %s 13 | \\::/ / /:/ / \\:\\__\\ /:/ / %s 14 | \\/__/ \\/__/ \\/__/ \\/__/ %s 15 | """, 16 | Runtime.version(), 17 | System.getProperty("os.name"), 18 | Path.of("").toUri() 19 | ) 20 | 21 | System.out.println("| Source Bach.java from " + Path.of("src/bach.run/Bach.java").toUri()) 22 | 23 | /open src/bach.run/Bach.java 24 | 25 | int code = 0 26 | try { 27 | Bach.init(); 28 | } 29 | catch(Throwable throwable) { 30 | System.err.println(throwable); 31 | code = 1; 32 | } 33 | 34 | System.out.println("| Installation of Bach finished with exit code " + code) 35 | 36 | /exit code 37 | -------------------------------------------------------------------------------- /.bach/src/run.bach/module-info.java: -------------------------------------------------------------------------------- 1 | /** Defines Bach's API. */ 2 | module run.bach { 3 | requires jdk.compiler; 4 | requires transitive jdk.jfr; 5 | 6 | exports run.bach; 7 | exports run.bach.info; 8 | exports run.bach.workflow; 9 | 10 | uses java.util.spi.ToolProvider; 11 | } 12 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Build.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | class Build { 4 | public static void main(String... args) { 5 | Project.ofCurrentWorkingDirectory().build(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Clean.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | class Clean { 4 | public static void main(String... args) { 5 | Project.ofCurrentWorkingDirectory().clean(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Format.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | import run.demo.GoogleJavaFormat; 4 | 5 | class Format { 6 | public static void main(String... args) { 7 | var tool = new GoogleJavaFormat("1.25.2").install(); 8 | if (args.length == 0) { 9 | tool.run(call -> call.add("--replace").addFiles("**.java")); 10 | } else { 11 | tool.run(args); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Prepare.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | class Prepare { 4 | public static void main(String... args) { 5 | Project.ofCurrentWorkingDirectory().prepare(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Project.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | import java.util.Optional; 4 | import run.bach.ModuleLocator; 5 | import run.bach.ToolCall; 6 | import run.bach.ToolRunner; 7 | import run.bach.workflow.Builder; 8 | import run.bach.workflow.ClassesCompiler; 9 | import run.bach.workflow.Folders; 10 | import run.bach.workflow.ImageCompiler; 11 | import run.bach.workflow.Starter; 12 | import run.bach.workflow.Structure; 13 | import run.bach.workflow.Structure.Basics; 14 | import run.bach.workflow.Structure.Space; 15 | import run.bach.workflow.Structure.Spaces; 16 | import run.bach.workflow.Workflow; 17 | import run.info.org.junit.JUnit; 18 | 19 | public record Project(boolean verbose, Workflow workflow) implements Builder, Starter { 20 | static Project ofCurrentWorkingDirectory() { 21 | var verbose = Boolean.getBoolean("-Debug".substring(2)); 22 | var folders = Folders.ofCurrentWorkingDirectory(); 23 | var basics = new Basics("Bach", "2024-ea"); 24 | var main = 25 | new Space("main") 26 | .withTargetingJavaRelease(22) 27 | .withLauncher("bach=run.bach/run.bach.Main") 28 | .withModule(".bach/src/run.bach", ".bach/src/run.bach/module-info.java") 29 | .with(Space.Flag.COMPILE_RUNTIME_IMAGE); 30 | var test = 31 | new Space("test", main) 32 | .withLauncher("tests=test.bach/test.bach.Tests") 33 | .withModule("src/test.bach", "src/test.bach/test/java/module-info.java") 34 | .withModule("src/test.junit", "src/test.junit/test/java/module-info.java"); 35 | var libraries = 36 | ModuleLocator.compose( 37 | JUnit.modules(), 38 | ModuleLocator.of( 39 | "org.junitpioneer", "pkg:maven/org.junit-pioneer/junit-pioneer@2.2.0")); 40 | var structure = new Structure(basics, new Spaces(main, test), libraries); 41 | var runner = ToolRunner.ofSystem(); 42 | return new Project(verbose, new Workflow(folders, structure, runner)); 43 | } 44 | 45 | public Space space(String name) { 46 | return workflow.structure().spaces().space(name); 47 | } 48 | 49 | public void printStatus() { 50 | var structure = workflow.structure(); 51 | System.out.println(structure.toNameAndVersion()); 52 | System.out.println(structure.basics()); 53 | structure.spaces().forEach(System.out::println); 54 | System.out.println(workflow.folders()); 55 | System.out.println(workflow.runner()); 56 | } 57 | 58 | @Override 59 | public boolean builderDoesCleanAtTheBeginning() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public void classesCompilerRunJavacToolCall(ToolCall javac) { 65 | run(javac.add("-X" + "lint:all").add("-W" + "error")); 66 | // Retain only "bach" subdirectory in out/main/classes/*/run.bach/run/* directory 67 | if (ClassesCompiler.space().name().equals("main")) { 68 | var classes = classesCompilerUsesDestinationDirectory(); 69 | var run = classes.resolve("run.bach", "run"); 70 | var bach = run.resolve("bach"); 71 | cleanerPrune(run, path -> !path.equals(bach)); 72 | } 73 | } 74 | 75 | @Override 76 | public ToolCall modulesCompilerUsesJarToolCall() { 77 | return Builder.super.modulesCompilerUsesJarToolCall().when(verbose, "--verbose"); 78 | } 79 | 80 | @Override 81 | public Optional imageCompilerUsesLauncher() { 82 | if (ImageCompiler.space().name().equals("main")) return Optional.of("bach=run.bach"); 83 | return Optional.empty(); 84 | } 85 | 86 | @Override 87 | public void junitTesterRunJUnitToolCall(ToolCall junit) { 88 | run(junit.add("--details", "none").add("--disable-banner").add("--disable-ansi-colors")); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Start.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | class Start { 4 | public static void main(String... args) { 5 | Project.ofCurrentWorkingDirectory().start(args); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Status.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | class Status { 4 | public static void main(String... args) { 5 | Project.ofCurrentWorkingDirectory().printStatus(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/Test.java: -------------------------------------------------------------------------------- 1 | package run; 2 | 3 | class Test { 4 | public static void main(String... args) { 5 | Project.ofCurrentWorkingDirectory().test(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/demo/GoogleJavaFormat.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Christian Stein 3 | * Licensed under the Universal Permissive License v 1.0 -> https://opensource.org/license/upl 4 | */ 5 | 6 | package run.demo; 7 | 8 | import java.net.URI; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.util.spi.ToolProvider; 12 | import run.bach.ToolInstaller; 13 | import run.bach.ToolProgram; 14 | 15 | /** 16 | * Google Java Format installer. 17 | * 18 | * @see Google Java Format 19 | */ 20 | public record GoogleJavaFormat(String version) implements ToolInstaller { 21 | /** 22 | * @see Latest release 23 | */ 24 | public static final String DEFAULT_VERSION = "1.25.2"; 25 | 26 | public static void main(String... args) { 27 | var version = System.getProperty("version", DEFAULT_VERSION); 28 | new GoogleJavaFormat(version) 29 | .install() 30 | .run(args.length == 0 ? new String[] {"--version"} : args); 31 | } 32 | 33 | public GoogleJavaFormat() { 34 | this(DEFAULT_VERSION); 35 | } 36 | 37 | @Override 38 | public String name() { 39 | return "google-java-format"; 40 | } 41 | 42 | @Override 43 | public ToolProvider install(Path into) throws Exception { 44 | var filename = "google-java-format-" + version + "-all-deps.jar"; 45 | var target = into.resolve(filename); 46 | if (!Files.exists(target)) { 47 | var releases = "https://github.com/google/google-java-format/releases/download/"; 48 | var source = releases + "v" + version + "/" + filename; 49 | download(target, URI.create(source)); 50 | } 51 | return ToolProgram.java("-jar", target.toString()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/demo/Greeter.java: -------------------------------------------------------------------------------- 1 | package run.demo; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.util.spi.ToolProvider; 6 | import run.bach.Tool; 7 | import run.bach.ToolInstaller; 8 | import run.bach.ToolProgram; 9 | import run.bach.workflow.Folders; 10 | 11 | public record Greeter(String version) implements ToolInstaller { 12 | public static void main(String... args) throws Exception { 13 | var greeter = new Greeter("99"); 14 | System.out.println("[1]"); 15 | greeter.install().run(); // "run.bach/greeter@99" in ".bach/tmp/tool" 16 | 17 | var folders = Folders.ofTemporaryDirectory(); 18 | System.out.println("\n[2]"); 19 | Tool.of("greeter/eager", greeter.install(folders.tool("greeter/eager"))).run(); 20 | System.out.println("\n[3]"); 21 | Tool.of("greeter/inert", () -> greeter.install(folders.tool("greeter/inert"))).run(); 22 | } 23 | 24 | @Override 25 | public ToolProvider install(Path into) throws Exception { 26 | System.out.println(into.toUri()); 27 | var file = into.resolve("Prog.java"); 28 | if (!Files.exists(file)) { 29 | Files.createDirectories(into); 30 | var text = // language=java 31 | """ 32 | class Prog { 33 | public static void main(String... args) { 34 | System.out.println("Greetings!"); 35 | } 36 | } 37 | """; 38 | Files.writeString(file, text); 39 | } 40 | return ToolProgram.java(file.toString()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/demo/JResolve.java: -------------------------------------------------------------------------------- 1 | package run.demo; 2 | 3 | /** 4 | * JResolve - a command line tool for resolving dependencies on the JVM. 5 | * 6 | * @see ref.descriptor().toNameAndVersion() + " -> " + ref.location().orElseThrow()) 35 | .sorted() 36 | .forEach(System.out::println); 37 | 38 | // "jreleaser" via the tool provider SPI 39 | var jreleaserHome = Folders.ofCurrentWorkingDirectory().tool("jreleaser@" + JReleaser.VERSION); 40 | var jreleaserResolver = ModuleResolver.ofSingleDirectory(jreleaserHome, JReleaser.MODULES); 41 | jreleaserResolver.resolveModule("org.jreleaser.tool"); 42 | jreleaserResolver.resolveMissingModules(); 43 | 44 | var tools = 45 | ToolFinder.compose( 46 | ToolFinder.of("jar"), // provides "jar" tool 47 | ToolFinder.of("java"), // provides "java" tool 48 | ToolFinder.of(ModuleFinder.of(lib)), // provides "junit" tool 49 | ToolFinder.of(ModuleFinder.of(jreleaserHome)), // provides "jreleaser" tool 50 | ToolFinder.ofInstaller().withJavaApplication("jrelease@uri", JReleaser.URI)); 51 | 52 | var junit = tools.get("junit"); 53 | junit.run("--version"); 54 | junit.run("engines"); 55 | 56 | tools.get("jreleaser").run("--version"); 57 | tools.get("jrelease@uri").run("--version"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/demo/ToolFinderDemo.java: -------------------------------------------------------------------------------- 1 | package run.demo; 2 | 3 | import java.io.*; 4 | import java.util.*; 5 | import java.util.concurrent.Callable; 6 | import java.util.function.Consumer; 7 | import java.util.spi.*; 8 | import run.bach.*; 9 | import run.info.bach.*; 10 | 11 | class ToolFinderDemo { 12 | public static void main(String... args) { 13 | var finder = 14 | ToolFinder.compose( 15 | // ToolProvider SPI, i.e. ToolProvider.findFirst(NAME) 16 | ToolFinder.of(Tool.of("jar"), Tool.of("javac")), 17 | // Native executable program in ${JAVA_HOME}/bin/NAME[.exe] 18 | ToolFinder.of(Tool.of("java"), Tool.of("jfr")), 19 | // ToolProvider instance 20 | ToolFinder.of( 21 | Tool.of(new Noop("noop", 0)), 22 | Tool.of(new Noop("fail", 1)), 23 | Tool.of(new RunnableTool("flush", System::gc)), 24 | Tool.of(new CallableTool("processors", Runtime.getRuntime()::availableProcessors)), 25 | Tool.of(new ConsumerTool("consume", ConsumerTool::example))), 26 | // Tool installation support 27 | ToolFinder.ofInstaller(ToolInstaller.Mode.INSTALL_IMMEDIATE) 28 | // dedicated installer 29 | .with(new Ant("1.10.14")) 30 | .with(new GoogleJavaFormat("1.22.0")) 31 | .with(new Maven("3.9.7")) 32 | // convenient installer 33 | .withJavaApplication( 34 | "rife2/bld@1.9.0", 35 | "https://github.com/rife2/bld/releases/download/1.9.0/bld-1.9.0.jar") 36 | .withJavaApplication( 37 | "kordamp/jarviz@0.3.0", 38 | "https://github.com/kordamp/jarviz/releases/download/v0.3.0/jarviz-tool-provider-0.3.0.jar")); 39 | 40 | System.out.println(toString(finder)); 41 | } 42 | 43 | record Noop(String name, int code) implements ToolProvider { 44 | @Override 45 | public int run(PrintWriter out, PrintWriter err, String... args) { 46 | return code; 47 | } 48 | } 49 | 50 | record RunnableTool(String name, Runnable runnable) implements ToolProvider { 51 | @Override 52 | public int run(PrintWriter out, PrintWriter err, String... args) { 53 | try { 54 | runnable.run(); 55 | return 0; 56 | } catch (RuntimeException exception) { 57 | return 1; 58 | } 59 | } 60 | } 61 | 62 | record CallableTool(String name, Callable callable) implements ToolProvider { 63 | @Override 64 | public int run(PrintWriter out, PrintWriter err, String... args) { 65 | try { 66 | return callable.call(); 67 | } catch (Exception exception) { 68 | return 1; 69 | } 70 | } 71 | } 72 | 73 | record ConsumerTool(String name, Consumer consumer) implements ToolProvider { 74 | static void example(String... args) {} 75 | 76 | @Override 77 | public int run(PrintWriter out, PrintWriter err, String... args) { 78 | try { 79 | consumer.accept(args); 80 | return 0; 81 | } catch (RuntimeException exception) { 82 | return 1; 83 | } 84 | } 85 | } 86 | 87 | static String toString(ToolFinder finder) { 88 | var joiner = new StringJoiner("\n"); 89 | var width = "jar".length(); 90 | var tools = finder.tools(); 91 | var names = new TreeMap>(); 92 | for (var tool : tools) { 93 | var name = tool.identifier().name(); 94 | names.computeIfAbsent(name, _ -> new ArrayList<>()).add(tool); 95 | var length = name.length(); 96 | if (length > width) width = length; 97 | } 98 | for (var entry : names.entrySet()) { 99 | var all = 100 | entry.getValue().stream() 101 | .map(tool -> tool.identifier().toNamespaceAndNameAndVersion()) 102 | .toList(); 103 | var line = "%" + width + "s %s %s"; 104 | joiner.add(line.formatted(entry.getKey(), "->", all.getFirst())); 105 | for (var next : all.stream().skip(1).toList()) { 106 | joiner.add(line.formatted("", " ", next)); 107 | } 108 | } 109 | var size = tools.size(); 110 | joiner.add(" %d tool%s".formatted(size, size == 1 ? "" : "s")); 111 | return joiner.toString().stripTrailing(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/demo/ToolSpaceDemo.java: -------------------------------------------------------------------------------- 1 | package run.demo; 2 | 3 | import run.bach.*; 4 | import run.info.bach.*; 5 | 6 | class ToolSpaceDemo extends ToolSpace { 7 | public static void main(String... args) { 8 | var finder = ToolFinder.ofInstaller().with(new Maven()); 9 | var space = new ToolSpaceDemo(finder); 10 | 11 | var run = space.run("maven", "--version"); 12 | 13 | run.out().lines().filter(line -> line.startsWith("Apache Maven")).forEach(System.out::println); 14 | if (run.code() != 0) throw new Error("Non-zero error code: " + run.code()); 15 | } 16 | 17 | ToolSpaceDemo(ToolFinder finder) { 18 | super(finder); 19 | } 20 | 21 | @Override 22 | protected void announce(ToolCall call) { 23 | System.out.println("BEGIN -> " + call.tool().name()); 24 | } 25 | 26 | @Override 27 | protected void verify(ToolRun run) { 28 | System.out.println("<-- END. " + run.call().tool().name()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.bach/src/run.bach/run/demo/ToolVersionsDemo.java: -------------------------------------------------------------------------------- 1 | package run.demo; 2 | 3 | import run.bach.*; 4 | import run.info.bach.*; 5 | 6 | class ToolVersionsDemo { 7 | public static void main(String... args) { 8 | // 1-shot, tool provider 9 | Tool.of("jar").run("--version"); 10 | 11 | // 1-shot, tool program 12 | Tool.of("java").run("--version"); 13 | 14 | // 1-shot, tool installer 15 | Tool.of("https://github.com/rife2/bld/releases/download/2.0.1/bld-2.0.1.jar").run("version"); 16 | Tool.of(new Ant(), ToolInstaller.Mode.INSTALL_IMMEDIATE).run("-version"); 17 | 18 | // multi-shot, tool finder 19 | var finder = 20 | ToolFinder.ofInstaller(ToolInstaller.Mode.INSTALL_IMMEDIATE) 21 | .with(new Ant()) 22 | .withJavaApplication(JResolve.ID, JResolve.URI) 23 | .withJavaApplication( 24 | "rife2/bld@2.0.1", 25 | "https://github.com/rife2/bld/releases/download/2.0.1/bld-2.0.1.jar") 26 | .withJavaApplication( 27 | "org.junit.platform/junit@1.11.0", 28 | "https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-console-standalone/1.11.0/junit-platform-console-standalone-1.11.0.jar") 29 | .with("run.bach/google-java-format@1.23", new GoogleJavaFormat("1.23.0")) 30 | .with("run.bach/google-java-format@1.19", new GoogleJavaFormat("1.19.2")) 31 | .with(new Maven("3.9.9")); 32 | 33 | var runner = ToolRunner.of(finder); 34 | runner.run("ant", "-version"); 35 | runner.run("bld", "version"); 36 | runner.run("junit", "--version"); 37 | runner.run("jresolve", "--version"); 38 | runner.run("google-java-format", "--version"); 39 | runner.run("google-java-format@1.19", "--version"); 40 | runner.run("maven", "--version"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/EMPTY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/bach/ae3214f92281df62dcba2eaa392f95d9d91892a5/.github/EMPTY -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ '*' ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: 'Check out repository' 15 | uses: actions/checkout@v4 16 | with: 17 | submodules: recursive 18 | - name: 'List all -info.java files' 19 | run: find | grep \\-info.java | sort 20 | - name: 'Set up Java' 21 | uses: oracle-actions/setup-java@v1 22 | with: 23 | website: jdk.java.net 24 | release: 24 25 | - name: 'Print various versions' 26 | run: | 27 | java @bach jar --version 28 | java @bach jfr --version 29 | java @bach https://raw.githubusercontent.com/openjdk/jdk/refs/heads/jdk24/test/jdk/java/lang/System/Versions.java 30 | java .bach/src/run.bach/run/demo/ToolVersionsDemo.java 31 | - name: 'Build Bach with Bach' 32 | run: java @build 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "run.bach"] 2 | path = .bach/src/run.bach/run/bach 3 | url = https://github.com/sormuras/run.bach 4 | [submodule "junit"] 5 | path = .bach/src/run.bach/run/info/org/junit 6 | url = https://github.com/junit-team/bach-info 7 | [submodule "run.info.bach"] 8 | path = .bach/src/run.bach/run/info/bach 9 | url = https://github.com/sormuras/bach-info 10 | [submodule "jreleaser"] 11 | path = .bach/src/run.bach/run/info/org/jreleaser 12 | url = https://github.com/jreleaser/bach-info 13 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | shelf/ 3 | sonarlint* 4 | dbnavigator.xml 5 | runConfigurations.xml 6 | uiDesigner.xml 7 | workspace.xml 8 | 9 | -------------------------------------------------------------------------------- /.idea/bach.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/libraries/lib.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/project.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/run.bach.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/test.bach.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/test.junit.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Christian Stein 2 | 3 | The Universal Permissive License (UPL), Version 1.0 4 | 5 | Subject to the condition set forth below, permission is hereby granted to any 6 | person obtaining a copy of this software, associated documentation and/or data 7 | (collectively the "Software"), free of charge and under any and all copyright 8 | rights in the Software, and any and all patent rights owned or freely 9 | licensable by each licensor hereunder covering either (i) the unmodified 10 | Software as contributed to or provided by such licensor, or (ii) the Larger 11 | Works (as defined below), to deal in both 12 | 13 | (a) the Software, and 14 | (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if 15 | one is included with the Software (each a "Larger Work" to which the Software 16 | is contributed by such licensors), 17 | 18 | without restriction, including without limitation the rights to copy, create 19 | derivative works of, display, perform, and distribute the Software and make, 20 | use, sell, offer for sale, import, export, have made, and have sold the 21 | Software and the Larger Work(s), and to sublicense the foregoing rights on 22 | either these or other terms. 23 | 24 | This license is subject to the following condition: 25 | The above copyright notice and either this complete permission notice or at 26 | a minimum a reference to the UPL must be included in all copies or 27 | substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bach - Java Shell Builder - Builds (on(ly)) Modules 2 | 3 | > "The tools we use have a profound (and devious!) influence on our thinking habits, and, therefore, on our thinking abilities." 4 | > 5 | > [E. W. Dijkstra, 18 June 1975](https://www.cs.virginia.edu/~evans/cs655/readings/ewd498.html) 6 | 7 | Bach is a tool that orchestrates [JDK tools] for building modular Java projects. 8 | 9 | If you are eager to try out Bach and already set [JDK 22] or higher up, these simple steps work most of the time: 10 | 11 | ```shell 12 | mkdir example && cd example 13 | jshell 14 | /open https://install.bach.run 15 | java @bach jar --version 16 | java @bach jcmd -l 17 | java @bach https://src.bach.run/Hi.java Lo 18 | ``` 19 | 20 | For detailed installation instructions please see the [installing](doc/installing.md) document. 21 | 22 | # be free - have fun 23 | 24 | [![jdk22](https://img.shields.io/badge/JDK-22-blue.svg)](https://jdk.java.net) 25 | ![experimental](https://img.shields.io/badge/API-experimental-yellow.svg) 26 | [![jsb](https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Bachsiegel.svg/220px-Bachsiegel.svg.png)](https://wikipedia.org/wiki/Johann_Sebastian_Bach) 27 | 28 | [JDK tools]: https://docs.oracle.com/en/java/javase/22/docs/specs/man/index.html 29 | [JDK 22]: https://jdk.java.net/22 30 | -------------------------------------------------------------------------------- /bach: -------------------------------------------------------------------------------- 1 | # java [VM-OPTIONS...] @bach [OPTIONS...] TOOL [ARGS...] 2 | .bach/src/run.bach/run/bach/Main.java 3 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | # Short-cut for building Bach with Bach 2 | .bach/src/run.bach/run/Build.java 3 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Bach - Documentation 2 | 3 | - [installing.md](installing.md) 4 | -------------------------------------------------------------------------------- /doc/installing.md: -------------------------------------------------------------------------------- 1 | # Installing Bach 2 | 3 | This document describes common ways to install Bach. 4 | 5 | Bach is usually installed as source code relative to each project's root directory. 6 | The default directory path is `.bach/src/run/bach`. 7 | 8 | ## Prerequisite 9 | 10 | - JDK 22 or higher 11 | 12 | ## Install Bach using JShell 13 | 14 | Install Bach using `jshell` by running the snippets from the default installation script. 15 | The https://install.bach.run URL forwards to the _"install default version of Bach into `.bach` directory of the current working directory"_ [Java Shell](../src/bach.run/install.jshell) script. 16 | 17 | ```shell 18 | mkdir example && cd example 19 | jshell 20 | /open https://install.bach.run 21 | ``` 22 | 23 | Above's commands are a shortcut for the following Java Shell commands and snippets. 24 | 25 | ```shell 26 | mkdir example && cd example 27 | jshell 28 | /open https://src.bach.run/Bach.java 29 | Bach.init() 30 | /exit 31 | ``` 32 | 33 | Consult `Bach.java`'s source and documentation for customizing the installation process. 34 | 35 | ## Install Bach using Git 36 | 37 | Install Bach using `git` and create `java`'s argument file manually. 38 | 39 | First time: 40 | ```shell 41 | mkdir example && cd example 42 | git init 43 | git submodule add https://github.com/sormuras/run.bach .bach/src/run/bach 44 | echo .bach/src/run/bach/Main.java > bach 45 | ``` 46 | Consult the following manual pages for details of `git` and `java` tools: 47 | - [git init](https://git-scm.com/docs/git-init) - Create an empty Git repository or reinitialize an existing one 48 | - [git submodule](https://git-scm.com/docs/git-submodule) - Initialize, update or inspect submodules 49 | - [java @file](https://docs.oracle.com/en/java/javase/22/docs/specs/man/java.html#java-command-line-argument-files) - Java Command-Line Argument Files 50 | 51 | Subsequent times: 52 | ```shell 53 | cd example 54 | git submodule update --remote --recursive 55 | ``` 56 | 57 | ## Play with Bach 58 | 59 | Running a tool via the `ToolProvider` SPI. 60 | 61 | - `java @bach jar --version` 62 | 63 | Running a tool via the `ProcessBuilder` API. 64 | 65 | - `java @bach jcmd -l` 66 | 67 | Running a tool via the `ToolInstaller` API. 68 | 69 | - `java @bach https://src.bach.run/Hi.java Lo` 70 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.jar 2 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Bach's Modular Sources 2 | 3 | For the time being, this folder only contains a subdirectory named `bach.run`. 4 | The `bach.run` subdirectory contains helper files used by https://bach.run path forwarding rules. 5 | -------------------------------------------------------------------------------- /src/bach.run/Bach.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Christian Stein 3 | * Licensed under the Universal Permissive License v 1.0 -> https://opensource.org/license/upl 4 | */ 5 | 6 | // TODO import module java.base; 7 | 8 | import java.io.*; 9 | import java.net.*; 10 | import java.nio.file.*; 11 | import java.util.*; 12 | 13 | @SuppressWarnings("unused") 14 | interface Bach { 15 | static void build() { 16 | /* java @build */ { 17 | var build = Path.of("build"); 18 | if (Files.isRegularFile(build)) { 19 | System.out.println("Running java @build ..."); 20 | Internal.java("@" + build); 21 | return; 22 | } 23 | System.out.println("Running java @build not possible."); 24 | System.out.println(" Create a `build` argument file to enable it."); 25 | } 26 | /* java Build.java */ { 27 | var candidates = 28 | List.of( 29 | Path.of("Build.java"), 30 | Path.of(".bach/Build.java"), 31 | Path.of(".bach/src/Build.java"), 32 | Path.of(".bach/src/run/Build.java"), 33 | Path.of(".bach/src/run.bach/run/Build.java")); 34 | for (var build : candidates) { 35 | if (Files.isRegularFile(build)) { 36 | System.out.println("Running java " + build + " ..."); 37 | Internal.java(build.toString()); 38 | return; 39 | } 40 | } 41 | System.out.println("Running java Build.java not possible."); 42 | System.out.println(" Create a `Build.java` file to enable it."); 43 | } 44 | // zero-configuration 45 | System.out.println("TODO: java @bach build"); 46 | System.out.println("TODO: java .bach/src/run/bach/Main.java build"); 47 | System.out.println("TODO: java .bach/src/run.bach/run/bach/Main.java build"); 48 | // zero-installation + zero-configuration 49 | System.out.println("TODO: init(temp) && java ${temp}/.bach/src/run/bach/Main.java build"); 50 | } 51 | 52 | static void init() { 53 | new Installer().install(); 54 | } 55 | 56 | static void status() { 57 | var directory = Path.of(""); 58 | System.out.printf( 59 | """ 60 | ___ ___ ___ ___ 61 | /\\ \\ /\\ \\ /\\ \\ /\\__\\ 62 | /::\\ \\ /::\\ \\ /::\\ \\ /:/__/_ 63 | /::\\:\\__\\/::\\:\\__\\/:/\\:\\__\\/::\\/\\__\\ 64 | \\:\\::/ /\\/\\::/ /\\:\\ \\/__/\\/\\::/ / Java %s 65 | \\::/ / /:/ / \\:\\__\\ /:/ / %s 66 | \\/__/ \\/__/ \\/__/ \\/__/ %s 67 | """, 68 | Runtime.version(), System.getProperty("os.name"), directory.toUri()); 69 | 70 | System.out.println("\nModule declarations"); 71 | var matcher = directory.getFileSystem().getPathMatcher("glob:**module-info.java"); 72 | try (var stream = Files.find(directory, 9, (p, _) -> matcher.matches(p))) { 73 | for (var path : stream.toList()) { 74 | System.out.println(" -> " + path.toUri()); 75 | } 76 | } catch (Exception exception) { 77 | throw new RuntimeException("Find in %s failed".formatted(directory), exception); 78 | } 79 | } 80 | 81 | interface Internal { 82 | boolean DEBUG = Boolean.getBoolean("-Debug".substring(2)); 83 | 84 | static void debug(String message) { 85 | if (DEBUG) System.out.println(message); 86 | } 87 | 88 | static void copy(String source, Path target, CopyOption... options) throws Exception { 89 | debug("<< %s".formatted(source)); 90 | Files.createDirectories(target.getParent()); 91 | try (var stream = 92 | source.startsWith("http") 93 | ? URI.create(source).toURL().openStream() 94 | : Files.newInputStream(Path.of(source))) { 95 | var size = Files.copy(stream, target, options); 96 | debug(">> %,7d %s".formatted(size, target.getFileName())); 97 | } 98 | } 99 | 100 | static void delete(Path path) throws Exception { 101 | var start = path.normalize().toAbsolutePath(); 102 | if (Files.notExists(start)) return; 103 | for (var root : start.getFileSystem().getRootDirectories()) { 104 | if (start.equals(root)) { 105 | debug("deletion of root directory?! " + path); 106 | return; 107 | } 108 | } 109 | debug("delete directory tree " + start); 110 | try (var stream = Files.walk(start)) { 111 | var files = stream.sorted((p, q) -> -p.compareTo(q)); 112 | for (var file : files.toArray(Path[]::new)) Files.deleteIfExists(file); 113 | } 114 | } 115 | 116 | static boolean head(String source) throws Exception { 117 | var url = URI.create(source).toURL(); 118 | var con = (HttpURLConnection) url.openConnection(); 119 | try { 120 | con.setRequestMethod("HEAD"); 121 | var status = con.getResponseCode(); 122 | debug("%d <- HEAD %s".formatted(status, source)); 123 | if (status < 299) return true; 124 | } finally { 125 | con.disconnect(); 126 | } 127 | return false; 128 | } 129 | 130 | static void java(String... args) { 131 | var java = Path.of(System.getProperty("java.home"), "bin", "java" /*.exe*/); 132 | var code = run(List.of(java.toString()), args); 133 | if (code == 0) return; 134 | throw new RuntimeException("Non-zero error code: " + code); 135 | } 136 | 137 | static int run(List command, String... arguments) { 138 | debug("| " + String.join(" ", command)); 139 | var out = System.out; 140 | var err = System.err; 141 | record LinePrinter(InputStream stream, PrintStream writer) implements Runnable { 142 | @Override 143 | public void run() { 144 | new BufferedReader(new InputStreamReader(stream)).lines().forEach(writer::println); 145 | } 146 | } 147 | var processBuilder = new ProcessBuilder(new ArrayList<>(command)); 148 | processBuilder.command().addAll(List.of(arguments)); 149 | try { 150 | var process = processBuilder.start(); 151 | var threadBuilder = Thread.ofVirtual(); 152 | threadBuilder.name("-out").start(new LinePrinter(process.getInputStream(), out)); 153 | threadBuilder.name("-err").start(new LinePrinter(process.getErrorStream(), err)); 154 | return process.isAlive() ? process.waitFor() : process.exitValue(); 155 | } catch (InterruptedException exception) { 156 | Thread.currentThread().interrupt(); 157 | return -1; 158 | } catch (Exception exception) { 159 | exception.printStackTrace(err); 160 | return 1; 161 | } 162 | } 163 | 164 | static void unzip(Path zip, Path dir, int sub) throws Exception { 165 | debug("<< %s".formatted(zip.toUri())); 166 | debug(">> %s".formatted(dir.toUri())); 167 | var files = new ArrayList(); 168 | try (var fs = FileSystems.newFileSystem(zip)) { 169 | for (var root : fs.getRootDirectories()) { 170 | try (var stream = Files.walk(root)) { 171 | var list = stream.filter(Files::isRegularFile).toList(); 172 | for (var file : list) { 173 | var relative = root.relativize(file); 174 | var source = sub == 0 ? relative : relative.subpath(sub, relative.getNameCount()); 175 | var target = dir.resolve(source.toString()); 176 | // debug(target.toUri().toString()); 177 | Files.createDirectories(target.getParent()); 178 | Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING); 179 | files.add(target); 180 | } 181 | } 182 | } 183 | } 184 | debug(">> %d files copied".formatted(files.size())); 185 | } 186 | } 187 | 188 | record Installer(String version, Path home, Path path) { 189 | // defaults to git head reference of the `main` branch 190 | static String VERSION = System.getProperty("-Dversion".substring(2), "main"); 191 | // defaults to the current working directory 192 | static Path HOME = Path.of(System.getProperty("-Dhome".substring(2), "")); 193 | // git submodule add [] <- .bach/src[/run.bach]/run/bach 194 | static Path PATH = Path.of(System.getProperty("-Dpath".substring(2), ".bach/src/run/bach")); 195 | 196 | Installer() { 197 | this(VERSION); 198 | } 199 | 200 | Installer(String version) { 201 | this(version, HOME, PATH); 202 | } 203 | 204 | void install() { 205 | try { 206 | installSources(); 207 | installArgumentFiles(); 208 | } catch (Exception exception) { 209 | System.err.println("Install failed: " + exception.getMessage()); 210 | } 211 | } 212 | 213 | void installSources() throws Exception { 214 | var uris = 215 | List.of( 216 | "https://github.com/sormuras/run.bach/archive/refs/tags/" + version + ".zip", 217 | "https://github.com/sormuras/run.bach/archive/refs/heads/" + version + ".zip"); 218 | for (var uri : uris) { 219 | if (Internal.head(uri)) { 220 | installSourcesFromUri(uri); 221 | return; 222 | } 223 | } 224 | } 225 | 226 | void installSourcesFromUri(String uri) throws Exception { 227 | var tmp = Files.createTempDirectory("run.bach-" + version + "-"); 228 | var dir = Files.createDirectories(home.resolve(path)); 229 | var zip = tmp.resolve("run.bach-" + version + ".zip"); 230 | System.out.println("Installing Bach [" + version + "] into " + path.toUri() + "..."); 231 | // download and unzip 232 | Internal.copy(uri, zip, StandardCopyOption.REPLACE_EXISTING); 233 | Internal.unzip(zip, dir, 1); 234 | // clean up 235 | Internal.delete(tmp); 236 | } 237 | 238 | void installArgumentFiles() throws Exception { 239 | var bach = home.resolve("bach"); 240 | if (!Files.exists(bach)) { 241 | var program = home.resolve(path).resolve("Main.java"); 242 | var command = home.relativize(program).toString().replace('\\', '/'); 243 | var lines = List.of("# Argument file for launching Bach's main application", command); 244 | Files.write(bach, lines); 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/bach.run/Hi.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Christian Stein 3 | * Licensed under the Universal Permissive License v 1.0 -> https://opensource.org/license/upl 4 | */ 5 | 6 | class Hi { 7 | public static void main(String... args) { 8 | String name = args.length == 0 ? System.getProperty("user.name") : String.join(" ", args); 9 | System.out.printf("Hi %s!%n", name); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/bach.run/Ho.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 Christian Stein 3 | * Licensed under the Universal Permissive License v 1.0 -> https://opensource.org/license/upl 4 | */ 5 | 6 | void main(String... args) { 7 | String name = args.length == 0 ? System.getProperty("user.name") : String.join(" ", args); 8 | IO.println("Ho %s!".formatted(name)); 9 | } 10 | -------------------------------------------------------------------------------- /src/bach.run/README.md: -------------------------------------------------------------------------------- 1 | # Bach's `src/bach.run` folder 2 | 3 | This folder contains Java source files (`.java`) and Java Shell scripts (`.jshell`) helping to install Bach. 4 | 5 | ## Install default version of Bach 6 | 7 | 8 | 9 | ## Test-drive 10 | 11 | 12 | 13 | ## Path forwarding 14 | 15 | - https://src.bach.run forwards to https://raw.githubusercontent.com/sormuras/bach/main/src/bach.run - resulting in `404: Not Found` 16 | - https://src.bach.run/install.jshell forwards to https://raw.githubusercontent.com/sormuras/bach/main/src/bach.run/install.jshell 17 | - https://src.bach.run/Bach.java forwards to https://raw.githubusercontent.com/sormuras/bach/main/src/bach.run/Bach.java 18 | - https://src.bach.run/Hi.java forwards to https://raw.githubusercontent.com/sormuras/bach/main/src/bach.run/Hi.java 19 | -------------------------------------------------------------------------------- /src/bach.run/install.jshell: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 Christian Stein 3 | * Licensed under the Universal Permissive License v 1.0 -> https://opensource.org/license/upl 4 | */ 5 | 6 | System.out.printf( 7 | """ 8 | ___ ___ ___ ___ 9 | /\\ \\ /\\ \\ /\\ \\ /\\__\\ 10 | /::\\ \\ /::\\ \\ /::\\ \\ /:/__/_ 11 | /::\\:\\__\\/::\\:\\__\\/:/\\:\\__\\/::\\/\\__\\ 12 | \\:\\::/ /\\/\\::/ /\\:\\ \\/__/\\/\\::/ / Java %s 13 | \\::/ / /:/ / \\:\\__\\ /:/ / %s 14 | \\/__/ \\/__/ \\/__/ \\/__/ %s 15 | """, 16 | Runtime.version(), 17 | System.getProperty("os.name"), 18 | Path.of("").toUri() 19 | ) 20 | 21 | System.out.println("| Source Bach.java from https://src.bach.run/Bach.java ...") 22 | 23 | /open https://src.bach.run/Bach.java 24 | 25 | int code = 0 26 | try { 27 | Bach.init(); 28 | } 29 | catch(Throwable throwable) { 30 | System.err.println(throwable); 31 | code = 1; 32 | } 33 | 34 | System.out.println("| Installation of Bach finished with exit code " + code) 35 | 36 | /exit code 37 | -------------------------------------------------------------------------------- /src/test.bach/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open /*for testing*/ module test.bach { 2 | requires run.bach; // module under test 3 | requires static org.junit.platform.console; // for running tests 4 | requires org.junit.jupiter; // for writing tests 5 | 6 | provides java.util.spi.ToolProvider with 7 | test.bach.Tests; 8 | } 9 | -------------------------------------------------------------------------------- /src/test.bach/test/java/test/bach/Tests.java: -------------------------------------------------------------------------------- 1 | package test.bach; 2 | 3 | import java.io.PrintWriter; 4 | import java.util.spi.ToolProvider; 5 | import run.bach.ToolRunner; 6 | 7 | public record Tests(String name) implements ToolProvider { 8 | public static void main(String... args) { 9 | System.exit(new Tests().run(System.out, System.err, args)); 10 | } 11 | 12 | public Tests() { 13 | this(Tests.class.getName()); 14 | } 15 | 16 | @Override 17 | public int run(PrintWriter out, PrintWriter err, String... args) { 18 | var actual = ToolRunner.ofSilence().run("jar", "--version").out(); 19 | var expected = "jar " + System.getProperty("java.version"); 20 | if (actual.equals(expected)) return 0; 21 | err.println("expected: " + expected); 22 | err.println("actual : " + actual); 23 | return 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test.bach/test/java/test/bach/workflow/WorkflowTests.java: -------------------------------------------------------------------------------- 1 | package test.bach.workflow; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Nested; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.ValueSource; 7 | import run.bach.workflow.Structure.Launcher; 8 | 9 | class WorkflowTests { 10 | @Nested 11 | class StructureTests { 12 | @Nested 13 | class LauncherTests { 14 | @ParameterizedTest 15 | @ValueSource(strings = {"", "a", "=", "a=", "=a", "a=/", "a=a/", "a=/a"}) 16 | void illegalArgumentThrowsIllegalArgumentException(String s) { 17 | Assertions.assertThrows(IllegalArgumentException.class, () -> Launcher.of(s)); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test.junit/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open /* for testing */ module test.junit { 2 | requires static org.junit.platform.console; // for running tests 3 | requires org.junit.jupiter; // for writing tests 4 | requires org.junitpioneer; // for writing more tests 5 | } 6 | -------------------------------------------------------------------------------- /src/test.junit/test/java/test/junit/JUnitPioneerTests.java: -------------------------------------------------------------------------------- 1 | package test.junit; 2 | 3 | import org.junitpioneer.jupiter.cartesian.CartesianTest; 4 | import org.junitpioneer.jupiter.cartesian.CartesianTest.Values; 5 | 6 | class JUnitPioneerTests { 7 | @CartesianTest 8 | void test(@Values(ints = {1, 2}) int x, @Values(ints = {3, 4}) int y) { 9 | assert x * y > 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test.junit/test/java/test/junit/JUnitTests.java: -------------------------------------------------------------------------------- 1 | package test.junit; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class JUnitTests { 6 | @Test 7 | void test() {} 8 | } 9 | --------------------------------------------------------------------------------