From 6529568baa3dff009c53585d3641f2d86939a486 Mon Sep 17 00:00:00 2001 From: Naofal Date: Thu, 13 Nov 2025 22:15:04 +0300 Subject: [PATCH] nobuild 0.2: markdown javadoc, use newer Java methods --- nobuild/NoBuild.java | 355 ++++++++++++++++++------------------------- 1 file changed, 150 insertions(+), 205 deletions(-) diff --git a/nobuild/NoBuild.java b/nobuild/NoBuild.java index 62a206e..f65f99a 100644 --- a/nobuild/NoBuild.java +++ b/nobuild/NoBuild.java @@ -1,8 +1,4 @@ -/* - * NoBuild system - * - * @version 0.1 - * @since 1.8 +/* NoBuild system * * MIT License * @@ -32,90 +28,110 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; -/** - * NoBuild system - * - *

Quick Guide

- * - * A simple build script: - * - *
- * // Build.java
- * import static nobuild.Nobuild.*;
- *
- * public class Build {
- *   public static void main(String[] args) {
- *     rebuildSelf(Build.class, args);
- *     compileJava("Hello.java");
- *     runJava("Hello");
- *   }
- * }
- * 
- * - * Which can be run as follows: - * - *
- * $ javac -d build/ Build.java
- * $ java -cp build/ Build
- * 
- * - * Note that by using {@link NoBuild#rebuildSelf}, the build script {@code Build} will automatically - * rebuild itself if {@code Build.java} is modified. - * - * @author Naofal Helal - * @version 0.1 - * @since 1.8 - */ +/// # NoBuild system +/// +/// ## Quick Guide +/// +/// A simple build script: +/// +/// ```java +/// // Build.java +/// import static nobuild.Nobuild.*; +/// +/// public class Build { +/// public static void main(String[] args) { +/// rebuildSelf(Build.class, args); +/// compileJava("Hello.java"); +/// runJava("Hello"); +/// } +/// } +/// ``` +/// +/// Which can be run as follows: +/// +/// ```console +/// $ javac -d build/ Build.java +/// $ java -cp build/ Build +/// ``` +/// +/// or: +/// +/// ```console +/// $ java -cp build/ Build.java +/// ``` +/// +/// Note that by using [rebuildSelf][#rebuildSelf], the build script `Build` will automatically +/// rebuild itself if `Build.java` is modified. +/// +/// @author Naofal Helal +/// @version 0.2 +/// @since 23 +//// public class NoBuild { public static Logger logger = Logger.getLogger("logger"); public static Handler loggingHandler = new NoBuildLogHandler(); public static JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - public static Path javaHome = Paths.get(System.getProperty("java.home")); + public static Path javaHome = Path.of(System.getProperty("java.home")); public static String javaBin = javaHome.resolve("bin", "java").toString(); public static String javacBin = javaHome.resolve("bin", "javac").toString(); - /** The class path used by the java executable */ + /// The class path used by the java executable. public static String javaClassPath = System.getProperty("java.class.path"); - /** - * Path to the build script class file, or {@code null} if {@link #rebuildSelf} hasn't been - * called. - * - *

Will be used as the default output directory for compilation. - */ + /// Path to the build script class file, or `null` if [rebuildSelf][#rebuildSelf] hasn't been + /// called. + /// + /// Will be used as the default output directory for compilation. public static String buildClassPath = null; - /** Returns the class path of the build script */ + /// Returns the class path of the build script. public static String defaultBuildClassPath(Class buildClass) { URL buildClassUrl = buildClass.getResource(buildClass.getSimpleName() + ".class"); - return Paths.get(buildClassUrl.getPath()).getParent().toString(); + return Optional.ofNullable(Path.of(buildClassUrl.getPath()).getParent()) + .orElseGet( + () -> { + Path path = Path.of(".", "build"); + boolean isPathInClasspath = + Arrays.stream(javaClassPath.split(File.pathSeparator)) + .anyMatch( + it -> { + try { + return Files.isSameFile(Path.of(it), path); + } catch (IOException e) { + return false; + } + }); + if (!isPathInClasspath) { + logger.warning( + "Using %s as default classpath. Make sure to include with -cp %1$s" + .formatted(path)); + } + return path; + }) + .toString(); } static { @@ -165,28 +181,24 @@ public class NoBuild { } } if (record.getThrown() != null) { - System.err.println(indent(record.getThrown().toString(), 4)); + System.err.println(record.getThrown().toString().indent(4)); } } } - /** - * Rebuilds the build script if necessary - * - * @param buildClass The main class of the build script - * @param args arguments from the main method - */ + /// Rebuilds the build script if necessary. + /// + /// @param buildClass The main class of the build script + /// @param args arguments from the main method public static void rebuildSelf(Class buildClass, String[] args) { rebuildSelf(buildClass, args, new String[0]); } - /** - * Rebuilds the build script if necessary - * - * @param buildClass The main class of the build script - * @param args Arguments from the main method - * @param additionalSources Additional sources to watch and compile - */ + /// Rebuilds the build script if necessary. + /// + /// @param buildClass The main class of the build script + /// @param args Arguments from the main method + /// @param additionalSources Additional sources to watch and compile public static void rebuildSelf(Class buildClass, String[] args, String... additionalSources) { String buildSource = buildClass.getName().replaceAll("\\.", File.separator) + ".java"; buildClassPath = defaultBuildClassPath(buildClass); @@ -197,7 +209,7 @@ public class NoBuild { if (!classNeedsRebuild(buildClass.getName(), sourcePaths)) return; - logger.info(String.format("Recompiling %s...", buildSource)); + logger.info("Recompiling %s...".formatted(buildSource)); if (!compileJava(buildClassPath, sourcePaths)) { logger.severe("Compilation failed"); @@ -208,35 +220,29 @@ public class NoBuild { System.exit(status); } - /** - * Runs a Java class - * - * @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass} - * @return Exit status code - */ + /// Runs a Java class. + /// + /// @param mainClass The fully qualified class name, e.g. `com.example.MyClass$MySubClass` + /// @return Exit status code public static int runJava(String mainClass) { return runJava(mainClass, new String[0]); } - /** - * Runs a Java class - * - * @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass} - * @param args Arguments to pass to the main method - * @return Exit status code - */ + /// Runs a Java class. + /// + /// @param mainClass The fully qualified class name, e.g. `com.example.MyClass$MySubClass` + /// @param args Arguments to pass to the main method + /// @return Exit status code public static int runJava(String mainClass, String... args) { return runJava(new String[0], mainClass, args); } - /** - * Runs a Java class - * - * @param additionalClassPaths Additional class paths to pass to the compiler - * @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass} - * @param args Arguments to pass to the main method - * @return Exit status code - */ + /// Runs a Java class. + /// + /// @param additionalClassPaths Additional class paths to pass to the compiler + /// @param mainClass The fully qualified class name, e.g. `com.example.MyClass$MySubClass` + /// @param args Arguments to pass to the main method + /// @return Exit status code public static int runJava(String[] additionalClassPaths, String mainClass, String... args) { String pathSeparator = System.getProperty("path.separator"); @@ -253,15 +259,13 @@ public class NoBuild { return command(commandLineStream.toArray(String[]::new)); } - /** - * Runs a Java class - * - * @param additionalClassPaths Additional class paths to pass to the compiler - * @param javaArguments Arguments to pass to the java binary - * @param mainClass The fully qualified class name, e.g. {@code com.example.MyClass$MySubClass} - * @param args Arguments to pass to the main method - * @return Exit status code - */ + /// Runs a Java class. + /// + /// @param additionalClassPaths Additional class paths to pass to the compiler + /// @param javaArguments Arguments to pass to the java binary + /// @param mainClass The fully qualified class name, e.g. `com.example.MyClass$MySubClass` + /// @param args Arguments to pass to the main method + /// @return Exit status code public static int runJava( String[] additionalClassPaths, String[] javaArguments, String mainClass, String... args) { String pathSeparator = System.getProperty("path.separator"); @@ -283,38 +287,30 @@ public class NoBuild { return command(commandLineStream.toArray(String[]::new)); } - /** - * Compiles java sources - * - * @return {@code true} if all the sources compiled successfully - */ + /// Compiles java sources. + /// + /// @return `true` if all the sources compiled successfully public static boolean compileJava(String... sourcePaths) { return compileJava(new String[0], buildClassPath, sourcePaths); } - /** - * Compiles java sources - * - * @return {@code true} if all the sources compiled successfully - */ + /// Compiles java sources. + /// + /// @return `true` if all the sources compiled successfully public static boolean compileJava(String[] additionalClassPaths, String... sourcePaths) { return compileJava(additionalClassPaths, buildClassPath, sourcePaths); } - /** - * Compiles java sources - * - * @return {@code true} if all the sources compiled successfully - */ + /// Compiles java sources. + /// + /// @return `true` if all the sources compiled successfully public static boolean compileJava(String classOutputPath, String... sourcePaths) { return compileJava(new String[0], classOutputPath, sourcePaths); } - /** - * Compiles java sources - * - * @return {@code true} if all the sources compiled successfully - */ + /// Compiles java sources. + /// + /// @return `true` if all the sources compiled successfully public static boolean compileJava( String[] additionalClassPaths, String classOutputPath, String... sourcePaths) { try (StandardJavaFileManager fileManager = @@ -343,11 +339,9 @@ public class NoBuild { } } - /** - * Runs a shell command - * - * @return Exit status code - */ + /// Runs a shell command. + /// + /// @return Exit status code public static int command(String... command) { try { return commandThrows(command); @@ -357,12 +351,10 @@ public class NoBuild { } } - /** - * Runs a shell command. May throw an {@code IOException} - * - * @return Exit status code - * @throws Exception - */ + /// Runs a shell command. May throw an `IOException`. + /// + /// @return Exit status code + /// @throws Exception public static int commandThrows(String... command) throws IOException { ProcessBuilder pb = new ProcessBuilder(command).inheritIO(); Process process; @@ -377,13 +369,11 @@ public class NoBuild { } } - /** - * Returns file paths that match a {@code globPattern} - * - * @see java.nio.file.FileSystem#getPathMatcher(String) getPathMatcher - */ + /// Returns file paths that match a `globPattern`. + /// + /// @see java.nio.file.FileSystem#getPathMatcher(String) getPathMatcher public static String[] glob(String globPattern) { - Path cwd = Paths.get("."); + Path cwd = Path.of("."); PathMatcher pathMatcher = FileSystems.getDefault() .getPathMatcher(String.join("", "glob:", cwd.toString(), File.separator, globPattern)); @@ -396,7 +386,7 @@ public class NoBuild { } } - /** Checks if the {@code targetPath} needs to be rebuilt from {@code sourcePaths} */ + /// Checks if the `targetPath` needs to be rebuilt from `sourcePaths`. public static boolean needsRebuild(String targetPath, String... sourcePaths) { long targetLastModified = new File(targetPath).lastModified(); return targetLastModified == 0 @@ -404,7 +394,7 @@ public class NoBuild { .anyMatch(sourcePath -> new File(sourcePath).lastModified() > targetLastModified); } - /** Checks if the {@code targetPath} needs to be rebuilt from {@code sourcePaths} */ + /// Checks if the `targetPath` needs to be rebuilt from `sourcePaths`. public static boolean needsRebuild(Path targetPath, Path... sourcePaths) { long targetLastModified = targetPath.toFile().lastModified(); return targetLastModified == 0 @@ -412,21 +402,17 @@ public class NoBuild { .anyMatch(sourcePath -> sourcePath.toFile().lastModified() > targetLastModified); } - /** - * Checks if the class {@code className} needs to be rebuilt from {@code sourcePaths} - * - * @param className The fully qualified class name - */ + /// Checks if the class `className` needs to be rebuilt from `sourcePaths`. + /// + /// @param className The fully qualified class name public static boolean classNeedsRebuild(String className, String... sourcePaths) { return classNeedsRebuild(buildClassPath, className, sourcePaths); } - /** - * Checks if the class {@code className} needs to be rebuilt from {@code sourcePaths} - * - * @param classPath Path to look for the class in - * @param className The fully qualified class name - */ + /// Checks if the class `className` needs to be rebuilt from `sourcePaths`. + /// + /// @param classPath Path to look for the class in + /// @param className The fully qualified class name public static boolean classNeedsRebuild( String classPath, String className, String... sourcePaths) { String targetClass = @@ -435,50 +421,13 @@ public class NoBuild { return needsRebuild(targetClass, sourcePaths); } - /** Indents lines in {@code str} with {@code n} spaces */ - public static String indent(String str, int n) { - if (str.isEmpty()) { - return ""; - } else { - StringBuilder sb = new StringBuilder(n); - for (int i = 0; i < n; i++) { - sb.append(" "); - } - String spaces = sb.toString(); - return Arrays.stream(str.split("\n")) - .map(line -> spaces + line) - .collect(Collectors.joining("\n", "", "\n")); - } - } - - /** Transfers bytes from IO streams {@code in} to {@code out} */ - public static long transferTo(InputStream in, OutputStream out) throws IOException { - Objects.requireNonNull(out, "out"); - long transferred = 0L; - byte[] buffer = new byte[16384]; - - int read; - while ((read = in.read(buffer, 0, 16384)) >= 0) { - out.write(buffer, 0, read); - if (transferred < Long.MAX_VALUE) { - try { - transferred = Math.addExact(transferred, (long) read); - } catch (ArithmeticException ex) { - transferred = Long.MAX_VALUE; - } - } - } - - return transferred; - } - - /** Downloads file from {@code url} to {@code destination} */ + /// Downloads file from `url` to `destination`. public static boolean downloadArtefact(String url, Path destination) { try { InputStream urlStream = URI.create(url).toURL().openStream(); Files.createDirectories(destination.getParent()); - transferTo(urlStream, new FileOutputStream(destination.toFile())); + urlStream.transferTo(new FileOutputStream(destination.toFile())); return true; } catch (IOException ex) { @@ -487,13 +436,13 @@ public class NoBuild { } } - /** Downloads file from {@code url} to {@code destination} */ + /// Downloads file from `url` to `destination`. public static boolean downloadArtefact(URL url, Path destination) { try { InputStream urlStream = url.openStream(); Files.createDirectories(destination.getParent()); - transferTo(urlStream, new FileOutputStream(destination.toFile())); + urlStream.transferTo(new FileOutputStream(destination.toFile())); return true; } catch (IOException ex) { @@ -502,26 +451,22 @@ public class NoBuild { } } - /** Describes a Java dependency */ + /// Describes a Java dependency. public static record Dependency(String group, String name, String version) {} - /** URL template for maven central dependencies */ + /// URL template for maven central dependencies. public static String mavenCentral = "https://repo1.maven.org/maven2/%1$s/%2$s/%3$s/%2$s-%3$s.jar"; - /** - * Downloads {@code dependencies} from {@code repository} - * - * @param repository URL template for a repository, template arguments are provided: - *

- * See {@link NoBuild#mavenCentral} - * @param destination Path to download artefacts to - * @param dependencies Dependencies to download - * @return {@code true} if all dependencies were downloaded successfully - */ + /// Downloads `dependencies` from `repository`. + /// + /// @param repository URL template for a repository, template arguments are provided: + /// - `%1$s`: group id, delimeted with slashes + /// - `%2$s`: artefact name + /// - `%3$s`: artefact version + /// See [mavenCentral][NoBuild#mavenCentral] + /// @param destination Path to download artefacts to + /// @param dependencies Dependencies to download + /// @return `true` if all dependencies were downloaded successfully public static boolean downloadDependencies( String repository, Path destination, Dependency... dependencies) { boolean success = true;