# HG changeset patch # User cin # Date 2025-11-07 15:29:10 # Node ID 8e171ef3106fd90a4890a3548af6e99dd4aea01a # Parent 44d7291c1c1baaa4d7f27692e3d53108f8851439 Added OS tools to resolve exec path, minor changes diff --git a/common/src/main/java/org/implab/gradle/common/exec/CommandArgumentsBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/CommandArgumentsBuilder.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandArgumentsBuilder.java @@ -0,0 +1,52 @@ +package org.implab.gradle.common.exec; + +import static java.util.Objects.requireNonNull; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.common.utils.Values; + +@NonNullByDefault +public interface CommandArgumentsBuilder> { + + S self(); + + default S flag(String name, boolean value) { + requireNonNull(name, "Parameter name cannot be null"); + if (value) + addArguments(name); + return self(); + } + + default S param(String name, String value) { + requireNonNull(name, "Parameter name cannot be null"); + requireNonNull(value, "Parameter value cannot be null"); + if (value != null && value.length() > 0) + addArguments(name, value); + return self(); + } + + default S param(String name, Optional value) { + value.ifPresent(v -> param(name, v)); + return self(); + } + + /** Adds the specified arguments to this builder */ + S addArguments(String... args); + + default S addArguments(Iterable args) { + for (String arg : args) + addArguments(arg); + + return self(); + } + + /** Replaces arguments in the builder with the specified one. */ + default S arguments(String... args) { + return arguments(Values.iterable(args)); + } + + /** Replaces arguments in the builder with the specified one. */ + S arguments(Iterable args); +} 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 @@ -1,17 +1,27 @@ package org.implab.gradle.common.exec; -import java.util.function.Consumer; +import java.io.File; import org.eclipse.jdt.annotation.NonNullByDefault; import org.implab.gradle.common.utils.Values; /** Command builder interface, used to specify the executable and parameters */ @NonNullByDefault -public interface CommandBuilder { +public interface CommandBuilder extends CommandArgumentsBuilder { + + @Override + default CommandBuilder self() { + return this; + } /** Sets the executable, the parameters are left intact. */ CommandBuilder executable(String executable); + default CommandBuilder executable(File executable) { + executable(executable.toString()); + return this; + } + /** * Sets the specified executable and parameters, old executable and parameters * are discarded. @@ -44,38 +54,6 @@ public interface CommandBuilder { return this; } - default Consumer flag(String name) { - return f -> { - if (f) - addArguments(name); - }; - } - - default Consumer param(String name) { - return v -> { - if (v != null && v.length() > 0) - addArguments(name, v); - }; - } - - /** Adds the specified arguments to this builder */ - CommandBuilder addArguments(String... args); - - default CommandBuilder addArguments(Iterable args) { - for (String arg : args) - addArguments(arg); - - return this; - } - - /** Replaces arguments in the builder with the specified one. */ - default CommandBuilder arguments(String... args) { - return arguments(Values.iterable(args)); - } - - /** Replaces arguments in the builder with the specified one. */ - CommandBuilder arguments(Iterable args); - default CommandBuilder from(CommandSpec commandSpec) { return executable(commandSpec.executable()) .arguments(commandSpec.arguments()); 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 @@ -3,6 +3,9 @@ package org.implab.gradle.common.exec; import java.util.List; import java.util.stream.Stream; +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault public interface CommandSpec { String executable(); 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 --- a/common/src/main/java/org/implab/gradle/common/exec/CommandSpecRecord.java +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandSpecRecord.java @@ -1,10 +1,14 @@ package org.implab.gradle.common.exec; import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + import static java.util.Objects.requireNonNull; import java.util.ArrayList; +@NonNullByDefault public record CommandSpecRecord(String executable, List arguments) implements CommandSpec { static class Builder implements CommandBuilder { 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,6 +6,9 @@ import java.nio.charset.StandardCharsets import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault class EchoExecBuilder extends AbstractExecBuilder { private final boolean echoToStderr; diff --git a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java --- a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java +++ b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java @@ -4,6 +4,9 @@ import java.io.File; import java.util.Map; import java.util.Optional; +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault public interface EnvironmentSpec { boolean inheritEnvironment(); diff --git a/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java b/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java --- a/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java +++ b/common/src/main/java/org/implab/gradle/common/exec/RedirectFrom.java @@ -6,12 +6,15 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.CompletableFuture; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Describes how to redirect input streams. This interface is used to configure * lazy redirection. {@link #redirect(OutputStream)} is called when the process * is started. Before the process is started the redirection isn't invoked and * no resources are allocated or used. */ +@NonNullByDefault public interface RedirectFrom { CompletableFuture redirect(OutputStream to); 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 --- a/common/src/main/java/org/implab/gradle/common/exec/Shell.java +++ b/common/src/main/java/org/implab/gradle/common/exec/Shell.java @@ -1,8 +1,15 @@ package org.implab.gradle.common.exec; +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault public interface Shell { - ShellExec of(CommandSpec spec); + ShellExec create(CommandSpec spec); + + default ShellTool tool(String executable) { + return new ShellTool(executable, this); + } /** Creates a new shell to start processes using ProcessBuilder. */ static Shell system() { 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 --- a/common/src/main/java/org/implab/gradle/common/exec/ShellExec.java +++ b/common/src/main/java/org/implab/gradle/common/exec/ShellExec.java @@ -5,8 +5,10 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.implab.gradle.common.utils.Exceptions; +@NonNullByDefault public interface ShellExec extends PipeBuilder, EnvironmentBuilder { default CompletableFuture> readAllLines() throws IOException { diff --git a/common/src/main/java/org/implab/gradle/common/exec/ShellTool.java b/common/src/main/java/org/implab/gradle/common/exec/ShellTool.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/ShellTool.java @@ -0,0 +1,40 @@ +package org.implab.gradle.common.exec; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.Action; + +@NonNullByDefault +public class ShellTool { + + private final Shell shell; + + private final String executable; + + ShellTool(String executable, Shell shell) { + this.shell = shell; + this.executable = executable; + } + + public ShellExec arguments(String... args) { + return shell.create(CommandSpec.builder() + .executable(executable) + .arguments(args) + .build()); + } + + public ShellExec arguments(Iterable args) { + return shell.create(CommandSpec.builder() + .executable(executable) + .arguments(args) + .build()); + } + + public ShellExec arguments(Action> args) { + var builder = CommandSpec.builder() + .executable(executable); + args.execute(builder); + + return shell.create(builder.build()); + } + +} 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,6 +5,9 @@ import java.lang.ProcessBuilder.Redirect import java.util.ArrayList; import java.util.concurrent.CompletableFuture; +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault class SystemExecBuilder extends AbstractExecBuilder { SystemExecBuilder(CommandSpec command) { super(command); 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 @@ -23,7 +23,7 @@ public abstract class ShellExecTask @Override protected ShellExec execBuilder() { return getShell().get() - .of(CommandSpec.builder() + .create(CommandSpec.builder() .commandLine(getCommandLine().get()) .build()); } diff --git a/common/src/main/java/org/implab/gradle/common/utils/Configurations.java b/common/src/main/java/org/implab/gradle/common/utils/Configurations.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/utils/Configurations.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.implab.gradle.common.utils; - -import org.gradle.api.Action; -import org.gradle.api.artifacts.Configuration; - -public final class Configurations { - - public static final Action RESOLVABLE = Configurations::resolvable; - - public static final Action CONSUMABLE = Configurations::consumable; - - private Configurations() { - } - - private static void resolvable(Configuration configuration) { - configuration.setCanBeResolved(true); - configuration.setCanBeConsumed(false); - } - - private static void consumable(Configuration configuration) { - configuration.setCanBeResolved(false); - configuration.setCanBeConsumed(true); - } -} diff --git a/common/src/main/java/org/implab/gradle/common/utils/OperatingSystem.java b/common/src/main/java/org/implab/gradle/common/utils/OperatingSystem.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/OperatingSystem.java @@ -0,0 +1,35 @@ +package org.implab.gradle.common.utils; + +import java.io.File; +import java.util.Optional; + +import org.implab.gradle.common.utils.os.SystemResolver; + +public interface OperatingSystem { + + public static OperatingSystem CURRENT = SystemResolver.current(); + + public static final String WINDOWS_FAMILY = "windows"; + + public static final String LINUX_FAMILY = "linux"; + + public static final String FREE_BSD_FAMILY = "freebsd"; + + public static final String MAC_OS_FAMILY = "os x"; + + public static final String UNKNOWN_FAMILY = "unknown"; + + String family(); + + String name(); + + String version(); + + Optional which(String cmd); + + Optional which(String cmd, Iterable paths); + + public static OperatingSystem current() { + return CURRENT; + } +} 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,9 +1,13 @@ package org.implab.gradle.common.utils; +import java.text.MessageFormat; import java.util.Iterator; import java.util.Spliterators; import java.util.Optional; +import java.util.Set; +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; @@ -49,6 +53,17 @@ public final class Values { return provider.isPresent() ? Optional.of(provider.get()) : Optional.empty(); } + public static T required(Provider provider, String providerName) { + if (!provider.isPresent()) + throw new IllegalStateException( + MessageFormat.format("The value for the '{0}' provider must be specified", providerName)); + return provider.get(); + } + + public static Set mapSet(Set values, Function mapper) { + return values.stream().map(mapper).collect(Collectors.toUnmodifiableSet()); + } + private static class ArrayIterator implements Iterator { private final T[] data; diff --git a/common/src/main/java/org/implab/gradle/common/utils/os/FreeBsd.java b/common/src/main/java/org/implab/gradle/common/utils/os/FreeBsd.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/os/FreeBsd.java @@ -0,0 +1,16 @@ +package org.implab.gradle.common.utils.os; + +import org.implab.gradle.common.utils.OperatingSystem; + +public class FreeBsd extends GenericSystem{ + + FreeBsd(String name, String version) { + super(name, version); + } + + @Override + public String family() { + return OperatingSystem.FREE_BSD_FAMILY; + } + +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/os/GenericSystem.java b/common/src/main/java/org/implab/gradle/common/utils/os/GenericSystem.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/os/GenericSystem.java @@ -0,0 +1,64 @@ +package org.implab.gradle.common.utils.os; + +import java.io.File; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.implab.gradle.common.utils.OperatingSystem; +import org.implab.gradle.common.utils.Values; + +class GenericSystem implements OperatingSystem { + + private final String name; + + private final String version; + + GenericSystem(String name, String version) { + this.name = name; + this.version = version; + } + + public String getPathVar() { + return "PATH"; + } + + @Override + public String family() { + return OperatingSystem.UNKNOWN_FAMILY; + } + + @Override + public String name() { + return name; + } + + @Override + public String version() { + return version; + } + + protected Stream getPath() { + String path = System.getenv(getPathVar()); + return path == null + ? Stream.empty() + : Arrays.stream(path.split(Pattern.quote(File.pathSeparator))) + .map(File::new); + } + + protected Function> candidates(String cmd) { + return base -> Stream.of(new File(base, cmd)); + } + + @Override + public Optional which(String cmd) { + return getPath().flatMap(candidates(cmd)).filter(File::isFile).findAny(); + } + + @Override + public Optional which(String cmd, Iterable paths) { + return Values.stream(paths.iterator()).flatMap(candidates(cmd)).filter(File::isFile).findAny(); + } +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/os/Linux.java b/common/src/main/java/org/implab/gradle/common/utils/os/Linux.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/os/Linux.java @@ -0,0 +1,16 @@ +package org.implab.gradle.common.utils.os; + +import org.implab.gradle.common.utils.OperatingSystem; + +public class Linux extends GenericSystem { + + Linux(String name, String version) { + super(name, version); + } + + @Override + public String family() { + return OperatingSystem.LINUX_FAMILY; + } + +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/os/MacOs.java b/common/src/main/java/org/implab/gradle/common/utils/os/MacOs.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/os/MacOs.java @@ -0,0 +1,15 @@ +package org.implab.gradle.common.utils.os; + +import org.implab.gradle.common.utils.OperatingSystem; + +public class MacOs extends GenericSystem { + + MacOs(String name, String version) { + super(name, version); + } + + @Override + public String family() { + return OperatingSystem.MAC_OS_FAMILY; + } +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/os/SystemResolver.java b/common/src/main/java/org/implab/gradle/common/utils/os/SystemResolver.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/os/SystemResolver.java @@ -0,0 +1,29 @@ +package org.implab.gradle.common.utils.os; + +import org.implab.gradle.common.utils.OperatingSystem; + +public class SystemResolver { + public static OperatingSystem current() { + var osName = System.getProperty("os.name"); + var osVersion = System.getProperty("os.version"); + + return forName(osName, osVersion); + } + + public static OperatingSystem forName(String os, String version) { + var osName = os.toLowerCase(); + + if (osName.contains("windows")) { + return new Windows(osName, version); + } else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) { + return new MacOs(osName, version); + } else if (osName.contains("linux")) { + return new Linux(osName, version); + } else if (osName.contains("freebsd")) { + return new FreeBsd(osName, version); + } else { + // Not strictly true + return new GenericSystem(osName, version); + } + } +} diff --git a/common/src/main/java/org/implab/gradle/common/utils/os/Windows.java b/common/src/main/java/org/implab/gradle/common/utils/os/Windows.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/os/Windows.java @@ -0,0 +1,26 @@ +package org.implab.gradle.common.utils.os; + +import java.io.File; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.implab.gradle.common.utils.OperatingSystem; + +class Windows extends GenericSystem { + + private Stream exeSuffixes = Stream.of(".cmd", ".bat", ".exe"); + + Windows(String name, String version) { + super(name, version); + } + + @Override + public String family() { + return OperatingSystem.WINDOWS_FAMILY; + } + + @Override + protected Function> candidates(String cmd) { + return base -> exeSuffixes.map(suffix -> new File(base, cmd + suffix)); + } +}