diff --git a/common/src/main/java/org/implab/gradle/common/ProjectMixin.java b/common/src/main/java/org/implab/gradle/common/ProjectMixin.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/ProjectMixin.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.implab.gradle.common; - -import javax.inject.Inject; - -import org.gradle.api.Action; -import org.gradle.api.NamedDomainObjectProvider; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.file.Directory; -import org.gradle.api.tasks.TaskContainer; -import org.gradle.api.tasks.TaskProvider; -import org.implab.gradle.common.dsl.TaskGroup; -import org.implab.gradle.common.dsl.TasksMixin; -import org.implab.gradle.common.utils.ExtraProps; - -/** Project configuration traits */ -public interface ProjectMixin extends TasksMixin { - @Inject - Project getProject(); - - @Override - default TaskProvider task(String name, Class clazz) { - return getProject().getTasks().register(name, clazz); - } - - /** Creates a new task group */ - default TaskGroup taskGroup(String name) { - return new TaskGroup(name) { - @Override - protected TaskContainer tasks() { - return getProject().getTasks(); - } - }; - } - - /** Registers the new configuration */ - default NamedDomainObjectProvider configuration(String name, Action configure) { - return getProject().getConfigurations().register(name, configure); - } - - /** Returns the project directory */ - default Directory projectDirectory() { - return getProject().getLayout().getProjectDirectory(); - } - - /** Applies and returns the specified plugin, the plugin is applied only once. */ - default > T plugin(Class clazz) { - getProject().getPluginManager().apply(clazz); - return getProject().getPlugins().findPlugin(clazz); - } - - /** - * @param classes The list of classes to register as extensions - */ - default void exportClasses(Class... classes) { - var props = ExtraProps.of(getProject()); - for (var clazz : classes) - props.put(clazz.getSimpleName(), clazz); - } - - /** Creates and register a new project extension. - * - * @param The type of the extension - * @param extensionName The name of the extension in the project - * @param clazz The class of the extension - * @return the newly created extension - */ - default T extension(String extensionName, Class clazz) { - T extension = getProject().getObjects().newInstance(clazz); - getProject().getExtensions().add(extensionName, extension); - return extension; - } - -} - diff --git a/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java --- a/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java +++ b/common/src/main/java/org/implab/gradle/common/dsl/RedirectFromSpec.java @@ -20,26 +20,30 @@ public class RedirectFromSpec { } public void fromFile(File file) { - this.streamRedirect = () -> RedirectFrom.file(file); + streamRedirect = () -> RedirectFrom.file(file); } public void fromFile(Provider fileProvider) { - this.streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; + streamRedirect = fileProvider.map(RedirectFrom::file)::getOrNull; } public void fromStream(InputStream stream) { - this.streamRedirect = () -> RedirectFrom.stream(stream); + streamRedirect = () -> RedirectFrom.stream(stream); } public void fromStream(Provider streamProvider) { - this.streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; + streamRedirect = streamProvider.map(RedirectFrom::stream)::getOrNull; } public void from(Object input) { if (input instanceof Provider inputProvider) { - this.streamRedirect = inputProvider.map(RedirectFrom::any)::get; + streamRedirect = inputProvider.map(RedirectFrom::any)::get; } else { - this.streamRedirect = () -> RedirectFrom.any(input); + streamRedirect = () -> RedirectFrom.any(input); } } + + public void empty() { + streamRedirect = () -> null; + } } diff --git a/common/src/main/java/org/implab/gradle/common/dsl/RedirectToSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/RedirectToSpec.java --- a/common/src/main/java/org/implab/gradle/common/dsl/RedirectToSpec.java +++ b/common/src/main/java/org/implab/gradle/common/dsl/RedirectToSpec.java @@ -14,7 +14,7 @@ import org.implab.gradle.common.exec.Red @NonNullByDefault public class RedirectToSpec { private Supplier streamRedirect; - + public boolean isRedirected() { return getRedirection().isPresent(); } @@ -28,30 +28,38 @@ public class RedirectToSpec { } public void toFile(File file) { - this.streamRedirect = () -> RedirectTo.file(file); + streamRedirect = () -> RedirectTo.file(file); } public void toFile(Provider fileProvider) { - this.streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; + streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; } public void toStream(OutputStream stream) { - this.streamRedirect = () -> RedirectTo.stream(stream); + streamRedirect = () -> RedirectTo.stream(stream); } public void toStream(Provider streamProvider) { - this.streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; + streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; } public void to(Object output) { if (output instanceof Provider outputProvider) { - this.streamRedirect = outputProvider.map(RedirectTo::any)::get; + streamRedirect = outputProvider.map(RedirectTo::any)::get; } else { - this.streamRedirect = () -> RedirectTo.any(output); + streamRedirect = () -> RedirectTo.any(output); } } - public void consume(Consumer consumer) { - this.streamRedirect = () -> RedirectTo.consumer(consumer); + public void eachLine(Consumer consumer) { + streamRedirect = () -> RedirectTo.eachLine(consumer); + } + + public void allText(Consumer consumer) { + streamRedirect = () -> RedirectTo.allText(consumer); + } + + public void discard() { + streamRedirect = () -> null; } } diff --git a/common/src/main/java/org/implab/gradle/common/dsl/ShellSpecMixin.java b/common/src/main/java/org/implab/gradle/common/dsl/ShellSpecMixin.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/dsl/ShellSpecMixin.java @@ -0,0 +1,15 @@ +package org.implab.gradle.common.dsl; + +import org.implab.gradle.common.exec.Shell; + +public interface ShellSpecMixin { + void setShell(Shell shell); + + default void useEchoShell() { + setShell(Shell.echo()); + } + + default void useSystemShell() { + setShell(Shell.system()); + } +} diff --git a/common/src/main/java/org/implab/gradle/common/dsl/TaskCommandSpecMixin.java b/common/src/main/java/org/implab/gradle/common/dsl/TaskCommandSpecMixin.java --- a/common/src/main/java/org/implab/gradle/common/dsl/TaskCommandSpecMixin.java +++ b/common/src/main/java/org/implab/gradle/common/dsl/TaskCommandSpecMixin.java @@ -1,13 +1,26 @@ package org.implab.gradle.common.dsl; +import java.util.stream.Stream; import org.gradle.api.provider.ListProperty; +import org.implab.gradle.common.utils.Properties; public interface TaskCommandSpecMixin { ListProperty getCommandLine(); - void commandLine(Object arg0, Object... args); + default void commandLine(Object arg0, Object... args) { + getCommandLine().empty(); + Properties.mergeList( + getCommandLine(), + () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), + Object::toString); + } - void args(Object arg0, Object... args); + default void args(Object arg0, Object... args) { + Properties.mergeList( + getCommandLine(), + () -> Stream.concat(Stream.of(arg0), Stream.of(args)).iterator(), + Object::toString); + } } diff --git a/common/src/main/java/org/implab/gradle/common/dsl/TaskEnvSpecMixin.java b/common/src/main/java/org/implab/gradle/common/dsl/TaskEnvSpecMixin.java --- a/common/src/main/java/org/implab/gradle/common/dsl/TaskEnvSpecMixin.java +++ b/common/src/main/java/org/implab/gradle/common/dsl/TaskEnvSpecMixin.java @@ -1,15 +1,13 @@ package org.implab.gradle.common.dsl; -import java.util.HashMap; import java.util.Map; import org.gradle.api.Action; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; -import org.gradle.api.provider.Provider; import org.implab.gradle.common.utils.Closures; -import org.implab.gradle.common.utils.Values; +import org.implab.gradle.common.utils.Properties; import groovy.lang.Closure; @@ -38,7 +36,7 @@ public interface TaskEnvSpecMixin { * properties may be assigned to providers. */ default void env(Action> configure) { - Values.configureMap(getEnvironment(), configure, Object.class, Values::toString); + Properties.configureMap(getEnvironment(), configure, Object::toString); } default void env(Closure configure) { diff --git a/common/src/main/java/org/implab/gradle/common/dsl/TaskGroup.java b/common/src/main/java/org/implab/gradle/common/dsl/TaskGroup.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/dsl/TaskGroup.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.implab.gradle.common.dsl; - -import java.util.Objects; - -import org.gradle.api.Task; -import org.gradle.api.tasks.TaskContainer; -import org.gradle.api.tasks.TaskProvider; - -public abstract class TaskGroup implements TasksMixin { - private final String groupName; - - protected abstract TaskContainer tasks(); - - public TaskGroup(String name) { - Objects.requireNonNull(name, "The group name is required"); - this.groupName = name; - } - - @Override - public TaskProvider task(String taskName, Class clazz) { - var provider = tasks().register(taskName, clazz); - provider.configure(t -> t.setGroup(groupName)); - return provider; - } - -} diff --git a/common/src/main/java/org/implab/gradle/common/dsl/TasksMixin.java b/common/src/main/java/org/implab/gradle/common/dsl/TasksMixin.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/dsl/TasksMixin.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.implab.gradle.common.dsl; - -import org.gradle.api.Task; -import org.gradle.api.tasks.TaskProvider; - -public interface TasksMixin { - - default TaskProvider task(String name) { - return task(name, Task.class); - } - - TaskProvider task(String name, Class clazz); -} diff --git a/common/src/main/java/org/implab/gradle/common/exec/AbstractExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/AbstractExecBuilder.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/AbstractExecBuilder.java @@ -0,0 +1,212 @@ +package org.implab.gradle.common.exec; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import java.io.File; +import java.io.IOException; + +import static java.util.Objects.requireNonNull; + +/** Command line builder */ +@NonNullByDefault +public abstract class AbstractExecBuilder implements ShellExec { + + private boolean inheritEnvironment = true; + + private final Map environment = new HashMap<>(); + + private @Nullable File directory; + + private RedirectFrom inputRedirect; + + private RedirectTo outputRedirect; + + private RedirectTo errorRedirect; + + private final CS command; + + protected AbstractExecBuilder(CS command) { + this.command = command; + } + + + /** Sets the working directory */ + @Override + public ShellExec workingDirectory(File directory) { + requireNonNull(directory, "directory parameter can't be null"); + + this.directory = directory; + return this; + } + + @Override + public ShellExec workingDirectory(Optional directory) { + requireNonNull(directory, "directory parameter can't be null"); + + this.directory = directory.orElse(null); + return this; + } + + @Override + public ShellExec inheritEnvironment(boolean inherit) { + this.inheritEnvironment = inherit; + return this; + } + + /** + * Sets the environment value. The value cannot be null. + * + * @param envVar The name of the environment variable + * @param value The value to set, + */ + @Override + public ShellExec putEnvironment(String envVar, String value) { + requireNonNull(value, "Value can't be null"); + requireNonNull(envVar, "envVar parameter can't be null"); + + environment.put(envVar, value); + return this; + } + + @Override + public ShellExec environment(Map env) { + requireNonNull(env, "env parameter can't be null"); + + environment.clear(); + environment.putAll(env); + return this; + } + + /** + * Sets redirection for the stdin, {@link RedirectFrom} will be applied + * every time the process is started. + * + *

+ * If redirection + */ + @Override + public ShellExec stdin(RedirectFrom from) { + requireNonNull(from, "from parameter can't be null"); + + inputRedirect = from; + return this; + } + + @Override + public ShellExec stdin(Optional from) { + requireNonNull(from, "from parameter can't be null"); + inputRedirect = from.orElse(null); + return this; + } + + /** + * Sets redirection for the stdout, {@link RedirectTo} will be applied + * every time the process is started. + */ + @Override + public ShellExec stdout(RedirectTo out) { + requireNonNull(out, "out parameter can't be null"); + outputRedirect = out; + return this; + } + + @Override + public ShellExec stdout(Optional to) { + requireNonNull(to, "from parameter can't be null"); + outputRedirect = to.orElse(null); + return this; + } + + /** + * Sets redirection for the stderr, {@link RedirectTo} will be applied + * every time the process is started. + */ + @Override + public ShellExec stderr(RedirectTo err) { + requireNonNull(err, "err parameter can't be null"); + errorRedirect = err; + return this; + } + + @Override + public ShellExec stderr(Optional to) { + requireNonNull(to, "from parameter can't be null"); + errorRedirect = to.orElse(null); + return this; + } + + @Override + public ShellExec from(PipeSpec pipeSpec) { + ShellExec.super.from(pipeSpec); + return this; + } + + @Override + public ShellExec from(EnvironmentSpec environmentSpec) { + ShellExec.super.from(environmentSpec); + return this; + } + + /** Implement this function to */ + protected abstract CompletableFuture startInternal( + CS command, + EnvironmentSpec environment, + PipeSpec redirect) throws IOException; + + /** + * Creates and starts new process and returns {@link CompletableFuture}. The + * process may be a remote process, or a shell process or etc. + * + * @return + * @throws IOException + */ + public CompletableFuture exec() throws IOException { + return startInternal( + command, + new SelfEnvironmentSpec(), + new SelfPipeSpec()); + } + + private class SelfEnvironmentSpec implements EnvironmentSpec { + @Override + public boolean inheritEnvironment() { + return inheritEnvironment; + } + + @Override + public Map environment() { + return environment; + } + + @Override + public Optional workingDirectory() { + return Optional.ofNullable(directory); + } + } + + private class SelfPipeSpec implements PipeSpec { + + @Override + public Optional stdout() { + return Optional.ofNullable(outputRedirect); + } + + @Override + public Optional stderr() { + return Optional.ofNullable(errorRedirect); + } + + @Override + public Optional stdin() { + return Optional.ofNullable(inputRedirect); + } + + } + +} diff --git a/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java @@ -24,10 +24,10 @@ public interface CommandBuilder { /** * Sets the specified executable and parameters, old executable and parameters * are discarded. - * + * * @param command The command line. Must contain at least one element * (executable). - * + * */ default CommandBuilder commandLine(Iterable command) { var iterator = command.iterator(); @@ -80,4 +80,6 @@ public interface CommandBuilder { return executable(commandSpec.executable()) .arguments(commandSpec.arguments()); } + + CommandSpec build(); } diff --git a/common/src/main/java/org/implab/gradle/common/exec/CommandSpec.java b/common/src/main/java/org/implab/gradle/common/exec/CommandSpec.java --- a/common/src/main/java/org/implab/gradle/common/exec/CommandSpec.java +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandSpec.java @@ -13,4 +13,8 @@ public interface CommandSpec { return Stream.concat(Stream.of(executable()), arguments().stream()).toList(); } + public static CommandBuilder builder() { + return new CommandSpecRecord.Builder(); + } + } diff --git a/common/src/main/java/org/implab/gradle/common/exec/CommandSpecRecord.java b/common/src/main/java/org/implab/gradle/common/exec/CommandSpecRecord.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandSpecRecord.java @@ -0,0 +1,47 @@ +package org.implab.gradle.common.exec; + +import java.util.List; +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; + +public record CommandSpecRecord(String executable, List arguments) implements CommandSpec { + + static class Builder implements CommandBuilder { + + private String executable; + + private List arguments = new ArrayList<>(); + + @Override + public CommandBuilder executable(String executable) { + requireNonNull(executable, "cmd can't be null"); + this.executable = executable; + return this; + } + + @Override + public CommandBuilder arguments(Iterable args) { + requireNonNull(args, "Args must not be null"); + arguments.clear(); + for (var arg : args) + arguments.add(arg); + return this; + } + + @Override + public CommandBuilder addArguments(String... args) { + for (var arg : args) + arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); + + return this; + } + + @Override + public CommandSpec build() { + requireNonNull(executable, "Executable must be specified"); + return new CommandSpecRecord(executable, arguments); + } + + } +} diff --git a/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/EchoExecBuilder.java @@ -6,11 +6,12 @@ import java.nio.charset.StandardCharsets import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -class EchoExecBuilder extends ExecBuilder { +class EchoExecBuilder extends AbstractExecBuilder { private final boolean echoToStderr; - public EchoExecBuilder(boolean echoToStderr) { + public EchoExecBuilder(CommandSpec command, boolean echoToStderr) { + super(command); this.echoToStderr = echoToStderr; } diff --git a/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java +++ /dev/null @@ -1,264 +0,0 @@ -package org.implab.gradle.common.exec; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import java.io.File; -import java.io.IOException; - -import static java.util.Objects.requireNonNull; - -/** Command line builder */ -@NonNullByDefault -public abstract class ExecBuilder implements - CommandBuilder, - PipeBuilder, - EnvironmentBuilder, - Executable { - private String executable; - - private final List arguments = new ArrayList<>(); - - private boolean inheritEnvironment = true; - - private final Map environment = new HashMap<>(); - - private @Nullable File directory; - - private RedirectFrom inputRedirect; - - private RedirectTo outputRedirect; - - private RedirectTo errorRedirect; - - @Override - public ExecBuilder executable(String executable) { - requireNonNull(executable, "cmd can't be null"); - this.executable = executable; - return this; - } - - @Override - public ExecBuilder arguments(Iterable args) { - requireNonNull(args, "Args must not be null"); - arguments.clear(); - for (var arg : args) - arguments.add(arg); - return this; - } - - @Override - public ExecBuilder addArguments(String... args) { - for (var arg : args) - arguments.add(requireNonNull(arg, "arguments element shouldn't be null")); - - return this; - } - - /** Sets the working directory */ - @Override - public ExecBuilder workingDirectory(File directory) { - requireNonNull(directory, "directory parameter can't be null"); - - this.directory = directory; - return this; - } - - @Override - public ExecBuilder workingDirectory(Optional directory) { - requireNonNull(directory, "directory parameter can't be null"); - - this.directory = directory.orElse(null); - return this; - } - - @Override - public ExecBuilder inheritEnvironment(boolean inherit) { - this.inheritEnvironment = inherit; - return this; - } - - /** - * Sets the environment value. The value cannot be null. - * - * @param envVar The name of the environment variable - * @param value The value to set, - */ - @Override - public ExecBuilder putEnvironment(String envVar, String value) { - requireNonNull(value, "Value can't be null"); - requireNonNull(envVar, "envVar parameter can't be null"); - - environment.put(envVar, value); - return this; - } - - @Override - public ExecBuilder environment(Map env) { - requireNonNull(env, "env parameter can't be null"); - - environment.clear(); - environment.putAll(env); - return this; - } - - /** - * Sets redirection for the stdin, {@link RedirectFrom} will be applied - * every time the process is started. - * - *

- * If redirection - */ - @Override - public ExecBuilder stdin(RedirectFrom from) { - requireNonNull(from, "from parameter can't be null"); - - inputRedirect = from; - return this; - } - - @Override - public ExecBuilder stdin(Optional from) { - requireNonNull(from, "from parameter can't be null"); - inputRedirect = from.orElse(null); - return this; - } - - /** - * Sets redirection for the stdout, {@link RedirectTo} will be applied - * every time the process is started. - */ - @Override - public ExecBuilder stdout(RedirectTo out) { - requireNonNull(out, "out parameter can't be null"); - outputRedirect = out; - return this; - } - - @Override - public ExecBuilder stdout(Optional to) { - requireNonNull(to, "from parameter can't be null"); - outputRedirect = to.orElse(null); - return this; - } - - /** - * Sets redirection for the stderr, {@link RedirectTo} will be applied - * every time the process is started. - */ - @Override - public ExecBuilder stderr(RedirectTo err) { - requireNonNull(err, "err parameter can't be null"); - errorRedirect = err; - return this; - } - - @Override - public ExecBuilder stderr(Optional to) { - requireNonNull(to, "from parameter can't be null"); - errorRedirect = to.orElse(null); - return this; - } - - @Override - public ExecBuilder from(CommandSpec commandSpec) { - CommandBuilder.super.from(commandSpec); - return this; - } - - @Override - public ExecBuilder from(PipeSpec pipeSpec) { - PipeBuilder.super.from(pipeSpec); - return this; - } - - @Override - public ExecBuilder from(EnvironmentSpec environmentSpec) { - EnvironmentBuilder.super.from(environmentSpec); - return this; - } - - /** Implement this function to */ - protected abstract CompletableFuture startInternal( - CommandSpec command, - EnvironmentSpec environment, - PipeSpec redirect) throws IOException; - - /** - * Creates and starts new process and returns {@link CompletableFuture}. The - * process may be a remote process, or a shell process or etc. - * - * @return - * @throws IOException - */ - public CompletableFuture exec() throws IOException { - if (executable == null || executable.isEmpty()) - throw new IllegalStateException("The executable isn't set"); - - var commandLine = new ArrayList(); - commandLine.add(executable); - commandLine.addAll(arguments); - - return startInternal( - new SelfCommandSpec(), - new SelfEnvironmentSpec(), - new SelfPipeSpec()); - } - - private class SelfCommandSpec implements CommandSpec { - - @Override - public String executable() { - return executable; - } - - @Override - public List arguments() { - return arguments; - } - } - - private class SelfEnvironmentSpec implements EnvironmentSpec { - @Override - public boolean inheritEnvironment() { - return inheritEnvironment; - } - - @Override - public Map environment() { - return environment; - } - - @Override - public Optional workingDirectory() { - return Optional.ofNullable(directory); - } - } - - private class SelfPipeSpec implements PipeSpec { - - @Override - public Optional stdout() { - return Optional.ofNullable(outputRedirect); - } - - @Override - public Optional stderr() { - return Optional.ofNullable(errorRedirect); - } - - @Override - public Optional stdin() { - return Optional.ofNullable(inputRedirect); - } - - } - -} diff --git a/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java b/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.implab.gradle.common.exec; - -import java.io.IOException; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; - -/** Shell used to start processes */ -public interface ExecShell { - - /** Creates new process builder {@link ExecBuilder} */ - ExecBuilder executable(); - - /** - * Creates default process execution "shell" which will execute processes - * directly. - */ - static ExecShell system() { - return () -> new SystemExecBuilder(); - } - - /** - * Creates a stub shell which will print command to STDERR and return 0 (no - * error). - */ - static ExecShell echoStderr() { - return () -> new EchoExecBuilder(true); - } - - /** - * Creates a stub shell which will print command to STDOUT and return 0 (no - * error). - */ - - static ExecShell echo() { - return () -> new EchoExecBuilder(false); - } - - /** - * Creates a stub shell which will call the specified consumer and will supply a - * command line to it. - * The command line will be passed as a list of strings. - * - * @param consumer The consumer fot the command line - * @return - */ - static ExecShell echo(Consumer> consumer) { - return () -> new ExecBuilder() { - @Override - protected CompletableFuture startInternal( - CommandSpec command, - EnvironmentSpec environment, - PipeSpec redirect) throws IOException { - consumer.accept(command.commandLine()); - return CompletableFuture.completedFuture(0); - } - }; - } -} diff --git a/common/src/main/java/org/implab/gradle/common/exec/Executable.java b/common/src/main/java/org/implab/gradle/common/exec/Executable.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/exec/Executable.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.implab.gradle.common.exec; - -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -public interface Executable { - CompletableFuture exec() throws IOException; - - /** Starts process and will throw error if error code is non-zero */ - default CompletableFuture execChecked() throws IOException { - return exec().thenAccept(code -> { - if (code != 0) - throw new CompletionException(new IOException( - String.format("The process is terminated with code %d", code))); - }); - } -} diff --git a/common/src/main/java/org/implab/gradle/common/exec/PipeSpec.java b/common/src/main/java/org/implab/gradle/common/exec/PipeSpec.java --- a/common/src/main/java/org/implab/gradle/common/exec/PipeSpec.java +++ b/common/src/main/java/org/implab/gradle/common/exec/PipeSpec.java @@ -4,6 +4,12 @@ import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; +/** + * The execution shell uses this specification when starting + * a new process. The shell may check for the specified + * redirections and apply them when launching the process. + * The exact moment they are applied is at the shell’s discretion. + */ @NonNullByDefault public interface PipeSpec { Optional stdout(); diff --git a/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java b/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java --- a/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java +++ b/common/src/main/java/org/implab/gradle/common/exec/RedirectTo.java @@ -7,6 +7,8 @@ import java.io.OutputStream; import java.util.Scanner; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -21,10 +23,12 @@ public interface RedirectTo { CompletableFuture redirect(InputStream from); public interface StringConsumer extends Consumer { + default void complete() { + } } /** Creates a redirect to the specified consumer */ - public static RedirectTo consumer(final Consumer consumer) { + public static RedirectTo eachLine(final Consumer consumer) { return consumer(new StringConsumer() { @Override public void accept(String s) { @@ -33,6 +37,22 @@ public interface RedirectTo { }); } + public static RedirectTo allText(final Consumer consumer) { + return consumer(new StringConsumer() { + final Stream.Builder builder = Stream.builder(); + + @Override + public void accept(String s) { + builder.accept(s); + } + + @Override + public void complete() { + consumer.accept(builder.build().collect(Collectors.joining("\n"))); + } + }); + } + /** Creates a redirect to the specified consumer */ public static RedirectTo consumer(final StringConsumer consumer) { return (src) -> CompletableFuture.runAsync(() -> { @@ -40,6 +60,7 @@ public interface RedirectTo { while (sc.hasNextLine()) { consumer.accept(sc.nextLine()); } + consumer.complete(); } }); } @@ -51,7 +72,7 @@ public interface RedirectTo { */ public static RedirectTo file(final File file) { return src -> CompletableFuture.runAsync(() -> { - try (OutputStream out = new FileOutputStream(file)) { + try (src; OutputStream out = new FileOutputStream(file)) { src.transferTo(out); } catch (Exception e) { // silence! diff --git a/common/src/main/java/org/implab/gradle/common/exec/Shell.java b/common/src/main/java/org/implab/gradle/common/exec/Shell.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/Shell.java @@ -0,0 +1,22 @@ +package org.implab.gradle.common.exec; + +public interface Shell { + + ShellExec of(CommandSpec spec); + + /** Creates a new shell to start processes using ProcessBuilder. */ + static Shell system() { + return spec -> new SystemExecBuilder(spec); + } + + /** Creates a stub shell which echoes the specified command line. */ + static Shell echo() { + return spec -> new EchoExecBuilder(spec, false); + } + + /** Creates a stub shell which echoes the specified command line. */ + static Shell echoStderr() { + return spec -> new EchoExecBuilder(spec, false); + } + +} diff --git a/common/src/main/java/org/implab/gradle/common/exec/ShellExec.java b/common/src/main/java/org/implab/gradle/common/exec/ShellExec.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/ShellExec.java @@ -0,0 +1,36 @@ +package org.implab.gradle.common.exec; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.implab.gradle.common.utils.Exceptions; + +public interface ShellExec extends PipeBuilder, EnvironmentBuilder { + + default CompletableFuture> readAllLines() throws IOException { + List lines = new ArrayList<>(); + stdout(RedirectTo.consumer(lines::add)); + + return exec() + .thenAccept(Exceptions.unchecked(this::assertExitCode)) + .thenApply(v -> lines); + } + + default CompletableFuture readAllText() throws IOException { + List lines = new ArrayList<>(); + stdout(RedirectTo.consumer(lines::add)); + + return exec() + .thenAccept(Exceptions.unchecked(this::assertExitCode)) + .thenApply(v -> String.join("\n", lines)); + } + + default void assertExitCode(Integer code) throws IOException { + if (code != 0) + throw new IOException(String.format("The process is terminated with code %d", code)); + } + + CompletableFuture exec() throws IOException; +} diff --git a/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java --- a/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/SystemExecBuilder.java @@ -5,7 +5,11 @@ import java.lang.ProcessBuilder.Redirect import java.util.ArrayList; import java.util.concurrent.CompletableFuture; -class SystemExecBuilder extends ExecBuilder { +class SystemExecBuilder extends AbstractExecBuilder { + SystemExecBuilder(CommandSpec command) { + super(command); + } + @Override protected CompletableFuture startInternal( CommandSpec command, diff --git a/common/src/main/java/org/implab/gradle/common/tasks/AbstractShellExecTask.java b/common/src/main/java/org/implab/gradle/common/tasks/AbstractShellExecTask.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/tasks/AbstractShellExecTask.java @@ -0,0 +1,108 @@ +package org.implab.gradle.common.tasks; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.TaskAction; +import org.implab.gradle.common.dsl.RedirectFromSpec; +import org.implab.gradle.common.dsl.RedirectToSpec; +import org.implab.gradle.common.dsl.TaskEnvSpecMixin; +import org.implab.gradle.common.dsl.TaskPipeSpecMixin; +import org.implab.gradle.common.exec.RedirectTo; +import org.implab.gradle.common.exec.ShellExec; +import org.implab.gradle.common.utils.Exceptions; +import org.implab.gradle.common.utils.Values; + +public abstract class AbstractShellExecTask + extends DefaultTask + implements TaskPipeSpecMixin, TaskEnvSpecMixin { + + private final RedirectToSpec redirectStderr = new RedirectToSpec(); + + private final RedirectToSpec redirectStdout = new RedirectToSpec(); + + private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); + + @Internal + @Override + public abstract Property getInheritEnvironment(); + + @Internal + @Override + public abstract DirectoryProperty getWorkingDirectory(); + + @Internal + @Override + public abstract MapProperty getEnvironment(); + + /** + * STDIN redirection, if not specified, no input will be passed to the command + */ + @Internal + @Override + public RedirectFromSpec getStdin() { + return redirectStdin; + } + + /** + * STDOUT redirection, if not specified, redirected to logger::info + */ + @Internal + @Override + public RedirectToSpec getStdout() { + return redirectStdout; + } + + /** + * STDERR redirection, if not specified, redirected to logger::error + */ + @Internal + @Override + public RedirectToSpec getStderr() { + return redirectStderr; + } + + protected abstract ShellExec execBuilder(); + + protected Optional conventionalStderr() { + return Optional.of(RedirectTo.eachLine(getLogger()::error)); + } + + protected Optional conventionalStdout() { + return Optional.of(RedirectTo.eachLine(getLogger()::info)); + } + + @TaskAction + public final void run() throws IOException, InterruptedException, ExecutionException { + // create new shell process + var execBuilder = execBuilder(); + + // configure environment + execBuilder + .workingDirectory(Values.optional(getWorkingDirectory().getAsFile())) + .environment(getEnvironment().getOrElse(Map.of())); + + // configure redirects + execBuilder + .stdout(getStdout().getRedirection().or(this::conventionalStdout)) + .stderr(getStderr().getRedirection().or(this::conventionalStderr)) + .stdin(getStdin().getRedirection()); + + // execute + execBuilder.exec() + .thenAccept(Exceptions.unchecked(this::assertExitCode)) + .join(); + } + + protected void assertExitCode(Integer code) throws IOException { + if (code != 0) + throw new IOException(String.format("The process is terminated with code %s", code)); + } +} \ No newline at end of file diff --git a/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java b/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java --- a/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java +++ b/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java @@ -1,115 +1,31 @@ package org.implab.gradle.common.tasks; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.stream.Stream; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.DirectoryProperty; import org.gradle.api.provider.ListProperty; -import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.TaskAction; import org.implab.gradle.common.dsl.TaskCommandSpecMixin; -import org.implab.gradle.common.dsl.TaskPipeSpecMixin; -import org.implab.gradle.common.dsl.RedirectFromSpec; -import org.implab.gradle.common.dsl.RedirectToSpec; -import org.implab.gradle.common.dsl.TaskEnvSpecMixin; -import org.implab.gradle.common.exec.ExecBuilder; -import org.implab.gradle.common.utils.ObjectsMixin; -import org.implab.gradle.common.utils.Strings; -import org.implab.gradle.common.utils.ThrowingConsumer; +import org.implab.gradle.common.exec.CommandSpec; +import org.implab.gradle.common.exec.Shell; +import org.implab.gradle.common.exec.ShellExec; public abstract class ShellExecTask - extends DefaultTask - implements TaskCommandSpecMixin, TaskPipeSpecMixin, TaskEnvSpecMixin, ObjectsMixin { - - private final RedirectToSpec redirectStderr = new RedirectToSpec(); - - private final RedirectToSpec redirectStdout = new RedirectToSpec(); - - private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); + extends AbstractShellExecTask + implements TaskCommandSpecMixin { @Internal - @Override - public abstract Property getInheritEnvironment(); - - @Internal - @Override - public abstract DirectoryProperty getWorkingDirectory(); - - @Internal - @Override - public abstract MapProperty getEnvironment(); + public abstract Property getShell(); @Internal @Override public abstract ListProperty getCommandLine(); - /** - * STDIN redirection, if not specified, no input will be passed to the command - */ - @Internal - @Override - public RedirectFromSpec getStdin() { - return redirectStdin; - } - - /** - * STDOUT redirection, if not specified, redirected to logger::info - */ - @Internal - @Override - public RedirectToSpec getStdout() { - return redirectStdout; - } - - /** - * STDERR redirection, if not specified, redirected to logger::error - */ - @Internal - @Override - public RedirectToSpec getStderr() { - return redirectStderr; - } @Override - public void commandLine(Object arg0, Object... args) { - getCommandLine().set(provider(() -> Stream.concat( - Stream.of(arg0), - Stream.of(args)) - .map(Strings::asString).toList())); - - } - - @Override - public void args(Object arg0, Object... args) { - getCommandLine().addAll(provider(() -> Stream.concat( - Stream.of(arg0), - Stream.of(args)) - .map(Strings::asString).toList())); - } - - protected abstract ExecBuilder execBuilder(); - - @TaskAction - public final void run() throws IOException, InterruptedException, ExecutionException { - var execBuilder = execBuilder(); - execBuilder.workingDirectory(getWorkingDirectory().get().getAsFile()); - execBuilder.environment(getEnvironment().getOrElse(Map.of())); - execBuilder.commandLine(getCommandLine().get()); - - getStdout().getRedirection().ifPresent(execBuilder::stdout); - getStderr().getRedirection().ifPresent(execBuilder::stderr); - getStdin().getRedirection().ifPresent(execBuilder::stdin); - - execBuilder.exec().thenAccept(ThrowingConsumer.guard(this::checkRetCode)).join(); - } - - protected void checkRetCode(Integer code) throws IOException { - throw new IOException(String.format("The process is terminated with code %s", code)); + protected ShellExec execBuilder() { + return getShell().get() + .of(CommandSpec.builder() + .commandLine(getCommandLine().get()) + .build()); } } diff --git a/common/src/main/java/org/implab/gradle/common/utils/Closures.java b/common/src/main/java/org/implab/gradle/common/utils/Closures.java --- a/common/src/main/java/org/implab/gradle/common/utils/Closures.java +++ b/common/src/main/java/org/implab/gradle/common/utils/Closures.java @@ -23,12 +23,10 @@ public final class Closures { } public static void apply(Closure action, Object target) { - var prevDelegate = action.getDelegate(); - try { - action.setDelegate(target); - action.call(target); - } finally { - action.setDelegate(prevDelegate); - } + var c = (Closure)action.clone(); + c.setResolveStrategy(0); + c.setDelegate(target); + c.call(target); + } } diff --git a/common/src/main/java/org/implab/gradle/common/utils/Exceptions.java b/common/src/main/java/org/implab/gradle/common/utils/Exceptions.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/Exceptions.java @@ -0,0 +1,51 @@ +package org.implab.gradle.common.utils; + +import java.util.function.Consumer; +import java.util.function.Function; + +public class Exceptions { + /** + * Helper function which declares that this block can throw the specified + * exception. + * + * @param + * @param clazz + * @throws E + */ + @SuppressWarnings("unused") + public static void mayThrow(Class clazz) throws E { + } + + @SuppressWarnings("unchecked") + public static E sneakyThrow(Throwable t) throws E { + throw (E) t; + } + + public static Function unchecked(ThrowingFunction fn) { + return val -> { + try { + return fn.apply(val); + } catch (Exception e) { + throw sneakyThrow(e); + } + }; + } + + public static Consumer unchecked(ThrowingConsumer c) { + return val -> { + try { + c.accept(val); + } catch (Exception e) { + throw sneakyThrow(e); + } + }; + } + + public interface ThrowingConsumer { + void accept(T value) throws Exception; + } + + public interface ThrowingFunction { + U apply(T value) throws Exception; + } +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/ExtraProps.java b/common/src/main/java/org/implab/gradle/common/utils/ExtraProps.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/utils/ExtraProps.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.implab.gradle.common.utils; - -import java.util.Optional; -import java.util.Set; - -import org.gradle.api.plugins.ExtensionAware; - -public interface ExtraProps { - - Set keys(); - - boolean has(String name); - - Optional get(String name, Class clazz); - - void put(String name, Object value); - - void remove(String name); - - public static ExtraProps of(Object target) { - return target instanceof ExtensionAware ext - ? new ExtraPropsExtension(ext.getExtensions().getExtraProperties()) - : new ExtraPropsNone(); - } -} diff --git a/common/src/main/java/org/implab/gradle/common/utils/ExtraPropsExtension.java b/common/src/main/java/org/implab/gradle/common/utils/ExtraPropsExtension.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/utils/ExtraPropsExtension.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.implab.gradle.common.utils; - -import java.util.Optional; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.gradle.api.plugins.ExtraPropertiesExtension; - -@NonNullByDefault -class ExtraPropsExtension implements ExtraProps { - - private final ExtraPropertiesExtension ext; - - public ExtraPropsExtension(ExtraPropertiesExtension ext) { - this.ext = ext; - } - - @Override - public Set keys() { - return ext.getProperties().keySet(); - } - - @Override - public boolean has(String name) { - return ext.has(name); - } - - @Override - public Optional get(String name, Class clazz) { - return Optional.ofNullable(ext.get(name)).map(clazz::cast); - } - - @Override - public void put(String name, Object value) { - ext.set(name, value); - } - - @Override - public void remove(String name) { - ext.set(name, null); - } - -} diff --git a/common/src/main/java/org/implab/gradle/common/utils/ExtraPropsNone.java b/common/src/main/java/org/implab/gradle/common/utils/ExtraPropsNone.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/utils/ExtraPropsNone.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.implab.gradle.common.utils; - -import java.util.Optional; -import java.util.Set; - -public class ExtraPropsNone implements ExtraProps { - - @Override - public Set keys() { - return Set.of(); - } - - @Override - public boolean has(String name) { - return false; - } - - @Override - public Optional get(String name, Class clazz) { - return Optional.empty(); - } - - @Override - public void put(String name, Object value) { - } - - @Override - public void remove(String name) { - } - -} diff --git a/common/src/main/java/org/implab/gradle/common/utils/JsonDelegate.java b/common/src/main/java/org/implab/gradle/common/utils/JsonDelegate.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/JsonDelegate.java @@ -0,0 +1,84 @@ +package org.implab.gradle.common.utils; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import groovy.lang.Closure; +import groovy.lang.GroovyObjectSupport; + +public class JsonDelegate extends GroovyObjectSupport { + private final Map content; + + private JsonDelegate() { + content = new LinkedHashMap<>(); + } + + private JsonDelegate(Map content) { + this.content = new LinkedHashMap<>(content); + } + + public Object invokeMethod(String name, Object args) { + Object val = null; + if (args != null && Object[].class.isAssignableFrom(args.getClass())) { + Object[] arr = (Object[]) args; + if (arr.length == 1) { + val = processValue(arr[0], content.get(name)); + } else if (arr.length == 2 && arr[1] instanceof Closure c) { + if (arr[0] instanceof Iterable iter) { + val = processStream(Values.stream(iter.iterator()), c); + } else if(arr[0] != null && arr[0].getClass().isArray()) { + val = processStream(Stream.of((Object)arr[0]), c); + } else { + val = arr; + } + } else { + val = arr; + } + } + + this.content.put(name, val); + return val; + } + + private Object processValue(Object value, Object original) { + if (value instanceof Closure c) { + return Objects.nonNull(original) ? of(c, original) : of(c); + } else { + return value; + } + } + + private Object processStream(Stream stream, Closure c) { + return stream.map(transform(c)).toArray(); + } + + public static Map of(Closure c) { + return invoke((Closure) c.clone(), new JsonDelegate()); + } + + public static Function> transform(Closure c) { + return o -> of(c, o); + } + + public static Map of(Closure c, Object o) { + return invoke((Closure) c.curry(o), new JsonDelegate()); + } + + public static Map with(Closure c, Map m) { + return invoke((Closure) c.clone(), new JsonDelegate(m)); + } + + private static Map invoke(Closure c, JsonDelegate d) { + c.setDelegate(d); + c.setResolveStrategy(1); + c.call(); + return d.getContent(); + } + + public Map getContent() { + return this.content; + } +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java b/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/utils/ObjectsMixin.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.implab.gradle.common.utils; - -import java.util.function.Supplier; - -import javax.inject.Inject; - -import org.gradle.api.NamedDomainObjectContainer; -import org.gradle.api.NamedDomainObjectFactory; -import org.gradle.api.model.ObjectFactory; -import org.gradle.api.provider.Provider; -import org.gradle.api.provider.ProviderFactory; - -import groovy.lang.Closure; - -public interface ObjectsMixin { - @Inject - ObjectFactory getObjects(); - - @Inject - ProviderFactory getProviders(); - - default Provider provider(Supplier supplier) { - return getProviders().provider(supplier::get); - } - - default Provider provider(Closure closure) { - return getProviders().provider(closure); - } - - default T newInstance(Class clazz) { - return getObjects().newInstance(clazz); - } - - default T newInstance(Class clazz, Object... params) { - return getObjects().newInstance(clazz, params); - } - - default NamedDomainObjectContainer newContainer(Class clazz) { - return getObjects().domainObjectContainer(clazz); - } - - default NamedDomainObjectContainer newContainer(Class clazz, NamedDomainObjectFactory factory) { - return getObjects().domainObjectContainer(clazz, factory); - } -} diff --git a/common/src/main/java/org/implab/gradle/common/utils/Properties.java b/common/src/main/java/org/implab/gradle/common/utils/Properties.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/Properties.java @@ -0,0 +1,63 @@ +package org.implab.gradle.common.utils; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.gradle.api.Action; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Provider; + +public final class Properties { + private Properties() { + } + + public static void mergeMap(MapProperty property, Map map) { + map.forEach((k, v) -> { + if (v instanceof Provider) + property.put(k, (Provider) v); + else + property.put(k, v); + }); + } + + public static void mergeMap(MapProperty property, Map map, Function mapper) { + map.forEach((k, v) -> { + if (v instanceof Provider) + property.put(k, ((Provider) v).map(mapper::apply)); + else + property.put(k, mapper.apply(v)); + }); + } + + public static void mergeList(ListProperty property, Iterable values) { + values.forEach(v -> { + if (v instanceof Provider) + property.add((Provider) v); + else + property.add(v); + }); + } + + public static void mergeList(ListProperty property, Iterable values, Function mapper) { + values.forEach(v -> { + if (v instanceof Provider) + property.add(((Provider) v).map(mapper::apply)); + else + property.add(mapper.apply(v)); + }); + } + + public static void configureMap(MapProperty prop, Action> configure) { + var map = new HashMap(); + configure.execute(map); + mergeMap(prop, map); + } + + public static void configureMap(MapProperty prop, Action> configure, Function mapper) { + var map = new HashMap(); + configure.execute(map); + mergeMap(prop, map, mapper); + } +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/Strings.java b/common/src/main/java/org/implab/gradle/common/utils/Strings.java --- a/common/src/main/java/org/implab/gradle/common/utils/Strings.java +++ b/common/src/main/java/org/implab/gradle/common/utils/Strings.java @@ -16,6 +16,28 @@ public class Strings { : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase()); } + public static String toCamelCase(String name) { + if (name == null || name.isEmpty()) + return name; + StringBuilder out = new StringBuilder(name.length()); + boolean up = false; + boolean first = true; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + switch (c) { + case '-', '_', ' ', '.' -> up = true; + default -> { + out.append( + first ? Character.toLowerCase(c) + : up ? Character.toUpperCase(c): c); + up = false; + first = false; + } + } + } + return out.toString(); + } + public static void argumentNotNullOrEmpty(String value, String argumentName) { if (value == null || value.length() == 0) throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName)); diff --git a/common/src/main/java/org/implab/gradle/common/utils/ThrowingConsumer.java b/common/src/main/java/org/implab/gradle/common/utils/ThrowingConsumer.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/utils/ThrowingConsumer.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.implab.gradle.common.utils; - -import java.util.concurrent.CompletionException; -import java.util.function.Consumer; - -public interface ThrowingConsumer { - void accept(T value) throws E; - - static Consumer guard(ThrowingConsumer target) { - return arg -> { - try { - target.accept(arg); - } catch (Throwable err) { - throw new CompletionException(err); - } - }; - } -} diff --git a/common/src/main/java/org/implab/gradle/common/utils/Values.java b/common/src/main/java/org/implab/gradle/common/utils/Values.java --- a/common/src/main/java/org/implab/gradle/common/utils/Values.java +++ b/common/src/main/java/org/implab/gradle/common/utils/Values.java @@ -1,19 +1,12 @@ package org.implab.gradle.common.utils; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Spliterators; -import java.util.Map.Entry; import java.util.Optional; -import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.gradle.api.Action; -import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Provider; public final class Values { @@ -22,50 +15,6 @@ public final class Values { } /** - * Converts values in the specified map - * - * @param - * @param - * @param - * @param map - * @param mapper - * @return - */ - public static Map mapValues(Map map, Function mapper) { - Function, V> getter = Entry::getValue; - - return map.entrySet().stream() - .collect(Collectors.toMap(Entry::getKey, getter.andThen(mapper))); - } - - public static void mergeMap(MapProperty prop, Map map, Class clazz) { - map.forEach((k,v) -> { - if(v instanceof Provider) - prop.put(k, ((Provider)v).map(clazz::cast)); - else - prop.put(k, clazz.cast(v)); - }); - } - - public static void mergeMap(MapProperty prop, Map map, Class clazz, Function mapper) { - Function caster = clazz::cast; - var castMap = caster.andThen(mapper); - - map.forEach((k,v) -> { - if(v instanceof Provider) - prop.put(k, ((Provider)v).map(x -> castMap.apply(x))); - else - prop.put(k, castMap.apply(v)); - }); - } - - public static void configureMap(MapProperty prop, Action> configure, Class clazz, Function mapper) { - var map = new HashMap(); - configure.execute(map); - mergeMap(prop, map, clazz, mapper); - } - - /** * Converts the supplied value to a string. */ public static String toString(Object value) {