Compare commits
2 Commits
main
...
multithrea
| Author | SHA1 | Date | |
|---|---|---|---|
| e97a196354 | |||
| e782d88f37 |
42
Build.java
42
Build.java
@@ -1,14 +1,10 @@
|
||||
import static nobuild.NoBuild.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Build {
|
||||
@@ -61,9 +57,8 @@ public class Build {
|
||||
break;
|
||||
|
||||
case "package":
|
||||
clean();
|
||||
buildJtags(mainClass, sourcePaths, classPaths);
|
||||
packageJar(Optional.ofNullable(arguments.poll()).orElse("Jtags.jar"));
|
||||
packageJar(Optional.ofNullable(arguments.poll()));
|
||||
break;
|
||||
|
||||
case "test":
|
||||
@@ -90,34 +85,15 @@ public class Build {
|
||||
}
|
||||
}
|
||||
|
||||
static void clean() {
|
||||
command("rm", "-rf", buildClassPath + "/xyz");
|
||||
}
|
||||
|
||||
static void packageJar(String jarfile) {
|
||||
File file = new File(jarfile);
|
||||
file.delete();
|
||||
|
||||
static void packageJar(Optional<String> jarfile) {
|
||||
command(
|
||||
"jar", "cfe", jarfile, "xyz.naofal.jtags.Jtags", "-C", buildClassPath, "xyz/naofal/jtags");
|
||||
|
||||
// Make jar file executable by hashbang
|
||||
byte[] hashbang = "#!/usr/bin/env -S java -jar\n".getBytes();
|
||||
int fileLength = (int) file.length();
|
||||
int length = fileLength + hashbang.length;
|
||||
byte[] bytes = new byte[length];
|
||||
System.arraycopy(hashbang, 0, bytes, 0, hashbang.length);
|
||||
try {
|
||||
FileInputStream in = new FileInputStream(file);
|
||||
in.read(bytes, hashbang.length, fileLength);
|
||||
in.close();
|
||||
FileOutputStream out = new FileOutputStream(file);
|
||||
out.write(bytes);
|
||||
out.close();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Problem during file IO with " + jarfile, ex);
|
||||
}
|
||||
file.setExecutable(true);
|
||||
"jar",
|
||||
"cfe",
|
||||
jarfile.orElse("Jtags.jar"),
|
||||
"xyz.naofal.jtags.Jtags",
|
||||
"-C",
|
||||
buildClassPath,
|
||||
".");
|
||||
}
|
||||
|
||||
static void runTests(String[] args) {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
/* NoBuild system
|
||||
/*
|
||||
* NoBuild system
|
||||
*
|
||||
* @version 0.1
|
||||
* @since 1.8
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
@@ -28,110 +32,89 @@ 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.Optional;
|
||||
import java.util.Objects;
|
||||
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:
|
||||
///
|
||||
/// ```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
|
||||
////
|
||||
/**
|
||||
* NoBuild system
|
||||
*
|
||||
* <h2>Quick Guide</h2>
|
||||
*
|
||||
* A simple build script:
|
||||
*
|
||||
* <pre>
|
||||
* // Build.java
|
||||
* import static nobuild.Nobuild.*;
|
||||
*
|
||||
* public class Build {
|
||||
* public static void main(String[] args) {
|
||||
* rebuildSelf(Build.class, args);
|
||||
* compileJava("Hello.java");
|
||||
* runJava("Hello");
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Which can be run as follows:
|
||||
*
|
||||
* <pre>
|
||||
* $ javac -d build/ Build.java
|
||||
* $ java -cp build/ Build
|
||||
* </pre>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
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 = Path.of(System.getProperty("java.home"));
|
||||
public static Path javaHome = Paths.get(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 `null` if [rebuildSelf][#rebuildSelf] hasn't been
|
||||
/// called.
|
||||
///
|
||||
/// Will be used as the default output directory for compilation.
|
||||
/**
|
||||
* Path to the build script class file, or {@code null} if {@link #rebuildSelf} hasn't been
|
||||
* called.
|
||||
*
|
||||
* <p>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 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();
|
||||
URL buildClassUrl = buildClass.getResource(buildClass.getName() + ".class");
|
||||
return Paths.get(buildClassUrl.getPath()).getParent().toString();
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -171,7 +154,7 @@ public class NoBuild {
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
if (record.getMessage() != null && !record.getMessage().isEmpty()) {
|
||||
if (Optional.ofNullable(System.console()).map(it -> it.isTerminal()).orElse(false)) {
|
||||
if (System.console().isTerminal()) {
|
||||
int color = colors.get(record.getLevel());
|
||||
System.err.printf(
|
||||
"\u001b[38;5;%dm[%s]\u001b[0m %s%n",
|
||||
@@ -181,26 +164,30 @@ public class NoBuild {
|
||||
}
|
||||
}
|
||||
if (record.getThrown() != null) {
|
||||
System.err.println(record.getThrown().toString().indent(4));
|
||||
System.err.println(indent(record.getThrown().toString(), 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";
|
||||
String buildSource = buildClass.getName() + ".java";
|
||||
buildClassPath = defaultBuildClassPath(buildClass);
|
||||
|
||||
String[] sourcePaths =
|
||||
@@ -209,7 +196,7 @@ public class NoBuild {
|
||||
|
||||
if (!classNeedsRebuild(buildClass.getName(), sourcePaths)) return;
|
||||
|
||||
logger.info("Recompiling %s...".formatted(buildSource));
|
||||
logger.info(String.format("Recompiling %s...", buildSource));
|
||||
|
||||
if (!compileJava(buildClassPath, sourcePaths)) {
|
||||
logger.severe("Compilation failed");
|
||||
@@ -220,29 +207,35 @@ public class NoBuild {
|
||||
System.exit(status);
|
||||
}
|
||||
|
||||
/// Runs a Java class.
|
||||
///
|
||||
/// @param mainClass The fully qualified class name, e.g. `com.example.MyClass$MySubClass`
|
||||
/// @return Exit status code
|
||||
/**
|
||||
* Runs a Java class
|
||||
*
|
||||
* @param mainClass The fully qualified class name, e.g. {@code 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. `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. {@code 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. `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. {@code 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");
|
||||
|
||||
@@ -259,13 +252,15 @@ 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. `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. {@code 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");
|
||||
@@ -287,30 +282,38 @@ public class NoBuild {
|
||||
return command(commandLineStream.toArray(String[]::new));
|
||||
}
|
||||
|
||||
/// Compiles java sources.
|
||||
///
|
||||
/// @return `true` if all the sources compiled successfully
|
||||
/**
|
||||
* Compiles java sources
|
||||
*
|
||||
* @return {@code true} if all the sources compiled successfully
|
||||
*/
|
||||
public static boolean compileJava(String... sourcePaths) {
|
||||
return compileJava(new String[0], buildClassPath, sourcePaths);
|
||||
}
|
||||
|
||||
/// Compiles java sources.
|
||||
///
|
||||
/// @return `true` if all the sources compiled successfully
|
||||
/**
|
||||
* Compiles java sources
|
||||
*
|
||||
* @return {@code true} if all the sources compiled successfully
|
||||
*/
|
||||
public static boolean compileJava(String[] additionalClassPaths, String... sourcePaths) {
|
||||
return compileJava(additionalClassPaths, buildClassPath, sourcePaths);
|
||||
}
|
||||
|
||||
/// Compiles java sources.
|
||||
///
|
||||
/// @return `true` if all the sources compiled successfully
|
||||
/**
|
||||
* Compiles java sources
|
||||
*
|
||||
* @return {@code 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 `true` if all the sources compiled successfully
|
||||
/**
|
||||
* Compiles java sources
|
||||
*
|
||||
* @return {@code true} if all the sources compiled successfully
|
||||
*/
|
||||
public static boolean compileJava(
|
||||
String[] additionalClassPaths, String classOutputPath, String... sourcePaths) {
|
||||
try (StandardJavaFileManager fileManager =
|
||||
@@ -339,9 +342,11 @@ 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);
|
||||
@@ -351,10 +356,12 @@ public class NoBuild {
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a shell command. May throw an `IOException`.
|
||||
///
|
||||
/// @return Exit status code
|
||||
/// @throws Exception
|
||||
/**
|
||||
* Runs a shell command. May throw an {@code IOException}
|
||||
*
|
||||
* @return Exit status code
|
||||
* @throws Exception
|
||||
*/
|
||||
public static int commandThrows(String... command) throws IOException {
|
||||
ProcessBuilder pb = new ProcessBuilder(command).inheritIO();
|
||||
Process process;
|
||||
@@ -369,24 +376,27 @@ public class NoBuild {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns file paths that match a `globPattern`.
|
||||
///
|
||||
/// @see java.nio.file.FileSystem#getPathMatcher(String) getPathMatcher
|
||||
/**
|
||||
* Returns file paths that match a {@code globPattern}
|
||||
*
|
||||
* @see java.nio.file.FileSystem#getPathMatcher(String) getPathMatcher
|
||||
*/
|
||||
public static String[] glob(String globPattern) {
|
||||
Path cwd = Path.of(".");
|
||||
Path cwd = Paths.get(".");
|
||||
PathMatcher pathMatcher =
|
||||
FileSystems.getDefault()
|
||||
.getPathMatcher(String.join("", "glob:", cwd.toString(), File.separator, globPattern));
|
||||
try (Stream<Path> paths =
|
||||
Files.find(
|
||||
cwd, Integer.MAX_VALUE, (path, basicFileAttributes) -> pathMatcher.matches(path))) {
|
||||
try (@SuppressWarnings("unused")
|
||||
Stream<Path> paths =
|
||||
Files.find(
|
||||
cwd, Integer.MAX_VALUE, (path, basicFileAttributes) -> pathMatcher.matches(path))) {
|
||||
return paths.map(Path::toString).toArray(String[]::new);
|
||||
} catch (IOException e) {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `targetPath` needs to be rebuilt from `sourcePaths`.
|
||||
/** Checks if the {@code targetPath} needs to be rebuilt from {@code sourcePaths} */
|
||||
public static boolean needsRebuild(String targetPath, String... sourcePaths) {
|
||||
long targetLastModified = new File(targetPath).lastModified();
|
||||
return targetLastModified == 0
|
||||
@@ -394,7 +404,7 @@ public class NoBuild {
|
||||
.anyMatch(sourcePath -> new File(sourcePath).lastModified() > targetLastModified);
|
||||
}
|
||||
|
||||
/// Checks if the `targetPath` needs to be rebuilt from `sourcePaths`.
|
||||
/** Checks if the {@code targetPath} needs to be rebuilt from {@code sourcePaths} */
|
||||
public static boolean needsRebuild(Path targetPath, Path... sourcePaths) {
|
||||
long targetLastModified = targetPath.toFile().lastModified();
|
||||
return targetLastModified == 0
|
||||
@@ -402,17 +412,21 @@ public class NoBuild {
|
||||
.anyMatch(sourcePath -> sourcePath.toFile().lastModified() > targetLastModified);
|
||||
}
|
||||
|
||||
/// Checks if the class `className` needs to be rebuilt from `sourcePaths`.
|
||||
///
|
||||
/// @param className The fully qualified class name
|
||||
/**
|
||||
* Checks if the class {@code className} needs to be rebuilt from {@code 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 `className` needs to be rebuilt from `sourcePaths`.
|
||||
///
|
||||
/// @param classPath Path to look for the class in
|
||||
/// @param className The fully qualified class name
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static boolean classNeedsRebuild(
|
||||
String classPath, String className, String... sourcePaths) {
|
||||
String targetClass =
|
||||
@@ -421,13 +435,50 @@ public class NoBuild {
|
||||
return needsRebuild(targetClass, sourcePaths);
|
||||
}
|
||||
|
||||
/// Downloads file from `url` to `destination`.
|
||||
/** 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} */
|
||||
public static boolean downloadArtefact(String url, Path destination) {
|
||||
try {
|
||||
InputStream urlStream = URI.create(url).toURL().openStream();
|
||||
|
||||
Files.createDirectories(destination.getParent());
|
||||
urlStream.transferTo(new FileOutputStream(destination.toFile()));
|
||||
transferTo(urlStream, new FileOutputStream(destination.toFile()));
|
||||
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
@@ -436,13 +487,13 @@ public class NoBuild {
|
||||
}
|
||||
}
|
||||
|
||||
/// Downloads file from `url` to `destination`.
|
||||
/** Downloads file from {@code url} to {@code destination} */
|
||||
public static boolean downloadArtefact(URL url, Path destination) {
|
||||
try {
|
||||
InputStream urlStream = url.openStream();
|
||||
|
||||
Files.createDirectories(destination.getParent());
|
||||
urlStream.transferTo(new FileOutputStream(destination.toFile()));
|
||||
transferTo(urlStream, new FileOutputStream(destination.toFile()));
|
||||
|
||||
return true;
|
||||
} catch (IOException ex) {
|
||||
@@ -451,22 +502,26 @@ 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 `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
|
||||
/**
|
||||
* Downloads {@code dependencies} from {@code repository}
|
||||
*
|
||||
* @param repository URL template for a repository, template arguments are provided:
|
||||
* <ul>
|
||||
* <li>{@code %1$s}: group id, delimeted with slashes
|
||||
* <li>{@code %2$s}: artefact name
|
||||
* <li>{@code %3$s}: artefact version
|
||||
* </ul>
|
||||
* 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
|
||||
*/
|
||||
public static boolean downloadDependencies(
|
||||
String repository, Path destination, Dependency... dependencies) {
|
||||
boolean success = true;
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package xyz.naofal.jtags;
|
||||
|
||||
import static xyz.naofal.jtags.JtagsLogger.logger;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class ArchiveExtractor {
|
||||
static List<String> extractArchive(Path archive, Path destination, String pattern) {
|
||||
logger.info("Extracting " + archive.toString() + "...");
|
||||
List<String> extractedPaths = new ArrayList<>();
|
||||
Pattern compiledPattern = Pattern.compile(pattern);
|
||||
try (InputStream in = new FileInputStream(archive.toFile());
|
||||
ZipInputStream zis = new ZipInputStream(in); ) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zis.getNextEntry()) != null) {
|
||||
logger.finest("Extracting " + entry.getName() + "...");
|
||||
if (!compiledPattern.matcher(entry.getName()).matches()) {
|
||||
zis.closeEntry();
|
||||
entry = zis.getNextEntry();
|
||||
continue;
|
||||
}
|
||||
Path entryPath = destination.resolve(entry.getName());
|
||||
Files.createDirectories(entryPath.getParent());
|
||||
logger.finest(() -> "Extracting " + entryPath.toString() + "...");
|
||||
var bytes = zis.readAllBytes();
|
||||
Files.write(entryPath, bytes);
|
||||
extractedPaths.add(entryPath.toString());
|
||||
zis.closeEntry();
|
||||
}
|
||||
return extractedPaths;
|
||||
} catch (IOException ex) {
|
||||
logger.severe(ex.toString());
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class Jtags {
|
||||
static class Options {
|
||||
@@ -18,7 +17,6 @@ public class Jtags {
|
||||
boolean excludeAnonymous = false;
|
||||
List<Class<? extends TagField>> fields =
|
||||
List.of(TagField.StaticTag.class, TagField.Package.class, TagField.EnclosingType.class);
|
||||
Optional<Path> extractPath = Optional.empty();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -106,26 +104,11 @@ public class Jtags {
|
||||
System.exit(1);
|
||||
yield null;
|
||||
}
|
||||
case "-" -> Path.of("-");
|
||||
case String output -> Path.of(output).toAbsolutePath();
|
||||
case String output -> Path.of(output);
|
||||
};
|
||||
logger.config("Writing tags to " + options.output.toString());
|
||||
break;
|
||||
|
||||
case "-extract-dir":
|
||||
options.extractPath =
|
||||
switch (arguments.poll()) {
|
||||
case null -> {
|
||||
logger.severe("Expected argument after " + argument);
|
||||
printUsage();
|
||||
System.exit(1);
|
||||
yield null;
|
||||
}
|
||||
case String dir -> Optional.of(Path.of(dir).toAbsolutePath());
|
||||
};
|
||||
logger.config("Extracting JARs to " + options.extractPath.toString());
|
||||
break;
|
||||
|
||||
default:
|
||||
options.sources.add(argument);
|
||||
break;
|
||||
@@ -154,7 +137,7 @@ public class Jtags {
|
||||
Usage: jtags [options] <sources...>
|
||||
Options:
|
||||
-o, -output <file> Write tags to specified <file>
|
||||
Use - for standard output. Default: tags
|
||||
Use - for standard output
|
||||
-lib Treat sources as third-party libraries
|
||||
(alias for -no-non-public -no-anonymous)
|
||||
-no-anonymous Exclude anonymous classes
|
||||
@@ -165,7 +148,6 @@ Options:
|
||||
s static tag
|
||||
p package
|
||||
t enclosing type
|
||||
-extract-dir <dir> Extract JAR and ZIP archives to <dir>
|
||||
-h, -help Show this message
|
||||
""");
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package xyz.naofal.jtags;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
@@ -48,7 +47,7 @@ public class JtagsLogger {
|
||||
@Override
|
||||
public void publish(LogRecord record) {
|
||||
if (record.getMessage() != null && !record.getMessage().isEmpty()) {
|
||||
if (Optional.ofNullable(System.console()).map(it->it.isTerminal()).orElse(false)) {
|
||||
if (System.console().isTerminal()) {
|
||||
int color = colors.get(record.getLevel());
|
||||
System.err.printf(
|
||||
"\u001b[38;5;%dm[%s]\u001b[0m %s%n",
|
||||
|
||||
@@ -5,12 +5,11 @@ import static xyz.naofal.jtags.JtagsLogger.logger;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.util.JavacTask;
|
||||
import com.sun.source.util.Trees;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.AbstractQueue;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
@@ -24,61 +23,31 @@ public class TagCollector {
|
||||
public static AbstractQueue<Tag> collectTags(Options options) {
|
||||
try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null)) {
|
||||
|
||||
String[] sources = resolveSources(options);
|
||||
logger.finest(() -> "Collecting tags from sources: " + Arrays.toString(sources));
|
||||
|
||||
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(sources);
|
||||
Iterable<? extends JavaFileObject> compilationUnits =
|
||||
fileManager.getJavaFileObjects(options.sources.toArray(String[]::new));
|
||||
|
||||
logger.info("Parsing sources...");
|
||||
JavacTask task =
|
||||
(JavacTask) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
|
||||
Iterable<? extends CompilationUnitTree> trees = task.parse();
|
||||
|
||||
TreeVisitor treeVisitor = new TreeVisitor(options);
|
||||
TreeVisitorContext context = new TreeVisitorContext(Trees.instance(task));
|
||||
logger.info("Collecting tags...");
|
||||
|
||||
Trees treeUtils = Trees.instance(task);
|
||||
AbstractQueue<Tag> tags = new PriorityBlockingQueue<>();
|
||||
|
||||
TreeVisitor treeVisitor = new TreeVisitor(options, tags);
|
||||
for (CompilationUnitTree compilationUnitTree : trees) {
|
||||
treeVisitor.scan(compilationUnitTree, context);
|
||||
TreeVisitorContext context = new TreeVisitorContext(compilationUnitTree, treeUtils);
|
||||
treeVisitor.scan(compilationUnitTree, context);
|
||||
}
|
||||
|
||||
return treeVisitor.tags;
|
||||
return tags;
|
||||
|
||||
} catch (IOException ex) {
|
||||
} catch (Exception ex) {
|
||||
logger.severe(ex.toString());
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String[] resolveSources(Options options) {
|
||||
return options.sources.stream()
|
||||
.flatMap(
|
||||
path ->
|
||||
switch (path) {
|
||||
case String _ when path.endsWith(".java") -> Stream.of(path);
|
||||
case String _ when path.endsWith(".jar") || path.endsWith(".zip") -> {
|
||||
if (options.extractPath.isEmpty()) {
|
||||
logger.severe("Archive file specified, but no -extract-dir");
|
||||
System.exit(1);
|
||||
}
|
||||
Path archivePath = Path.of(path);
|
||||
String archiveName = archivePath.getFileName().toString();
|
||||
yield ArchiveExtractor.extractArchive(
|
||||
archivePath,
|
||||
options
|
||||
.extractPath
|
||||
.get()
|
||||
.resolve(archiveName.substring(0, archiveName.lastIndexOf('.'))),
|
||||
"^.*\\.java$")
|
||||
.stream();
|
||||
}
|
||||
default -> {
|
||||
logger.warning("Ignoring unsupported source: " + path);
|
||||
yield Stream.of();
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
// TODO: support package-info and module-info
|
||||
.filter(it -> !it.endsWith("package-info.java") && !it.endsWith("module-info.java"))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ public record TagsWriter(Options options) {
|
||||
private static final int MAX_PATTERN_LENGTH = 96;
|
||||
|
||||
public boolean writeTagsFile(AbstractQueue<Tag> tags) {
|
||||
logger.fine("Writing tags to " + options().output.toString());
|
||||
try (var outputStream =
|
||||
options().output.toString().equals("-")
|
||||
? System.out
|
||||
|
||||
@@ -13,39 +13,30 @@ import java.util.AbstractQueue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import xyz.naofal.jtags.Jtags.Options;
|
||||
|
||||
public class TreeVisitor extends TreePathScanner<Void, TreeVisitorContext> {
|
||||
public final Options options;
|
||||
public final AbstractQueue<Tag> tags = new PriorityBlockingQueue<>();
|
||||
public final AbstractQueue<Tag> tags;
|
||||
|
||||
public TreeVisitor(Options options) {
|
||||
public TreeVisitor(Options options, AbstractQueue<Tag> tagQueue) {
|
||||
this.options = options;
|
||||
this.tags = tagQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitCompilationUnit(CompilationUnitTree node, TreeVisitorContext p) {
|
||||
p.compilationUnitTree = node;
|
||||
|
||||
logger.fine(() -> "Collecting tags in file: " + p.getLocation());
|
||||
|
||||
if (node.getPackage() != null) {
|
||||
scan(node.getPackage(), p);
|
||||
}
|
||||
|
||||
return scan(node.getTypeDecls(), p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitPackage(PackageTree node, TreeVisitorContext p) {
|
||||
Tag tag =
|
||||
new Tag(TagKind.PACKAGE, node.getPackageName().toString(), p.getLocation().getParent(), "");
|
||||
|
||||
if (tags.contains(tag)) {
|
||||
return null;
|
||||
}
|
||||
new Tag(
|
||||
TagKind.PACKAGE, node.getPackageName().toString(), p.getLocation(), p.getLine(node));
|
||||
|
||||
logger.finer(() -> "Package: " + tag);
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@ import java.util.Optional;
|
||||
public class TreeVisitorContext {
|
||||
final Trees trees;
|
||||
final SourcePositions sourcePositions;
|
||||
public CompilationUnitTree compilationUnitTree;
|
||||
final CompilationUnitTree compilationUnitTree;
|
||||
|
||||
public TreeVisitorContext(Trees trees) {
|
||||
public TreeVisitorContext(CompilationUnitTree compilationUnitTree, Trees trees) {
|
||||
this.compilationUnitTree = compilationUnitTree;
|
||||
this.trees = trees;
|
||||
sourcePositions = trees.getSourcePositions();
|
||||
this.sourcePositions = trees.getSourcePositions();
|
||||
}
|
||||
|
||||
public Path getLocation() {
|
||||
@@ -32,12 +33,10 @@ public class TreeVisitorContext {
|
||||
long offset = getOffsetInSource(compilationUnitTree, node);
|
||||
try (var reader = new BufferedReader(compilationUnitTree.getSourceFile().openReader(true))) {
|
||||
long skipped = 0;
|
||||
String prevLine = "";
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) return prevLine;
|
||||
if (line == null) throw new RuntimeException("Reached the end of the stream");
|
||||
skipped += line.length() + 1;
|
||||
prevLine = line;
|
||||
if (skipped >= offset) return line;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import static notest.Test.Util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import notest.Test;
|
||||
|
||||
class TestJtags {
|
||||
@@ -18,10 +25,70 @@ class TestJtags {
|
||||
Jtags,
|
||||
"-o",
|
||||
"-",
|
||||
"src/test/java/examples/BasicExample.java");
|
||||
"src/test/resources/BasicExample.java");
|
||||
|
||||
var tags = readAllLines(process.inputReader());
|
||||
|
||||
getSnapshot().assertEquals(tags);
|
||||
}
|
||||
|
||||
static void downloadOpenjdkSources() throws IOException {
|
||||
Path destination = Path.of("src", "test", "resources", "downloads");
|
||||
if (Files.exists(destination.resolve("openjdk-src"))) return;
|
||||
|
||||
URL openjdkSrcZipURL =
|
||||
URI.create("https://github.com/openjdk/jdk24u/archive/refs/tags/jdk-24+36.zip").toURL();
|
||||
Path openjdkSrcZip = destination.resolve("jdk-24+36.zip");
|
||||
|
||||
if (!Files.exists(openjdkSrcZip)) {
|
||||
System.err.println("Downloading openjdk sources jdk-24+36.zip (~118MB)...");
|
||||
openjdkSrcZipURL
|
||||
.openConnection()
|
||||
.getInputStream()
|
||||
.transferTo(Files.newOutputStream(openjdkSrcZip));
|
||||
}
|
||||
|
||||
System.err.println("Extracting openjdk sources...");
|
||||
|
||||
Files.createDirectories(destination.resolve("openjdk-src"));
|
||||
|
||||
try (InputStream in = Files.newInputStream(openjdkSrcZip);
|
||||
ZipInputStream zin = new ZipInputStream(in)) {
|
||||
ZipEntry entry;
|
||||
while ((entry = zin.getNextEntry()) != null) {
|
||||
if (!entry.getName().startsWith("jdk24u-jdk-24-36/src/j")) {
|
||||
zin.closeEntry();
|
||||
entry = zin.getNextEntry();
|
||||
continue;
|
||||
}
|
||||
Path entryPath =
|
||||
destination.resolve(
|
||||
"openjdk-src", entry.getName().substring("jdk24u-jdk-24-36/src/".length()));
|
||||
if (entry.isDirectory()) {
|
||||
Files.createDirectories(entryPath);
|
||||
} else {
|
||||
var bytes = zin.readAllBytes();
|
||||
Files.write(entryPath, bytes);
|
||||
}
|
||||
zin.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
static void openjdkSources() throws IOException {
|
||||
downloadOpenjdkSources();
|
||||
|
||||
// var process =
|
||||
// runJava(
|
||||
// new String[] {"-Dlogger.level=CONFIG"},
|
||||
// Jtags,
|
||||
// Stream.concat(Stream.of("-o", "-"),
|
||||
// Arrays.stream(glob("src/test/resources/downloads/**/.java")))
|
||||
// .toArray(String[]::new));
|
||||
|
||||
// var tags = readAllLines(process.inputReader());
|
||||
|
||||
// getSnapshot().assertEquals(tags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,8 @@ public @interface Test {
|
||||
case "-u", "-update-snapshots":
|
||||
options.updateSnapshots = true;
|
||||
break;
|
||||
case null, default:
|
||||
case null:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
ABC
|
||||
!_TAG_FILE_ENCODING utf-8
|
||||
!_TAG_FILE_SORTED 2 /0=unsorted, 1=sorted, 2=foldcase/
|
||||
InnerClass src/test/java/examples/BasicExample.java /^ class InnerClass {$/;" Cls package:examples class:BasicExample
|
||||
method1 src/test/java/examples/BasicExample.java /^ private boolean method1() {$/;" mthd class:BasicExample
|
||||
method2 src/test/java/examples/BasicExample.java /^ void method2() {}$/;" mthd class:(Anonymous)
|
||||
methodX src/test/java/examples/BasicExample.java /^ public static void method2($/;" mthd file: class:BasicExample
|
||||
method3 src/test/java/examples/BasicExample.java /^ void method3() {}$/;" mthd class:InnerClass
|
||||
method4 src/test/java/examples/BasicExample.java /^ private void method4() {}$/;" mthd class:InnerClass
|
||||
BasicExample src/test/resources/BasicExample.java /^public class BasicExample {$/;" Cls package:examples
|
||||
InnerClass src/test/resources/BasicExample.java /^ class InnerClass {$/;" Cls package:examples class:BasicExample
|
||||
method1 src/test/resources/BasicExample.java /^ private boolean method1() {$/;" mthd class:BasicExample
|
||||
method2 src/test/resources/BasicExample.java /^ void method2() {}$/;" mthd class:(Anonymous)
|
||||
method2 src/test/resources/BasicExample.java /^ public static void method2($/;" mthd file: class:BasicExample
|
||||
method3 src/test/resources/BasicExample.java /^ void method3() {}$/;" mthd class:InnerClass
|
||||
method4 src/test/resources/BasicExample.java /^ private void method4() {}$/;" mthd class:InnerClass
|
||||
|
||||
Reference in New Issue
Block a user