Skip to content

Instantly share code, notes, and snippets.

@bowbahdoe
Last active January 19, 2022 03:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bowbahdoe/842cb24e5f3b83bc400be1530bd0f181 to your computer and use it in GitHub Desktop.
Save bowbahdoe/842cb24e5f3b83bc400be1530bd0f181 to your computer and use it in GitHub Desktop.
/**
* A set of utilities for building java projects.
*/
public final class BuildTools {
private BuildTools() {}
/**
* Project root path, defaults to current directory.
* Use `resolve-path` to resolve relative paths in terms of the *project-root*.
* Use BuildTools#setProjectRoot to override the default for all tasks.
*/
public static String projectRoot() {
return (String) DEREF.invoke(API_PROJECT_ROOT);
}
/**
* Sets the project root in the given scope.
*/
@SuppressWarnings("unchecked")
public static <T> T withProjectRoot(String projectRoot, Supplier<T> callable) {
var sym = GENSYM.invoke();
var code = LIST.invoke(
SYMBOL.invoke("fn"),
VECTOR.invoke(sym),
LIST.invoke(
SYMBOL.invoke("clojure.core", "binding"),
VECTOR.invoke(
SYMBOL.invoke("clojure.tools.build.api", "*project-root*"),
projectRoot
),
LIST.invoke(SYMBOL.invoke(".get"), sym)
)
);
return (T) ((IFn) EVAL.invoke(code)).invoke(callable);
}
/**
* Copy the contents of the src-dirs to the target-dir, optionally do text replacement.
* @param targetDir dir to write files, will be created if it doesn't exist
* @param srcDirs coll of dirs to copy from
*/
public static void copyDir(String targetDir, List<String> srcDirs) {
copyDir(targetDir, srcDirs, CopyDirOptions.builder().build());
}
/**
* Copy the contents of the src-dirs to the target-dir, optionally do text replacement.
* @param targetDir dir to write files, will be created if it doesn't exist
* @param srcDirs coll of dirs to copy from
* @param options options
*/
public static void copyDir(String targetDir, List<String> srcDirs, CopyDirOptions options) {
Objects.requireNonNull(targetDir);
Objects.requireNonNull(srcDirs);
var args = HASH_MAP.invoke(
Clojure.read(":target-dir"), targetDir,
Clojure.read(":src-dirs"), SEQ.invoke(srcDirs)
);
if (options.include != null) {
args = ASSOC.invoke(args, Clojure.read(":include"), options.include);
}
if (options.ignores != null) {
args = ASSOC.invoke(args, Clojure.read(":ignores"), SEQ.invoke(options.ignores));
}
if (options.replace != null) {
args = ASSOC.invoke(args, Clojure.read(":replace"), INTO.invoke(Clojure.read("{}"), options.replace));
}
if (options.nonReplacedExts != null) {
args = ASSOC.invoke(args, Clojure.read(":non-replaced-exts"), SEQ.invoke(options.nonReplacedExts));
}
API_COPY_DIR.invoke(args);
}
/**
* Copy one file from source to target, creating target dirs if needed.
* @param src Source path
* @param target Target path
*/
public static void copyFile(String src, String target) {
API_COPY_FILE.invoke(
HASH_MAP.invoke(
Clojure.read(":src"), src,
Clojure.read(":target"), target
)
);
}
public static Basis createBasis() {
return new Basis(API_CREATE_BASIS.invoke());
}
public static Basis createBasis(CreateBasisOptions options) {
Objects.requireNonNull(options);
Function<DepSource, Object> toClojure = depSource -> {
if (depSource instanceof DepSource.Standard __) {
return Clojure.read(":standard");
}
else {
return ((DepSource.Path) depSource).path();
}
};
var args = Clojure.read("{}");
if (options.root != null) {
args = ASSOC.invoke(args, Clojure.read(":include"), toClojure.apply(options.root));
}
if (options.user != null) {
args = ASSOC.invoke(args, Clojure.read(":user"), toClojure.apply(options.user));
}
if (options.project != null) {
args = ASSOC.invoke(args, Clojure.read(":project"), toClojure.apply(options.project));
}
if (options.extra != null) {
args = ASSOC.invoke(args, Clojure.read(":extra"), toClojure.apply(options.extra));
}
if (options.aliases != null) {
args = ASSOC.invoke(args, Clojure.read(":aliases"), VEC.invoke(options.aliases
.stream()
.map(alias -> {
if (alias.startsWith(":")) {
return KEYWORD.invoke(alias.substring(1));
}
else {
return KEYWORD.invoke(alias);
}
})
.toList())
);
}
return new Basis(API_CREATE_BASIS.invoke(args));
}
/**
* Delete file or directory recursively, if it exists.
*/
public static void delete(String path) {
API_DELETE.invoke(HASH_MAP.invoke(Clojure.read(":path"), path));
}
/**
* Shells out to git and returns count of commits on this branch:
* git rev-list HEAD --count
*/
public static Long gitCountRevs(GitCountRevsOptions options) {
Objects.requireNonNull(options);
Object args = Clojure.read("{}");
if (options.dir != null) {
args = ASSOC.invoke(args, Clojure.read(":include"), options.dir);
}
if (options.gitCommand != null) {
args = ASSOC.invoke(args, Clojure.read(":git-command"), options.gitCommand);
}
if (options.path != null) {
args = ASSOC.invoke(args, Clojure.read(":path"), options.path);
}
Object revs = API_GIT_COUNT_REVS.invoke(args);
if (revs == null) {
return null;
}
else if (revs instanceof Number n) {
return n.longValue();
}
else {
return Long.parseLong((String) revs);
}
}
/**
* Shells out to git and returns count of commits on this branch:
* git rev-list HEAD --count
*/
public static Long gitCountRevs() {
return gitCountRevs(GitCountRevsOptions.builder().build());
}
public static String gitProcess(List<String> gitArgs) {
return gitProcess(gitArgs, GitProcessOptions.builder().build());
}
/**
* Run git process in the specified dir using git-command with git-args (which should not
* start with "git"). By default, stdout is captured, trimmed, and returned.
*/
public static String gitProcess(List<String> gitArgs, GitProcessOptions options) {
Object args = HASH_MAP.invoke(
Clojure.read(":git-args"), VEC.invoke(gitArgs)
);
if (options.capture != null) {
args = ASSOC.invoke(args, Clojure.read(":capture"), switch (options.capture) {
case OUT -> Clojure.read(":out");
case ERR -> Clojure.read(":err");
case NOTHING -> null;
});
}
if (options.dir != null) {
args = ASSOC.invoke(args, Clojure.read(":dir"), options.dir);
}
if (options.gitCommand != null) {
args = ASSOC.invoke(args, Clojure.read(":git-command"), options.gitCommand);
}
return (String) API_GIT_PROCESS.invoke(args);
}
public static void install(InstallOptions options) {
API_INSTALL.invoke();
}
public static void jar(String classDir, String jarFile) {
jar(classDir, jarFile, JarOptions.builder().build());
}
public static void jar(String classDir, String jarFile, JarOptions options) {
Objects.requireNonNull(classDir);
Objects.requireNonNull(jarFile);
Objects.requireNonNull(options);
Object args = HASH_MAP.invoke(
Clojure.read(":class-dir"), classDir,
Clojure.read(":jar-file"), jarFile
);
if (options.main != null) {
args = ASSOC.invoke(args, Clojure.read(":main"), SYMBOL.invoke(options.main));
}
API_JAR.invoke(args);
}
public static List<String> javaCommand(String main) {
return javaCommand(main, JavaCommandOptions.builder().build());
}
@SuppressWarnings("unchecked")
public static List<String> javaCommand(String main, JavaCommandOptions options) {
Objects.requireNonNull(main);
Objects.requireNonNull(options);
var result = API_JAVA_COMMAND.invoke(
ASSOC.invoke(options.toClojure(), Clojure.read(":main"), SYMBOL.invoke(main))
);
return (List<String>) ((IFn) Clojure.read(":command-args")).invoke(result);
}
public static void javac(
List<Path> srcDirs,
String classDir,
List<String> javacOpts,
Basis basis
) {
Objects.requireNonNull(srcDirs);
Objects.requireNonNull(classDir);
var args = HASH_MAP.invoke(
Clojure.read(":src-dirs"), SEQ.invoke(srcDirs.stream().map(Path::toString).toList()),
Clojure.read(":class-dir"), classDir
);
if (basis != null) {
args = ASSOC.invoke(args, Clojure.read(":basis"), basis.rawBasisObject);
}
if (javacOpts != null) {
args = ASSOC.invoke(args, Clojure.read(":javac-opts"), SEQ.invoke(javacOpts));
}
API_JAVAC.invoke(args);
}
public static void setProjectRoot(String newProjectRoot) {
API_SET_PROJECT_ROOT.invoke(newProjectRoot);
}
public static <ConflictHandlerState> void uber(
UberOptions<ConflictHandlerState> options
) {
API_UBER.invoke(options.toClojure());
}
/**
* Create zip file containing contents of src dirs.
*
* @param srcDirs coll of source directories to include in zip
* @param zipFile zip file to create
*/
public static void zip(List<String> srcDirs, String zipFile) {
API_ZIP.invoke(HASH_MAP.invoke(
Clojure.read(":src-dirs"), SEQ.invoke(srcDirs),
Clojure.read(":zip-file"), zipFile
));
}
/**
* All the clojure vars required by this library.
*/
final class Requires {
static final IFn DEREF;
static final IFn SEQ;
static final IFn VEC;
static final IFn INTO;
static final IFn ASSOC;
static final IFn KEYWORD;
static final IFn SYMBOL;
static final IFn HASH_MAP;
static final IFn VECTOR;
static final IFn LIST;
static final IFn GENSYM;
static final IFn EVAL;
static final IFn API_PROJECT_ROOT;
static final IFn API_COPY_DIR;
static final IFn API_COPY_FILE;
static final IFn API_CREATE_BASIS;
static final IFn API_DELETE;
static final IFn API_GIT_COUNT_REVS;
static final IFn API_GIT_PROCESS;
static final IFn API_INSTALL;
static final IFn API_JAR;
static final IFn API_JAVA_COMMAND;
static final IFn API_JAVAC;
static final IFn API_POM_PATH;
static final IFn API_PROCESS;
static final IFn API_RESOLVE_PATH;
static final IFn API_SET_PROJECT_ROOT;
static final IFn API_UBER;
static final IFn API_UNZIP;
static final IFn API_WRITE_FILE;
static final IFn API_WRITE_POM;
static final IFn API_ZIP;
static {
DEREF = Clojure.var("clojure.core","deref");
SEQ = Clojure.var("clojure.core","seq");
VEC = Clojure.var("clojure.core", "vec");
INTO = Clojure.var("clojure.core","into");
ASSOC = Clojure.var("clojure.core","assoc");
KEYWORD = Clojure.var("clojure.core", "keyword");
SYMBOL = Clojure.var("clojure.core", "symbol");
HASH_MAP = Clojure.var("clojure.core", "hash-map");
VECTOR = Clojure.var("clojure.core", "vector");
LIST = Clojure.var("clojure.core", "list");
GENSYM = Clojure.var("clojure.core", "gensym");
EVAL = Clojure.var("clojure.core", "eval");
IFn REQUIRE = Clojure.var("clojure.core", "require");
String api = "clojure.tools.build.api";
REQUIRE.invoke(Clojure.read(api));
API_PROJECT_ROOT = Clojure.var(api, "*project-root*");
API_COPY_DIR = Clojure.var(api, "copy-dir");
API_COPY_FILE = Clojure.var(api, "copy-file");
API_CREATE_BASIS = Clojure.var(api, "create-basis");
API_DELETE = Clojure.var(api, "delete");
API_GIT_COUNT_REVS = Clojure.var(api, "git-count-revs");
API_GIT_PROCESS = Clojure.var(api, "git-process");
API_INSTALL = Clojure.var(api, "install");
API_JAR = Clojure.var(api, "jar");
API_JAVA_COMMAND = Clojure.var(api, "java-command");
API_JAVAC = Clojure.var(api, "javac");
API_POM_PATH = Clojure.var(api, "pom-path");
API_PROCESS = Clojure.var(api, "process");
API_RESOLVE_PATH = Clojure.var(api, "resolve-path");
API_SET_PROJECT_ROOT = Clojure.var(api, "set-project-root!");
API_UBER = Clojure.var(api, "uber");
API_UNZIP = Clojure.var(api, "unzip");
API_WRITE_FILE = Clojure.var(api, "write-file");
API_WRITE_POM = Clojure.var(api, "write-pom");
API_ZIP = Clojure.var(api, "zip");
}
private Requires() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment