diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,3 +1,4 @@ syntax: glob .gradle/ common/build/ +common/bin/ diff --git a/common/src/main/java/org/implab/gradle/common/dsl/CommandSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/CommandSpec.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/dsl/CommandSpec.java @@ -0,0 +1,13 @@ +package org.implab.gradle.common.dsl; + + +import org.gradle.api.provider.ListProperty; + +public interface CommandSpec { + ListProperty getCommandLine(); + + void commandLine(Object arg0, Object... args); + + void args(Object arg0, Object... args); + +} diff --git a/common/src/main/java/org/implab/gradle/common/dsl/EnvironmentSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/EnvironmentSpec.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/dsl/EnvironmentSpec.java @@ -0,0 +1,48 @@ +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.implab.gradle.common.utils.Closures; +import org.implab.gradle.common.utils.Values; + +import groovy.lang.Closure; + +/** + * Configuration properties of the execution shell. This object specifies a + * working directory and environment variables for the processes started within + * this shell. + */ +public interface EnvironmentSpec { + + /** Working directory */ + DirectoryProperty getWorkingDirectory(); + + /** Environment variables */ + MapProperty getEnvironment(); + + /** + * Configures the environment variable using the specified action. The + * action is called when the value is calculated; + */ + default void env(Action> configure) { + var provider = getEnvironment() + .orElse(Map.of()) + .map((base) -> { + var props = new HashMap(base); + + configure.execute(props); + + return Values.mapValues(props, Values::toString); + }); + + getEnvironment().set(provider); + } + + default void env(Closure configure) { + env(Closures.action(configure)); + } +} diff --git a/common/src/main/java/org/implab/gradle/common/dsl/PipeSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/PipeSpec.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/dsl/PipeSpec.java @@ -0,0 +1,12 @@ +package org.implab.gradle.common.dsl; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +public interface PipeSpec { + RedirectToSpec getStdout(); + + RedirectToSpec getStderr(); + + RedirectFromSpec getStdin(); +} diff --git a/common/src/main/java/org/implab/gradle/common/dsl/ProcessSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/ProcessSpec.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/dsl/ProcessSpec.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.implab.gradle.common.dsl; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.implab.gradle.common.exec.ExecBuilder; -import org.implab.gradle.common.exec.RedirectFrom; -import org.implab.gradle.common.exec.RedirectTo; - -public class ProcessSpec { - private RedirectFrom inputRedirect; - - private RedirectTo outputRedirect; - - private RedirectTo errorRedirect; - - private List command = new ArrayList<>(); - - private File directory; - - public ProcessSpec command(String... args) { - command = new ArrayList<>(); - args(args); - return this; - } - - public ProcessSpec directory(File workingDir) { - directory = workingDir; - return this; - } - - public ProcessSpec command(Collection args) { - command = new ArrayList<>(); - args(args); - return this; - } - - public ProcessSpec args(String... args) { - for (String arg : args) - command.add(arg); - return this; - } - - public ProcessSpec args(Collection args) { - command.addAll(args); - return this; - } - - public ProcessSpec stderr(RedirectTo to) { - errorRedirect = to; - return this; - } - - public ProcessSpec stdin(RedirectFrom from) { - inputRedirect = from; - return this; - } - - public ProcessSpec stdout(RedirectTo to) { - outputRedirect = to; - return this; - } - - public void accept(ExecBuilder executor) { - command.forEach(executor::argument); - executor.directory(directory); - if (inputRedirect != null) - executor.stdin(inputRedirect); - if (outputRedirect != null) - executor.stdout(outputRedirect); - if (errorRedirect != null) - executor.stderr(errorRedirect); - } - - -} 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 @@ -1,7 +1,5 @@ package org.implab.gradle.common.dsl; -import java.io.File; -import java.io.InputStream; import java.util.Optional; import java.util.function.Supplier; @@ -19,19 +17,11 @@ public class RedirectFromSpec { return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); } - public void fromFile(File file) { - this.streamRedirect = () -> RedirectFrom.file(file); - } - - public void fromFile(Provider file) { - this.streamRedirect = file.map(RedirectFrom::file)::get; - } - - public void fromStream(InputStream stream) { - this.streamRedirect = () -> RedirectFrom.stream(stream); - } - - public void fromStream(Provider stream) { - this.streamRedirect = stream.map(RedirectFrom::stream)::get; + public void from(Object input) { + if (input instanceof Provider inputProvider) { + this.streamRedirect = inputProvider.map(RedirectFrom::any)::get; + } else { + this.streamRedirect = () -> RedirectFrom.any(input); + } } } 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 @@ -6,37 +6,52 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.gradle.api.provider.Provider; import org.implab.gradle.common.exec.RedirectTo; +@NonNullByDefault public class RedirectToSpec { private Supplier streamRedirect; public boolean isRedirected() { - return streamRedirect != null; + return getRedirection().isPresent(); } public Optional getRedirection() { return streamRedirect != null ? Optional.ofNullable(streamRedirect.get()) : Optional.empty(); } + public @Nullable RedirectTo getRedirectionOrNull() { + return streamRedirect != null ? streamRedirect.get() : null; + } + public void toFile(File file) { this.streamRedirect = () -> RedirectTo.file(file); } - public void toFile(Provider file) { - this.streamRedirect = file.map(RedirectTo::file)::get; + public void toFile(Provider fileProvider) { + this.streamRedirect = fileProvider.map(RedirectTo::file)::getOrNull; } public void toStream(OutputStream stream) { this.streamRedirect = () -> RedirectTo.stream(stream); } - public void toStream(Provider stream) { - this.streamRedirect = stream.map(RedirectTo::stream)::get; + public void toStream(Provider streamProvider) { + this.streamRedirect = streamProvider.map(RedirectTo::stream)::getOrNull; } - public void toConsumer(Consumer consumer) { + public void to(Object output) { + if (output instanceof Provider outputProvider) { + this.streamRedirect = outputProvider.map(RedirectTo::any)::get; + } else { + this.streamRedirect = () -> RedirectTo.any(output); + } + } + + public void consume(Consumer consumer) { this.streamRedirect = () -> RedirectTo.consumer(consumer); } } diff --git a/common/src/main/java/org/implab/gradle/common/dsl/TaskSpec.java b/common/src/main/java/org/implab/gradle/common/dsl/TaskSpec.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/dsl/TaskSpec.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.implab.gradle.common.dsl; - -import java.util.stream.Stream; - -import org.gradle.api.Action; -import org.gradle.api.Task; -import org.gradle.api.tasks.TaskProvider; - -/** - * Wrapper around TaskProvider for simplified lazy task configuration - */ -public class TaskSpec implements TaskDependency { - private final TaskProvider taskProvider; - - public TaskSpec(TaskProvider taskProvider) { - this.taskProvider = taskProvider; - } - - public TaskSpec configure(Action configure) { - taskProvider.configure(configure); - return this; - } - - public TaskSpec dependsOn(TaskDependency... other) { - taskProvider.configure(t -> t.dependsOn(references(other))); - return this; - } - - public TaskSpec finalizedBy(TaskDependency... other) { - taskProvider.configure(t -> t.finalizedBy(references(other))); - return this; - } - - public TaskSpec mustRunAfter(TaskDependency... other) { - taskProvider.configure(t -> t.mustRunAfter(references(other))); - return this; - } - - public TaskSpec doLast(Action action) { - taskProvider.configure(t -> t.doLast(self -> action.execute(t))); - return this; - } - - public TaskSpec doFirst(Action action) { - taskProvider.configure(t -> t.doFirst(self -> action.execute(t))); - return this; - } - - /** Returns a task provider for this task, can be used as dependency object */ - @Override - public Object reference() { - return taskProvider; - } - - private static Object[] references(TaskDependency[] other) { - return Stream.of(other).map(TaskDependency::reference).toArray(Object[]::new); - } -} 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 new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandBuilder.java @@ -0,0 +1,34 @@ +package org.implab.gradle.common.exec; + +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.implab.gradle.common.utils.Values; + +@NonNullByDefault +public interface CommandBuilder { + + CommandBuilder executable(String executable); + + default CommandBuilder commandLine(String executable, String... args) { + return executable(executable) + .arguments(args); + } + + default CommandBuilder arg(String arg0, String... args) { + return arguments(() -> Stream + .concat(Stream.of(arg0), Stream.of(args)) + .iterator()); + } + + default CommandBuilder arguments(String... args) { + return arguments(Values.iterable(args)); + } + + 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 new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/CommandSpec.java @@ -0,0 +1,16 @@ +package org.implab.gradle.common.exec; + +import java.util.List; +import java.util.stream.Stream; + +public interface CommandSpec { + + String executable(); + + List arguments(); + + default List commandLine() { + return Stream.concat(Stream.of(executable()), arguments().stream()).toList(); + } + +} 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 @@ -18,15 +18,25 @@ class EchoExecBuilder extends ExecBuilde } @Override - protected CompletableFuture startInternal(Map environment, File directory, - List commandLine, Optional stdinRedirect, Optional stdoutRedirect, + protected CompletableFuture startInternal( + Map environment, + Optional directory, + List commandLine, + Optional stdinRedirect, + Optional stdoutRedirect, Optional stderrRedirect) throws IOException { + var outputRedirect = echoToStderr ? stderrRedirect : stdoutRedirect; - return outputRedirect.map(to -> { - var bytes = String.format("exec: %s", commandLine).getBytes(StandardCharsets.UTF_8); - return to.redirect(new ByteArrayInputStream(bytes)).thenApply((x) -> 0); - }).orElse(CompletableFuture.completedFuture(0)); + return outputRedirect + .map(to -> { + var bytes = String.format("exec: %s", commandLine) + .getBytes(StandardCharsets.UTF_8); + + return to.redirect(new ByteArrayInputStream(bytes)) + .thenApply((x) -> 0); + }) + .orElse(CompletableFuture.completedFuture(0)); } } diff --git a/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentBuilder.java @@ -0,0 +1,25 @@ +package org.implab.gradle.common.exec; + +import java.io.File; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +public interface EnvironmentBuilder { + EnvironmentBuilder setVariable(String envVar, String value); + + EnvironmentBuilder environment(Map env); + + EnvironmentBuilder unsetVariable(String envVar); + + EnvironmentBuilder workingDirectory(File directory); + + EnvironmentBuilder workingDirectory(Optional directory); + + default EnvironmentBuilder from(EnvironmentSpec environmentSpec) { + return environment(environmentSpec.environment()) + .workingDirectory(environmentSpec.workingDirectory()); + } +} 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 new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/EnvironmentSpec.java @@ -0,0 +1,11 @@ +package org.implab.gradle.common.exec; + +import java.io.File; +import java.util.Map; +import java.util.Optional; + +public interface EnvironmentSpec { + Map environment(); + + Optional workingDirectory(); +} 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 --- a/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java +++ b/common/src/main/java/org/implab/gradle/common/exec/ExecBuilder.java @@ -4,26 +4,32 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; -import java.util.Collection; 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 { +public abstract class ExecBuilder implements + CommandBuilder, + PipeBuilder, + EnvironmentBuilder, + Runnable +{ + private String executable; - private final List commandLine = new ArrayList<>(); + private final List arguments = new ArrayList<>(); private final Map environment = new HashMap<>(); - private File directory; + private @Nullable File directory; private RedirectFrom inputRedirect; @@ -31,60 +37,83 @@ public abstract class ExecBuilder { private RedirectTo errorRedirect; - /** Sets command line, clears previous one if any */ - public ExecBuilder command(String cmd, String... args) { - commandLine.clear(); - commandLine.add(cmd); - Stream.of(args).forEach(commandLine::add); + @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; } - public ExecBuilder command(Collection cmd) { - this.commandLine.clear(); - this.commandLine.addAll(cmd); + public ExecBuilder commandLine(Iterable cmd) { + requireNonNull(cmd, "cmd can't be null"); + + this.arguments.clear(); + for (var arg : cmd) + this.arguments.add(arg); return this; } - /** Adds an argument to the command line */ - public ExecBuilder argument(String arg) { - Objects.requireNonNull(arg, "arg parameter can't be null"); - commandLine.add(arg); + @Override + public ExecBuilder arg(String arg0, String... args) { + requireNonNull(arg0, "arg0 parameter can't be null"); + arguments.add(arg0); + + for (var arg : args) + arguments.add(arg); + return this; } /** Sets the working directory */ - public ExecBuilder directory(File directory) { - Objects.requireNonNull(directory, "directory parameter can't be null"); + @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; + } + /** * Sets the environment value. The value cannot be null. * * @param envVar The name of the environment variable * @param value The value to set, */ - public ExecBuilder setEnvironment(String envVar, String value) { - Objects.requireNonNull(value, "Value can't be null"); - Objects.requireNonNull(envVar, "envVar parameter can't be null"); + @Override + public ExecBuilder setVariable(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; } - public ExecBuilder setEnvironment(Map env) { - Objects.requireNonNull(env, "env parameter can't be null"); + @Override + public ExecBuilder environment(Map env) { + requireNonNull(env, "env parameter can't be null"); environment.clear(); environment.putAll(env); return this; } - public ExecBuilder unsetEnvironment(String envVar) { - environment.remove(envVar); - return this; - } - /** * Sets redirection for the stdin, {@link RedirectFrom} will be applied * every time the process is started. @@ -92,36 +121,61 @@ public abstract class ExecBuilder { *

* If redirection */ + @Override public ExecBuilder stdin(RedirectFrom from) { - Objects.requireNonNull(from, "from parameter can't be null"); + requireNonNull(from, "from parameter can't be null"); + inputRedirect = from; return this; } + @Override + public PipeBuilder 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) { - Objects.requireNonNull(out, "out parameter can't be null"); + requireNonNull(out, "out parameter can't be null"); outputRedirect = out; return this; } + @Override + public PipeBuilder 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) { - Objects.requireNonNull(err, "err parameter can't be null"); + requireNonNull(err, "err parameter can't be null"); errorRedirect = err; return this; } - /** Implement this function to */ + @Override + public PipeBuilder stderr(Optional to) { + requireNonNull(to, "from parameter can't be null"); + errorRedirect = to.orElse(null); + return this; + } + + /** Implement this function to */ protected abstract CompletableFuture startInternal( Map environment, - File directory, + Optional directory, List commandLine, Optional stdinRedirect, Optional stdoutRedirect, @@ -134,10 +188,17 @@ public abstract class ExecBuilder { * @return * @throws IOException */ - public CompletableFuture start() throws IOException { + public CompletableFuture run() 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( environment, - directory, + Optional.ofNullable(directory), commandLine, Optional.ofNullable(inputRedirect), Optional.ofNullable(outputRedirect), 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 --- a/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java +++ b/common/src/main/java/org/implab/gradle/common/exec/ExecShell.java @@ -12,7 +12,7 @@ import java.util.function.Consumer; public interface ExecShell { /** Creates new process builder {@link ExecBuilder} */ - ExecBuilder builder(); + ExecBuilder executable(); /** * Creates default process execution "shell" which will execute processes @@ -47,7 +47,7 @@ public interface ExecShell { static ExecShell echo(Consumer> consumer) { return () -> new ExecBuilder() { @Override - protected CompletableFuture startInternal(Map environment, File directory, + protected CompletableFuture startInternal(Map environment, Optional directory, List commandLine, Optional stdinRedirect, Optional stdoutRedirect, Optional stderrRedirect) throws IOException { consumer.accept(commandLine); diff --git a/common/src/main/java/org/implab/gradle/common/exec/PipeBuilder.java b/common/src/main/java/org/implab/gradle/common/exec/PipeBuilder.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/PipeBuilder.java @@ -0,0 +1,27 @@ +package org.implab.gradle.common.exec; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +public interface PipeBuilder { + PipeBuilder stdin(RedirectFrom from); + + PipeBuilder stdin(Optional from); + + PipeBuilder stdout(RedirectTo to); + + PipeBuilder stdout(Optional to); + + PipeBuilder stderr(RedirectTo to); + + PipeBuilder stderr(Optional to); + + default PipeBuilder from(PipeSpec pipeSpec) { + return stdin(pipeSpec.stdin()) + .stdout(pipeSpec.stdout()) + .stderr(pipeSpec.stderr()); + } + +} 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 new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/PipeSpec.java @@ -0,0 +1,14 @@ +package org.implab.gradle.common.exec; + +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +@NonNullByDefault +public interface PipeSpec { + Optional stdout(); + + Optional stderr(); + + Optional stdin(); +} 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 @@ -33,5 +33,17 @@ public interface RedirectFrom { }); } + public static RedirectFrom any(final Object output) { + if (output instanceof File f) { + return file(f); + } else if (output instanceof InputStream stm) { + return stream(stm); + } else if (output instanceof RedirectFrom self) { + return self; + } else { + throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); + } + } + } 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 @@ -8,14 +8,28 @@ import java.util.Scanner; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * RedirectSpec */ +@NonNullByDefault public interface RedirectTo { CompletableFuture redirect(InputStream from); + public interface StringConsumer extends Consumer { + } + public static RedirectTo consumer(final Consumer consumer) { + return consumer(new StringConsumer() { + @Override + public void accept(String s) { + consumer.accept(s); + } + }); + } + public static RedirectTo consumer(final StringConsumer consumer) { return (src) -> CompletableFuture.runAsync(() -> { try (Scanner sc = new Scanner(src)) { while (sc.hasNextLine()) { @@ -44,4 +58,18 @@ public interface RedirectTo { } }); } + + public static RedirectTo any(final Object output) { + if (output instanceof StringConsumer fn) { + return consumer(s -> fn.accept(s)); + } else if (output instanceof File f) { + return file(f); + } else if (output instanceof OutputStream stm) { + return stream(stm); + } else if (output instanceof RedirectTo self) { + return self; + } else { + throw new IllegalArgumentException("The specified argument type isn't supported: " + output.getClass()); + } + } } \ No newline at end of file diff --git a/common/src/main/java/org/implab/gradle/common/exec/Runnable.java b/common/src/main/java/org/implab/gradle/common/exec/Runnable.java new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/exec/Runnable.java @@ -0,0 +1,19 @@ +package org.implab.gradle.common.exec; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +public interface Runnable { + CompletableFuture run() throws IOException; + + /** Starts process and will throw error if error code is non-zero */ + default CompletableFuture run(boolean throwOnError) throws IOException { + return run().thenApply(code -> { + if (!throwOnError && code != 0) + throw new CompletionException(new IOException( + String.format("The process is terminated with code %d", code))); + return code; + }); + } +} 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 @@ -11,12 +11,17 @@ import java.util.concurrent.CompletableF class SystemExecBuilder extends ExecBuilder { @Override - protected CompletableFuture startInternal(Map environment, File directory, - List commandLine, Optional stdinRedirect, Optional stdoutRedirect, - Optional stderrRedirect) throws IOException { + protected CompletableFuture startInternal( + Map environment, + Optional directory, + List commandLine, + Optional stdinRedirect, + Optional stdoutRedirect, + Optional stderrRedirect) throws IOException { - var builder = new ProcessBuilder(commandLine) - .directory(directory); + var builder = new ProcessBuilder(commandLine); + + directory.ifPresent(builder::directory); builder.environment().putAll(environment); diff --git a/common/src/main/java/org/implab/gradle/common/tasks/ExternalTask.java b/common/src/main/java/org/implab/gradle/common/tasks/ExternalTask.java deleted file mode 100644 --- a/common/src/main/java/org/implab/gradle/common/tasks/ExternalTask.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.implab.gradle.common.tasks; - -import java.io.IOException; -import java.util.Optional; -import java.util.concurrent.ExecutionException; - -import org.gradle.api.DefaultTask; -import org.implab.gradle.common.dsl.ProcessSpec; -import org.implab.gradle.common.exec.ExecBuilder; -import org.implab.gradle.common.exec.RedirectFrom; -import org.implab.gradle.common.exec.RedirectTo; - -public abstract class ExternalTask extends DefaultTask { - - /** - * A default redirection to the build log when loglevel is set to INFO, - * otherwise returns an empty redirection. - */ - protected Optional loggerInfoRedirect() { - return getLogger().isInfoEnabled() - ? Optional.of(RedirectTo.consumer(getLogger()::info)) - : Optional.empty(); - } - - /** - * A default redirection to the build log when loglevel is set to ERROR, - * otherwise returns an empty redirection. Note that ERROR level is set - * by default for a build runs. - */ - protected Optional loggerErrorRedirect() { - return getLogger().isErrorEnabled() - ? Optional.of(RedirectTo.consumer(getLogger()::error)) - : Optional.empty(); - } - - /** - * Stdout redirection for {@link #exec(ProcessSpec)}, default implementation - * will forward stdout to the build log through {@link #loggerErrorRedirect()} - */ - protected Optional stdoutRedirection() { - return loggerInfoRedirect(); - } - - /** - * Stderr redirection for {@link #exec(ProcessSpec)}, default implementation - * will forward stderr to build log through {@link #loggerErrorRedirect()}. - */ - protected Optional stderrRedirection() { - return loggerErrorRedirect(); - } - - /** - * Stdin redirection for {@link #exec(ProcessSpec)}, empty by default. - */ - protected Optional stdinRedirection() { - return Optional.empty(); - } - - /** - * Executes the specified process specification - * - * @param spec - * @throws InterruptedException - * @throws ExecutionException - * @throws IOException - */ - protected void exec(ProcessSpec spec) throws InterruptedException, ExecutionException, IOException { - var executor = exec(); - - stdoutRedirection().ifPresent(executor::stdout); - stderrRedirection().ifPresent(executor::stderr); - stdinRedirection().ifPresent(executor::stdin); - - getLogger().info("Staring: {}", spec.command()); - - spec.accept(executor); - - // runs the command and checks the error code - var code = executor.start().get(); - - // check success code - if (code != 0) - throw new IOException("The process exited with error code " + code); - } - - protected boolean checkRetCode(ProcessSpec proc, int code) - throws InterruptedException, ExecutionException, IOException { - - var executor = exec(); - - if (getLogger().isInfoEnabled()) { - loggerInfoRedirect().ifPresent(executor::stdout); - loggerInfoRedirect().ifPresent(executor::stderr); - } - - getLogger().info("Starting: {}", proc.command()); - - proc.accept(executor); - - return executor.start().get() == code; - } - - protected abstract ExecBuilder exec(); -} diff --git a/common/src/main/java/org/implab/gradle/common/tasks/ExecuteTask.java b/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java rename from common/src/main/java/org/implab/gradle/common/tasks/ExecuteTask.java rename to common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java --- a/common/src/main/java/org/implab/gradle/common/tasks/ExecuteTask.java +++ b/common/src/main/java/org/implab/gradle/common/tasks/ShellExecTask.java @@ -1,12 +1,29 @@ 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.tasks.Internal; +import org.gradle.api.tasks.TaskAction; +import org.implab.gradle.common.dsl.CommandSpec; +import org.implab.gradle.common.dsl.PipeSpec; import org.implab.gradle.common.dsl.RedirectFromSpec; import org.implab.gradle.common.dsl.RedirectToSpec; +import org.implab.gradle.common.dsl.EnvironmentSpec; +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; -public abstract class ExecuteTask extends DefaultTask { +public abstract class ShellExecTask + extends DefaultTask + implements CommandSpec, PipeSpec, EnvironmentSpec, ObjectsMixin { private final RedirectToSpec redirectStderr = new RedirectToSpec(); @@ -15,13 +32,22 @@ public abstract class ExecuteTask extend private final RedirectFromSpec redirectStdin = new RedirectFromSpec(); @Internal + @Override + public abstract DirectoryProperty getWorkingDirectory(); + + @Internal + @Override + public abstract MapProperty getEnvironment(); + + @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; } @@ -30,6 +56,7 @@ public abstract class ExecuteTask extend * STDOUT redirection, if not specified, redirected to logger::info */ @Internal + @Override public RedirectToSpec getStdout() { return redirectStdout; } @@ -38,15 +65,46 @@ public abstract class ExecuteTask extend * 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())); - /** Appends specified parameters to the command line */ - void commandLine(String... args) { - getCommandLine().addAll(args); - } - - + } + + @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() + .workingDirectory(getWorkingDirectory().get().getAsFile()) + .environment(getEnvironment().getOrElse(Map.of())) + .commandLine(getCommandLine().get()); + + getStdout().getRedirection().ifPresent(execBuilder::stdout); + getStderr().getRedirection().ifPresent(execBuilder::stderr); + getStdin().getRedirection().ifPresent(execBuilder::stdin); + + execBuilder.run().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)); + } + } 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 @@ -2,13 +2,31 @@ package org.implab.gradle.common.utils; import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.gradle.api.provider.Provider; + +@NonNullByDefault public class Strings { private static final Pattern firstLetter = Pattern.compile("^\\w"); public static String capitalize(String string) { - if (string == null || string.length() == 0) - return ""; - return firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase()); + return string == null ? null + : string.length() == 0 ? string + : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase()); + } + + 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)); + } + + public static String asString(Object value) { + if (value == null) + return null; + if (value instanceof Provider provider) + return asString(provider.get()); + else + return value.toString(); } } 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 new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/ThrowingConsumer.java @@ -0,0 +1,18 @@ +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 new file mode 100644 --- /dev/null +++ b/common/src/main/java/org/implab/gradle/common/utils/Values.java @@ -0,0 +1,73 @@ +package org.implab.gradle.common.utils; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.gradle.api.provider.Provider; + +public final class Values { + + private 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))); + } + + /** Converts the supplied value to a string. + */ + public static String toString(Object value) { + if (value == null) { + return null; + } else if (value instanceof String string) { + return string; + } else if (value instanceof Provider provider) { + return toString(provider.getOrNull()); + } else if (value instanceof Supplier supplier) { + return toString(supplier.get()); + } else { + return value.toString(); + } + } + + public static Iterable iterable(T[] values) { + return () -> new ArrayIterator<>(values); + } + + private static class ArrayIterator implements Iterator { + private final T[] data; + + private int pos = 0; + + ArrayIterator(T[] data) { + this.data = data; + } + + @Override + public boolean hasNext() { + return pos < data.length; + } + + @Override + public T next() { + return data[pos++]; + } + } + +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..e6441136f3d4ba8a0da8d277868979cfbc8ad796 GIT binary patch literal 43453 zc$}oVb8u$ey6zoxY?~e1wr%@~ZQHhOJL%YF$L!d)(Mi63*V^AX`>eh9TkF(${+LyB z&Z?(Ijq7(`H|9`~2K|N#1PKWV1O!9`^q+r-Ku|!kqAG&4l5%47GLpiga>}BrDqujs zf4$t)(so*BL-(Dj>oYesyTB5Y?7X~c@li4=Q)N$KPVCO$6C$Ai2qV)7Xf1!f@nURH zjLSW_@MbI*Mg#>py-vHqKS+6>n$1l*)(t`ER!&!_(|5hno8++Nytsb0Aoh;iCO=X1 z_`E-Lnqgdq6@1-JWex5!q%}=#tZrt7wmRDB?GC)JKVIWBXl#bNxm9Hye%#qMBVAuF z?*W{&!yhk4A2+84xxIB}wb>`9K0IIVLHUS)w|@@DRJxQwC`?c3d)SzMiZJg!HCl*N z8Iyx^oaS4f2VYPrX4dYh7P6^D%b^WaZe?21+&b)v?slfQ1}&{4<-weaglJbvS;rX` zNul4tjkTI?Tn-})EZztXdX?g8FTs_0VtJKnSiBW2b$$^3EKpYSm#SAl5GNW1*j0Zb zTpl0lXfa+K5*jSzq}121&q;5=XMk9e<2Xnc&Lm@U^AK9p1=C1M&4T;{y7puQ*SmqX zJao5WI5Ch_hO|(9nT?+Z%LUJi)dicBT4*V~Py8LVmmkJExeIr^*WFMo&cC)LFho)% z76w5u)s>8eHylCjGbKGG_zPsitY5l3`0C|Z2v#f^IXm{+gYq|dqd!{B#erPGy}dqr zs#l!`5xke^D8%(bPonX0DyMlgS-fM3Ac|w$`Z+%7QU|!z(?lx8+u3`oUwi2#Y&^N zyXJ|FR2&*^Cd`SHgixYp;BzNx`&T>SW@o$pyNBk&_EKQW2<_s5U6{TFkgxjZo93t2 zJE>wOHx8{I?7;)=XN}x<%=V+J3cicWO*f;C^xU&tB#XqY~3TlOvTy!NASeJ(?D_L5Yuxoa&KcNmkRL5wpCQL!}b^VMt)3B_gECT zJ46;T9Ki{xN&;@Xn2f-y&Yv1J)j$&XCAJ4GP_KErN&)7Pz#38nQcQb70_+T+UAVh3 zv|#jBOjWqx)wIS1`8600U=RxOjh6s34aiw|z%MK-Y1!LH`aR`pd<9Cf) zMJSf=tQ;|*D~Dg;pAUM->x2|NDeyTfid1TC5Muly2u5hOqE`bt92YL_GkrkWtO9WU zKby`Jlc*UZ`&szpgF#?$aQFfsua9`Lx)`b*Cj^V*A4#oEv8A9jO5HkwbfdC9p=Z?bRo*D zfQYpx6C*_umd4B%nM-E5CKkufuMsDFVtP+*7jVNVvo0~ZP;yrxp~~bZ3)haW+r*^p_zXlxMkKt>XJFU8n#S8 zK@%}q!{7I*>Pu%$-}AV;b6L{+xtC$8hraR-Iax2k8t=ahgo$wyQe#aK3y;3yBEmN^ zt*vi>ejuhku3%o)Ut&!3-nlodgxWy84f;u%1BB!f6or$YOh`+unE>5hgrjoq2pN{i zO+`aQx+L)h-bp}B@FQic*|7)%D_W+I@CVx(9EU9@SLI!kk+l1%#R0icmrCZMGQ+?_ zH4*+UMeWL=xfsOwy3{*X!3>!zDn**3=L*u3Fhyb9SUtvfq<+g55HIP3<1=s9+yV06 zT=?9^I~bJmwgN+=PsYj(5GF0qdr%Zb#3T?U_dZIz ztxmTv^Hdv8x;(xHYKF{?4v!6<4H@I}G~y;;s!GP-$dE+&%nclSU;^;F$$dATto|lK zFTfA1OM>55PGp`ld9HVcUT#_I@jC}c#&J!UarIZ!`pMyjuI7><56qS}p8eVYCoKkG z2O4n>-l5J=)9(&2R{fEPN zZ*(BBbC2mA7cV8}G=n`r;l<1sJRL^(9o81WvE|&MA?+YwN*X^lT{sJJAiA~ntOdTw z+?7te(!!raDx4n~Y8HJN(H{U+Fq9T4gLfOD9mwtFK^=JS`=&sv9V{<%JZHj&j#5m8 z|Bm1fIxaCk=#TO2D`K-86$PR}PnOT#`G%$s!chrT-F9E&ny5^6j62v6$ggEsNf=tO z%7P4P0zFkelRRr)jC)T?Ur$vFpI&&|_v6TCJ}w%yy#zF+E5vJ^4EA3i<@48-OU8^I&G=hU7L0*)CO zhO_{xd)o;k^u@*Y=;yWwep^bcFy9H0(tRYXNoDK|tx)y8F&n{TXf@UEO~c+Kabq&l z@+;a0)(nOEM?4NFF^K%kIkb3(sj17-S3*#MzAs}WQ8L~_@!I>eR zzSy$c(NK@8t-ZnsC3V53l+Z^RAUSaT3~GgNe(_qS*aMcvWvI#SzMTd0>wy%csPU^v z86Uwc;u2ZeslD3_HIlsPVHIMJHW#)jY3S@tHQ7pg<4q1}GO8E{7Ocr3xx-?VbdCcr zOV{7BK+lyCU>)u5C;5?Fj+vf;F;X#?^pZd6B=)@b-6x2D0Myt?-;#p;=5a>v+v>8e$Tx>_VLOl}g@7 z!4~oDLv}+Kfe^(8!>h=b`DI0Y{BN)Yu`;|BOhN7-Tjq#WuJd=@^A^{;*C3zZ3Yd`L z!Wuu_olPJ)AxbxX#UNsD>6bJ5K&@al4!3I>vv52rJt3LBeSSYVFiO{l7sO$KPe@!- zsrtli?&Tc_yy_#@m>(}uc#(}-Sty6oz;YLdf_SJ6mxYkCoH_bIlohz`&Dy2_vZ~~l z7lm_K?lK^~uH_B#3FXQ;LK0qS8@JJ?bR0vg76^Pt%t~(otws{|y5T0dM7@X>23|&M z`N3DkVlz8GJ5V1w5NeC`r1sFmxey2XEV!KZ+M6%;MWoG zV1RW~UH~9Jfy{AbvxH*TL=+I_V>(%^o$Kq7a?u@HzcOT+h)K$$A9+VZ0n0vR_mmEf zOPssriJ(o9$2(~JNepw!bwTMkqkv=#bR?o4sxLNYhu$mG_Gn;9b`TYRHyF7 zmbcRV2h)+#oH`am1o(qib>t`M9VC4lZ<~iG~%QC2MTC|XIfff@&8>!I< z3Xd7f#e2UG=U8(8wv@yUZ_v!j?_^jXkuL~u?GjuSJepLewa5R7v8{jVpWo2`vV>cY zvZuy`9et4jC3jq8*W@hgii6DA+KXO5RO=TlkZF+oKw6q)tB71!CaH)H?n@TUn^cRH z$GFS6JuI>)TuzxmiqO+D7PZ57^KAb#V)O_0VQ$7|rsepRaG$pKj_;thG z>%sUt-bpyR1Y3;pXS}G97)}u#ZeME^PIXmU5BT$0uYxoLWPNK9{wW*~P!Bl}ki!4i zFQWeEeo90A7GCiF(u7WBe)HZ}lTV;Kuu6G2Ba7aJ2>XHj<}6MJV1J6k#1s1k(x^Y{iA--_CUCEmr&=-Y0 z8VW8@(CwMuKEB<4Kz&#j%9w6hh?W<*)165ry(Mf#J} zii96PA95wL-P|gr`}^cpjYaazFTv{QWS2!DdHCF|_tL}=5m$?_RMW^=gq<`i582K_ ztciLvVZtR+48!@^f*Q?cn3bftm%%ZenCJ~pL@gT7#Ke+kG!K+0XFBhzTFjxf#v$LI zU7@pJ;_`;agvZ!Z<)+lmqJN#rf=^LjIy+45+_=A)A;pHt||O*Edbwq*=s1!#;ua zm%_ivM$df0`-OSB6|@AWwKKa-^BzxUyr0js5&#|5|Ge?FyY@JZQQJR?wWqa_WNvLL zH1&)rb#|xVRkw3b&F`Ye?FSW1bHP6`)lKIXtU@xE*4}^;QM6j~3}Sb|atL4B3slV_ zuhzF*v1`T+Ll(?A|A92rTtXd8ubb7_Ie;J$byt${nvrN5p?i4=gehEk*6gj!lN@W1 ze&@Yj?_Q=onyL5$8X0zQ-8&5j-p!^+X&v3PJU>tH)=;F6K`R!x zMuBU@2!FAPnB=?4)p|{+{b2KoY~n;RHw`sB0!UynEmC?~)est#kuTkUeMb^la8-T@|Jf85r32V)sgFFe6H1#zI4&agsOtR6zar zjw?y&eiC6roBLm&XvYb1d$@Gv5~7`aI!cW0DjcGhuF&ejyZ4#jQO#jNnYco7XBcuP z-B>$3v-Q;+;8qseB5`a|QJ$e459r{Am2at{^B#pnffp+Q0j{PZ8cQ zrjREPGII|b2e~xG@-_S%$B;qo-MbnI>+R(SZcu@W8U?|Mj5gE{Fd*2sLZ^Ppe{qrf+B z4C*m93Yfhx;E`{VGBW54h3U7$8!c|S1>L%~+{u`)M4#aN=7EXkdk8n;Sn{n%h^aI! zeW& zg6Z8!C^U`}ELK9g30Q;1G-@39kzvFJSwXiLx=F(7)$lo&nE}km1 z&b*Ytcg50MpasO0qMvtrcZR>5hXQULAfpAh;j<>tXL$4((hD4=YxiR5rCP32*air9 z<|`CRF?2+Qrm9yA3tF%vZX|^H2ZiLN)~!c*(6Pa>n4YkL`^$ACW&^p8=c^fE`JRd~ z_yOrt&!HHum~ZzpFx;9aV`sAr*~5tQ7O)1v0G6GwR6_qU@IXWihGBtO(?lmupj$U^2=83^n-49$eM%Sf=)_n%<4R)dFdNB z=PB`$+Qg1AC#Do~pN=eA@jvxFLr?VrSvL&FBAy%e&L?cZg+`Ea^gErIpMKAGDE+SA z6TQ_!ce7`YSx+~`yat&yU?^_e*TM9swUS?DgcCq#PG52$Krqi|J4t1XCLw0a3Lssi zwtWSWW>LZ3bO~)R9x-2$_rk-{x_@8|L46xDja*uUa=hPWctaa!7Q0S?rx0fYek|d0 zGu&@mid}FmtKt?$H?{7;vRUeuf+<}1fMw7bhh0$1z)jRkx8u<;SlY0zs>l3 z6kVk3?EhrZSe?>?Q1OoxZCuz*sP%{8mT!N@Fweiku#kzVoui4Eg`<Mh&PZ*+=9Od2{9SPf#0;P=m6K97OX_kW&i!AfURlTaG<{CVUm|tjw?-PW1 zp&^N0PsU48wA~7pVEo-Mt<}mg&$U{hEGPoDi?V*uy8UewXm zVV3DFL{}W?e094&cDB@T$Td z;%*<<1W$Z!rzdG*YI;K(Jm;UvpB8B!M|$r9;CzaPo(Ii6^kTWRHN~dkWU9}GKDk~5 z77C2mVj({#Ar~_9)W(~e(K$pas zdC0BQj4#|GFB<=WX{fLz!QLNGr@TFx<%!G%IDx`j@g9`f-qABjRd<4h-?it4--8rh zDK&u_#mt->FvHZKe^9$knDvobAd~2a8ZyQe-)`X=rfOzr*El4%KUa4Q?0mvb!Y(k|l#xgm7>ViX=VZLE=Mz9vH$T%XxbP}WU9}b7DGqm)-xapgo zy3dE#b1eCIN8Mc8Z9~JQ20|SeNAL3pU!&^{Cf}lO~ODdz8&bSvlnr zkUA9=p~|WcdvIB}@2}%z*Sq>3O>^RD?Fq288K-7q!(R?zY9;^e zBPrAt_EuW`(dWAR+9|JdiM{vtF$_@FEq$n4EZ+LclfAiuEWuGJx+OwMm{5Yk+QgBw zu#3xNv?*;&(#8?EI-XqEU?qwy!u?a24*W3n>AE6a?!fP(4SO74TYv`Jxc$rE(;M4*aS98*SDC6&y z3CHk*i7!MhA~A=$o8c~NEnMg;cv49!;vx|f&+Uz6WP=ST3BVQ~y z4cy>hQrIY}AyNRAuxe7>D&~-5b$kQjV{`ck6&7nbn%&AcWT9LaC6lqv_bnkN^c}5R zS6vy((ayvJzHUyYDD9nBB(tL~F2xkqf1P@;jRd9Nzkj>6K^E)1sZGSAGq7Sw3WD0E*<*EnC z6b!D59&!SLtPuO)lbrJIuWas8R7w>wRq^p(%i*;SvRIAQC9^(?LEhB9`&2k9+&J1+ z^+_B=Db-T1TWUnx5LAdtwWLNESEw>qJ<)@POYSD^MBlLWI}7g5g0k;Yqkjdd&uIjiB}h>4PLLsJex5-#PX7%S*BCcLT_IQ`C$KshPI|n02a++$q=3V7=f>B`!v z>u|7JNjGqYd^7}&T8H8b3Y50+{i!-E<0k~B4$ZZhM5tRn(CEHnV7Il|g%^x8N8*?` zC=WAXh0-}A%+WewcO%0xc0gL8o4zVg;`~5%gk5P12HkRI&N1H z_=%C~Q^=hu7qQV4*R0KSBzTdOhxD$}A}6$ec2#Asew_(ch}t#PJlpKX8ZURmiA4hM zgNSZl-jIoI^uC!RQgt}tT=Q-Hh%^*0qHFhz1-k5fO;N0Us?PJNO9-u~b3;vc(o{pM z8uL1J6O2>zH?|&z!d5;s9e>&lI-{Vzogvv6M}UuuJI=KNE*TVE-QX!|cNjlRcQ3=D zsWo*TPCi@GEqmVR^CWct{+%&m*|$jSN^m<$#Ynnay$uyJd$#cPTOtR$`)XDp=TBjp zc;y#z#?z>#EyFv$CzQ7cjxsQmlTvx@o-)a30Mdu=kq@Rc2{2o*G6aH}EZ`K3M*cEmVB+SRTC0qKtTBjH z5sJnNW_q6|fQZMYZzS-8u`ySC=402P8c+Ozhb?bpIaRVLN5F@JRZfnq!N&l(HK)_R z7S*EW7s?C0t^9D+z<4OP)@Mv-Y5Ys$fTrDTbQg1W**5sMlbE)A98wiU9pHgR3~p!* zWD=tX9sNiX$`4K6h#r)9`h0%0T%j`^jlK-m&SW9PSW?GMf1T<=KPz6LXCwW7m+0x> zbJ3F8l0MCEm3*15s3awYMV<}wK1NTpl1B^7nwA^vG3T`mq6Bsv>E-AR&yd)aSMuOo z60aav(#;U1_Fo+H6;Ee%iYu7r)wv?3`J`wOrD`nTWDXpfJCxbP9V^4*-neRk)m~a7 z32`!eyLI*~DUb`jqec7fcTT?@@WH#*^;h{~F-{xq0{SSM(05q7yut5h3}r*IyR?2r zAbd`_PaeCcDSEMrom|$z>I1A0+M^@0^SWP(u5W`RvvZfD!=f^$f*o|_gPp(R%yUUf zM26HmUUXTMoLBG(SBaE-jbDElBE2?vW%0j$NU;Z{+)NWPfjy9qcN?PpHNc@ zw|2g%D%v_Nyj1sBNjr9T-_>vYs2zmxAcB z5@vG=#I@q%rw2dZm=rY5wh3vz4998Hk`=J?*oqqdKgo_S5`$;sH z1(&n)o?{0K)`*8}hyqB{JWhbb*p#i#TT!2_C=W7pD3;~u2!1*X@nrlX3iP1k{pxB^ za2RA2s^4~k+=KsatuMBKM-)KAPisp_(O=6ykP(gZ;2yJ|LwIiAZauJAuqk7ivx0s5 zhF8z6z3IG$;_A}UBTI0OAk@UDCv#9U@?E{qAdv(=&Rkv?O_>7C9v=g9#QPP)_Z{*# z5|sQ(^>C6nWt+_Xu5_ZSF4CO$#`4$q_uVe4TEkL*>bBB7V!C13WlS-j_T9;TyE?q3 zrKA$ciDHWj4|o3V4AQCT!4!ns^`~HnJ|gDwLEfOLz$QAD161dWoLDzM43q+laiZHGy^|`jf6F)5FyUWJQ+eyv>HfUZr2*Y21OJzv6LML(XlpLnC?YKcWWC&q^NZ~ zg`ViIAvllu>^&l?UpFwI2VhStr`dVVFtH!LP{CbJQ~SU++sK%w6ppVo^uFxDo^tGg zy+#~q{Ze;j3%R;IUk*Ak3(dpx(VsB~B8Gv&3pk;{ZZXf0pk!)br{MXiAgNT|4nrB6 z$WvWkfoE1XSedPS?dcZApJAm8r==~0D?B&Id)HCHbVVH6)Sd4| zpQV1QlHOLL>GLC4s@XNap*=8dpoH}ta9X6|?!Mupsln^s(Q+pxB@)nT=JMkOMUo>u z6yy~&g9!F3ha$2^Yv*XvT|jS^O%KHjFh!bSc4hs_kwPt|{CPL=w&&%tlddczWN@%8F_ZSN9K=9mjK8rVlT%3P9Gm(;xz4u8!lmT2OR-^@Yz3w(v)LB=n}d(0+rcgWV{cHg2T_)&M8sq z^wayJY*ALLxhXE*?mK0ZqGL|*gWeFNZZgUn%wM6@l&oE}@+Xv}!2T|jIR5=m`k$jH zML|{`hY=C)xHBMBL5kDiq%y47P?M0i*gsy2M5KfwzjUI2{rbvyf9J8*+AcVFC+tE0 zL8T(>)Bm$P(g9n<4vp_6W;@FxH)Gq+_Y=e)bk*?Y^6JXzYXru_IfUv7zT157(p@q2 zNXU_z>99;QddMny3@(x5a3ro2Q#XT{b!#_RI*3~@_l0uCXP1Y;`-=|VE4k_&;W0@_ ziuh;p`#YT|sSuH60kYw7IoyY+w_j`P`m<5CS1X(WY-UmQEK)911S~vL&bNr8h9Sa4 zVvec#N z-tqnggp0N(f8ig!d_ex62fF_RLfP5zPkrz&EV%r~hYDdU?K4XY;HvyWYANjwmT&~L zXoSQUk>K!vWDMp`?oId$b<6@)j~%vPdK9|&lYQ)6edxi|yU_Sh9Tzua^;`p7 z5gmHPb}j8V5gqtkp`*gWWY`1VZ$oVC6zZ(>ua~vw$cQd&4Y|cHu@4~%^KOurP-t`7 z!R#w+%bB6Ho^O9u1HSwxRMK6^wFV3zhb*|mHtH-i>TE5bGy6T=!cty3qC&LSp^{VY z4ux5wnSi78XZOLwo`qG`xDYh61b~FYAw#mOuRc7bHf)9gpEsLNpp*%Z9c~4F zymg6Z{w`JRVZyKr9H)%@KC~&zL6~9$5e9@X_@GV+S4AM@;Mneneu+Mta}sQXVeQ_D z!U$gID^!VU91D#BW1M!|l)~rpr>J^Ii-P9H2ZGORe>YF6eV)t?K*C9ISzZb+NNi6V z8{dF4!Mz*+e=pfI&{tkZCG43+7FZDfyG31h2K5Y%T{pmmi9*_mGsJKYS9Ex&6?mYW zEapj-i1=@)fHFh;C+i=}u-*1>fLc(q`{^CN!L6vL?7+CmH4E9%6~$FXnxymsP)^W(J) z>rYoB*YZhOBXBPHiuuw?ZF(c+R3UQN^B);8m?x9tYKS`ud`JEvy_n6jN)PeVHFlYE zO%{o1U^m4K)2q>Ye16}@Gr_)G9)otupyyhU5zZ2Xtbm=tv5^CgnRn^JEyU1H-ft#n z0JcUr{`DU`#E*|hl>HHH`fqKB|KH(3)z<3Y8T+LuU&^5TjXSzRXBERAGP?;Q14Hq90pd$`u%(j`9YnAZ^rhtK|1Q{% zhmW-!TmMwpL2w$iz7Zv&SjQPUV-;G(A>y*zqnlHvqsiBAx568TI|t8hsw%b97JxVp zhkJJggQZA&mnMB*(g=w1`EX*#6gK6pmX_vm;O(zRlp1v=Y2^iIB*Hr3?H=#YF%}cM7*nWnAvX!8VIu2r!Gfmf_RPq} zLBzJ>SH)5;TICpGJ^c77i{BZON0^4+5rD&Qh}iyx#f_pPoQDkzu=z}!0=u6itdc{B}g;f`caNC$UDnsWt zCuln0uVk4U9uf)_ZC1V)Q~KVtw(nHxxGR*DGm;)EN2^Gkn3o)j85$?OVzk1XR&qc< zZHr28kv@=vzcHd4+V6xsKUU#4)Dn@e`&-LJUE@~CS6Uc&0qa1q*ra-v+wer4Snb?b zbxghM4C?1R#r#7i%2=iDEM?WxVp9`oBW1kd!$PY_t7HfHI6NBLHkq%1!5gCRuy8bH z80I@Xi-WpDP0FTtYQm+Iug!3+Q5V`|i($n8lC7^*2)RQQ?S=~*&n?1-KPvTuIl$>h zK*BMc(qdn?n>fQ33qvUotf{h4;99|SgiSq^6osI~Z?L~Wm!3(@clrbQ=->7-e}5L> zpHTk`&oQdnZYZj#pYTvJ*oCw9%o-<{{iPNQkfNs{QADT}Hc9iSmgWt)KeNe%$(aGB z^HGopGf8Wmh@xW}+ z9Kh-BU_wvNu$q*GzDq<`kWt@u~H|>hp zOJ3bgU0w>q=@x>BA=JLVmOHb}G(AwTImfZ%!hRFgVv1#6vs3FZl}{#DtbHF^Trhrj zN0)DOfvk1n)cdq{mIv$|6aG+q6ym=9RL9iVz>|fUkB=eMC>-v6r^77WLOgj`$TQh1 z*S^}9%OSG!Ksj+ok5xyHDP+3OmKnF+s^@XSus;b4(KRXWe4{4%Ci9_Bb|JUdVoK|& zE5$Awu)U2@kw`*hdMeM*$aVjiG4)H`6xa~ z$uIcqxtTLTC=t&%n*DGDlr? z9OEjRvQ4&>PO8>rnoJ$7qr#4fl8Gs;5M}k*pO%$kr}i$n z<5{8wBprPf7e-;95)_IrvB&YcL@%S7h~JeaxMB433h3cTSMWG=jE-ClUZ0;tP(>GZ zduo{b>4Zp<)|)m5day>YBw~@>WHj2M<%Gp$lp7Yr2N>%QAuVX$l7WT_cIE=Vf#UvW&Zq4}cVp@R`mmz?4ZFv@$C#WKxY z{hqGr3+&Gm9n?6ioDJstDr=P&`_BCkQq7l;UzoSRz z>Wa-E&+`*g#$co-cm$T(fn1!P^Xn69!+t`SaG-{R!OR65z`)iTytL9jZO}cw7;v1D?N++s9~+iQ-rqCTaZNqe%~p3lyEuRO-M&%xx!)+XNz zGXla%{BNKC@It)kjMKV5K4=8_f9Zw)FhJw~7gK}{=kJa+HEx*kNU zzovnalS-b5zqa|e{noZy-F)BANArMX294eCUT!xJ)OWKhFCC;2X2P1iInb9c496K% z|5%~>+%o4do~`53;lp9<*+?^9?PdF6Ax`d0Rq3SBt`d=+inN39;!b}x2J4u!u)bUMt z=2CV=D#2;ILNnSbI=C&GoB9|u9NQ*o?S8;t2b;8!8^F`Az3cda;T$0FitD;U9}=4= z8?=$y{c>0oryNAf@QDgFabLldi*c*qafs_@=W~$nXM`P(M>NJfOIIF{#2SR8g3RwF z=;qiAC^ST#9^;@^U;_VkCJQFD=@s>K#*`ZzA00)!4+jgqu?`D7xF37=D>mJ|(Bwcn z<|QytQ3pL|1T!#aTRPn!UV;_JS)}>$iaMN_%^z|P*0&0s?^pGv5U#4+4qiak8A)%* z6BiV+DJI=lnn>mPS0C^hJJ{zSK6xFF}4~2TLN)w*n)`EfqRG@S(%C$2VW7;!Q6Smc_GlRHu~@^!d=T2bcr2)=2kY zOV+uAYr-+?-(-1W7PUi0#5Nn^O}vP5jxB(EnEbmC-O^625T;^b;e%@TsJfTZ3m4K( zp8Mm7ZoA^yN$}${Rk2fZ!>|LlRB_8@c;BwIC(cOUWmyuZY}M@xBX_MU1C{3p7?g%J zck{k~c}MvbL`DrDB>9~p?pK_^$S`?_kvf)Q=^3^}R{e4YyT&&R0K&QD+Bu1n=nBxt zd?qkBh~{?UA zYRC+x%^IeSjmtSPj3Q%bWzy^OS|l-Tq08vs>p+&Hw*?b#a}H42W6X+ zQ~Pta-E>aIbzU4jke7yuyN7QNJV7^b+(`29RssfGHy-`OLPUHQqQ_C0hm4pyNrV;C zQMeU9wj#^2No98tQCg;Jm*ifV_g)lL5O^5Gt-*FZ)o*Toni7^ke1~3bB28i@V$#N; za%H8p3ntID&R(21M%nH%F3cdOq%^8RC!z?zcXi2l3Vdm3V#+}3T9)B%9$63cXW6a8 z;*Q1`kI-e_ygPwA8Ynu5XH+ycOW0=0G*XMu=Fgw>FhoJjB`?DICwr?vM{!tcRCdb^R8xzw8OwkScyqh$ zZlhC_sm>_3rk3qlUog`qlQZ1vf1Df9+H|#1`Ln~-e|yUIUnW@=w$3Jwwg78-Q;R=$ z3IJzw6-R)piK7$1TG-sg$m+k8*)GafP77kFyf|4BjJCkQ1+*m;dkG>#$cW}S;spi+ z3xkt9p`&TdU8!K6>6j|k?yu5bM&6F}Rqim>X2=|FIqbLSggGrVrk7g?=e z-5~bJvZ1>pBbRS>a#;}aqH7N5Ir-;5IR#T6iqg+1q^%^Ek%Fa>U~0dCdFd<#=0fIH z3KnJ$)5WEvJG}vDcR#d}ox-06D&)L!9Zf!jecTX7zm?J6YfHNUt6lpLlGGVm;Cw?&a3Ypg;l|f zmZ%}|>7G`|%<$0=MblTU$J1wK*dCI@7P6pg{?qW{4{p_DDyD>NdMx2rdRiMM0)F1h zcn=zYYgT$BQ-yvItgw0Nv<&?SkN{O=@DBUn9DFX`chGwWU79aN_o=GizL!^Aye}Ul zdb_!A`VJ-}RaWs>7@?U-p{yq*ZM3&qCR|5Niww#ddRZ{bZnBJ_RHwdcR0x7F%5RL| zjQT!Bv(vy}pJ>{?Zdjk0H=0_>5wd`iJ{mVv{=>9|%s9xprJbC>%&vi4b+I9SrdgtL zrLZZfh7x5GcG~nTg#(=^Zf4v*K3t{lH+HzMtqrIdX4mO=%eC0#9MiQUS6kSu$$X3& z=&Z!UjvUEOd9{Xnu!6(45I|Sfc(7Gy2<_9I;=R+PP0c$GcKa2KNGonh0^dED)kf7G zF`cSCdK%UQ9G5ICcc!y@Eka^Tyqh~W`XQxtP{41)bbm?qLDjeSq#H(0ySDEan8fxq zy5BGG_6n>nsrhW%ms`K%VjBLYm+4&YN6IEj)jhWdp6*0G%CSre{3K|)i zI627zYyoEfCG1QTWaZZd5Ph_fWrUO@EM9@Hz=)%VM~T2xDufP2RGTFpy7c6;NzYxH zti1`?u-QxsJd7Oc%I;!vRrq2FmuQGU0nfR80zjEKR&L^Ju@U&O0mHq z2(iMq3aK72+Zb2C;2erdMRPNe@G^2H@F}4vDeE8d@nA~tTTo}4DAJiF)bdhzwC0g{ zRl5`&e>#}Jg8C8{uLEQbNy0TQI6N9t@1Ew4`{ThflKvCUy4MM1}#X?P=voBNNJY7BnugSynLr-9F(%yS;$^XaB2L6`e zYblcRtngQa@4HAHEWs?n=LmIxzPV2)pnkZJPmg(A-`X4F6XY-XJ(}Gn!v2T?`nRJx zzW-Bx7iSCW|IL89Bf#F?#8Je;$=T7u(B-crsp@DgY-jt&d7b}@_92S8b_)!syr!E> z9Y8@f`|n1(B|)XYBf=U;^uiJn!uhJ8k8_f?%673RoO1ez??k^Nd0mi`+(q}qiLzZl zS>o%BHJ@Lyy{;O1zh3Up`cSHwytlTu;rTt0;+`c)w?Z)xx??epHHk>riPe2m$&DRk z6Wt}B9EYU&@fEw&)ih6SVB2Xb_R%WxZU-=25F&GVWeZ&};SZnfj-n&hdlZ%`WrD8J=}00D25fYhSayZo7LzC@^hf_I#uZt=E~q z@+U8wdr#@m9ootCIbFE0rurLMUr4VpP@@XMw;gPBs%2Q!v97-OXKw{ovdnkE2dl?z zVBc*onFVz>1}-z5!wMQKdRrSJH95-DrUE=Vv95kqMuD4NoZJI@eg8p?VHtR2{~tMv{+1g0|C1WGe{w=EWM}962Ni(5 zEWpB6*%|N`BqWUgT30Df+M);|RujdsjtJnB1pKKjV!QM*>=~dYh$ONDKG5YV zxc0Nf#qC>TKkrvlS=msWOpuwR{J4pQOEROJ9wYx1{R5_#!pED3bibWoMN~;C=Y%YX zJdWTn>HA#AoZXi*AR5tEY`i{URY=>k%!cS9F(*xLAP!tSNHOuA$#*+Y$vmzqWb!40 zt$MafCjmXyaqv`I++~W6x2kd{NGCU`WnX}<-isl+zP6sVjI<=wI-w{1CnpRN8`_h4 zb?rRag3>SJC~_fbzgw%{~{!#&z!3B|qoaf)Q@!O7?>u zHLea^2BwyVW{Na;m>sy2BdzrZ^KuI5K&+685@Ln=>iH*qQxNy6l@Z1}^trrA8K$9d zsB#;fpNFhMY07==MhjTV&d4st#_w;{<=Sl@RbA9~to`M^KR&P^2-HEE3yW`1{?a^K zbUriok2d&!tGVERuepewo2|7Sz?ekD#1!CS?fn1WD>&MjIsVBLGJnj&#P+|AL6sHl z7SvEb=`7Bcous93C{*H?ifEu@g(;~E0)VN7rJ&9i^9v=CR%3kj+gvU#*5ncpyDnnF zcyB=vZpN7BwW!GYbJ;B3CTKsaLRF!47#Th-c) zTH2Z^z1MY^YFaq0v;TmjkJlTH6$vkH=DPx4V$WfSbhiu4^~d7dDD*P4)2ouL=h&~| z0~k+Y2vbwr&O8GPuqDbaBy7D3yGT9$KTh5;NV8}87Vc?V)3$B9r)~Rb+qS1|+qS0d zX+Le-w(aisch392U(UU8Zp8gmk+mygSM016J9E{_tfMq}mHu<+{i8KbAH|lydv^V? zmBpbLdY}VWydP6}07S$r=-G31|F4Rktt~X|9McHX2}CR@*?{UN82F<};~104JTxAG z5;3L#_(xBQVxV`>6zcwfJMUyD#Tk;;!ZwDFpfww(@I#M}U%viv<=d zQ}C`~F|*yTVal;_944m1E)jUGcLZ(HJtaH_cMHURGs$BOb=dQuv`k5Y&{=omj;W&9 z&?3>r!&dYrb=n2^gPV$2<8*b$WzL(`^T%8Nm^Kt@PxfqsnYWFoA*-y&k!9G&XJ$1p zPc#>wSdxL=?+dK~Iynh3ADVYnKLr9SagITx`Wt$sO&JGnq5D+Ia1%sReDZ9P#E&}( z#WHlOfZY+^LBNHCoraI^Ix4>_Bb!ejQ+sPLEVOL&f6g43U=U&M>ocP&rW6pmxl1@G+TVEn5mSE!d@08+d}LvakETy z!0%13Pz@0b?FTSw@;Z0Fchpw)k1d|8t?``}$!&V&5yFcC{2bgzII z;so3K&F{*ODWGC9Pr232@XCmy$h(zia_B)s6`C_cPEe>lT`oy5LGF6*0P%>GHR@Df zX{^-U;fMl7U}&US|IZ4Yz;L@p`zaI>bKwqDWpH=JNanKNfCDpUcM#lk`80L*ub-GH z&=LGmRS~*<03+N7wbWe}bbe`8Zs{-h$4()E&$v-GWq+`L0rdAgIV3e02*|;IjpW$= zF97`qksh8xt}Yg)b}p92hA#I18zxoPmFE@FbfMx?k@Jc6J~0&M=c05Ll)Hpb7v-Wjy`kPf@6+hQS<6%WJr{wTh`ajQv$TFkMHN4 zAp4pI9-V)4WqOuiH#~^(RK_jDm7=UewJQJ|&-BYusyZr)If z+a}qB_1tjlMdiZ&Dj3=mVpx?VGsD0?EZy4r)vFv3T`UUG5XfaF+`^LM)JiU5l5uTR zx$^R4^Drva7WVxH@M9va6bQ`rtAWI)W6G6e?Uqpsl?RKJ@fFEI!HA&+$37utc)Mi)ryO_``^kH;5Sof4dziQadq+6^dwO2iJdHAJo;pD6 zp}slRjJDIPX{Z~2N40Q3B~dT|x}l5fF31~JY-(`NAiw=$iWwNYZIYp89K+XQGiP`@ z=>jpO-cqncAYqM~C8(FN;e4ogX8b%2q7l=KOSqwlAUZLg8P}g=?;Emd2frRN3CC(U z35PBzx}4Mf4U!O?A?4s3N=F)G*GwVXiC~A+0$!|i7vE(E+t9vEqb%N%1rb39SavL6 zI4Hs~7GkTJxpk^7EYB(&tjoj9C!7vRFmm$EFk@x%?9W1|}*Yc7>91g6BNq;&fiNwuqdJ8M;ra}?)&{^M@l{_l17 zwa513-{+NkBmsB>{wWqbZjV1@x4(vdqv9U{stYSfcr=ytQ5KV^*$Xr)Yax=nqJM7g zz|^K^V}zlxV&U)28nWiF(Pl!X1dpE6D&HC?OF^g+2O-&PVd1X)@{R%GeH-v35Cw7< zgD*$W19TR-I92K87f|fdi=$3r3J?l_2GMb<8y7!}Z$clNP!4$)z|A!)aBoA~3IOrYsqNTo<|UegYX`~Nu%w@9Nu@=EXZH}65igFFWQ1Wvq=e&!+l4;xA;4W$q9zBk zSsvQ@1$y^Y2la;*wpRzgQR&34{xrxFNBM^<+r5(#NslC6ax_WEislda%?U% z)N4(u`%C3^@XwV}wPo~>OF#!h)#VXdb&uTN9cO$n#~7K`cO#u>&Lb##9C1{A3JmSS zi>lUeB95+dJxJsA{ZXb==9+qvEx}P(d^(NMw@j2^5(=QT20CAPk0@EY?!} zR(opnc2ueI4D@+J>!~G4AlQsL(U|#xeS-eF!sJ1s8OuY%bn9p5zHRW<7)7v)@C=g_ zjMJl2;~N-6lXSl)cno@mD^{XJL zD2%?CD&HH!h!-nEi!Z@MKtD(5B3#D)!8{^ki*Xr}9vuc?-A6MLZcu}FfIN9U$coR_ zAyI(iFS{**l3j&T=l07Cb>eIc18iFb{GEVEGy7Uuv9jgKx#jpPZ5YeTog)lCd=wRh z;hBmQtaM$tDwZ!_{GDN|h2-z{3W{}>QY^60$`)`g!E|xWJ6Dn`X;UD(tuGBpB$qu{~+1;Jm%yJ#U zMg(0640-K%^p@->KZm}}7_ZQlMI#L6jNtn~D_DB8%wDNA?T6Nb!&_A_%k6YM_IU?^ z1AE6#WxazDYfVC%VeUK-PYjT)_)bVy6j?7ZmYHH$>S$wl9gJI1-d$~A*T!wG&r7+r zEwwpLc~a7cy4~HI4VV6&ZXhN(R|ZvDaXG5}VdWSWZW+#nU4U<malgR0_pjZ99*c9v*tHijp=dg98uUkLnq9YktHqO7PD zHB0#_0U~f{wN_)9cB5uIDTP@%4BUk=^D4rBOM8-FuO-^GW;lqiQ{R~JNh-+-ExkS+ zW*u69QZ$c*yyW8L$EOE5d8SaYichLN&2(h5yymdamW zm?PyG+)wr|os!Z1GkBZQ5%gPB4;<&xlu&K6v{#?uBnjL)nhNo)_WehPfP8yn_d1%8diaUarM3>{M6mRQ*C#DkQ zb7a1a6?KC%qSeHqyooxf6Z9ZN1RL*^2UbBB^7;xkH^4gZ@7w3~YFgiWT(3k!%bQ?& zpTsAjHRcEYmx8QOzd~+Sv}mTzR|P!5t$)W0@kU->k(}K(c7Xqum`Cx-cL);-n4S2z zGNb~sS|oTBXZxrA_)Zq>#=FHUOhf#F{R+OrYY)9g-q^9JRw_Nma3$WrY-8N=@)898 zKI_YF@Xsdm?0owEA9u6VAK1aQekeCeR;^;-dBu8EJ1QP)q0>8B zxIABsWq}1Wr5`kfy%_u4ZJNxpV>c=`6Ob=VU2Y8<3=MK^-^bj~t#B7<+|?omJyV7` zpzpSb=cp^!(;;&C{@FsCB?P;0s6DL}|LoporuxIh#63+S{7S+S^;Z zI{arA^uILFw3~kUno5eQCTdhL>oI#GVn%_=I|Y#QfS-Apr&pJ5r+%hxt)c(wY5`{) zDE>bZXp88*AVyZ_>1^lgcK4(8=V|>P8^e*ko%{Q4`!QQ-OW~l}+Y2=Te`~c12-|aI zH<*|{W+;H@o@B;*;|0+O){;w9@}HX9c#@2zuKZ5$`r@HkhxuOvP;UM~?UlDOf|C4k z0UlH_-q_?08^0yn0|rjgRVSLU>=!uC5C%%?ztn?}sa*S2wVKYo$o}Ba47(&Z@w4dD zVv7||bW5$oNL_{rH1|6si$fehm?0f%X6C}<-RJvDsw?+4_4dmVfcpy!Y|KS};tZ{Xu}NE!xhUt5z2c`^Q^}7o?j-(NEjVb0ZO=Q9G$?Uf8eP*W4;*h(?#DZ7ci@Qb z#bEl9d7o!3Ym2nbm=(KPsLqR3A3%sryh)~E(s(z&CEplJ9!IyJ!f-@vg}76At-l|~ zLwgB=nVND>N(x0C>)JlFKv(er#pp3OT9lYjugd%55O}_r)s_A0`#0}(Km^_JIhvE; zzwbnZoH1cm|8aA}f2F!;{@daCA5#2Z6z6}Y09r6UxQ8kJ_H1*}z%fHwc@p8kJu=Z) z`mEr1kv?R7sq7K?KR*MVkb!e67zu3R5~%YSElo`!3~;($Ar1T|h8-m#bD|t=$kN2W zWEj_kPd6{+6ucT-(k{NU5^Tm(Mc&pw5~Gjax4gGI0`|9Veb)QQW z!z!?&vd(pjaVIe_{EORdjn4qmOtq0AGRpdCHo-rFeD&K&=(0AI-HDb{gI3nU(0O`VZW?P7fzX;ZthPAp0e~!VVEED8?S`%MmAmmM_bS%X0{@q`Fs4OQ`bUWcKI|e>yQ3MB4;c*e5xNqk^}5KoWr+CriLhn~ z5>Sm=(TE*azCcZuVZQ^t+RbK+ex2f<0Xn&K$=0GS|4N@Cft<8hgmcWU%Y^j0P*2qps59eQQ^zvJO` zkMi-!&&SQeZzgnB%Y;aM7Z}^HxvJnfnuM48F}yki(}H2$kP!OwXa-c2ktHdpPD=;C zTV8llAW@Q+5BJ2?OW4%cR0CmXSwu3b6X0P*s$7CCTf`{RVkmJ2Zs8lYnV}brO+GpD zdv;Jn{2Hh8@R>f=>nwR{r}K&leuLjiby<&PL^YQhyFoSgIDZ z3VHrSYdOvM5neUJ8^355)GqK30!bC#QHBFc zYPV(o3B#KaG=l%Y5Mx*7Z+-_cHn1Y_ZWm)$@Ei_6%LTgrIPk^k&e@u7x_S&W^o*J{ zFTHfQ8hylgLhp#OwBDhUw>GxVgk_d%hw!^vIstni94fY0ttGWCe$-3Y5Ev{54rwpR z!c|bvmKXg&eDn#vs>ny0mxxl8enNlaYQSELV%XPQg0&C6V%pRe%F9oXIAO2>$@74Tb&DW{5+ta^rl!B@a*Dd-ZwB#Vf^P_%S*8c>cm-AnW=nE zs&wa^V0i=T2i&V<0SXGpapip&8%{}BL5hLH_o5usx#}CAUZNMq5c__C1O%t63v3B- z()lFXMkmjV5{I^b00HKVzUUFV3i;d>?=b33N8uVcopV%8S#m?Ab>Ph$n$uWrh_)v14hfQ||q=(>uPGlEb?$8g!I)U-i)Wb3%7&5kt5W zqPta;x#-6b--Af!;3srN$8QuqDr7(93nY5K%&7Fs(Gdg6;2>dw zU_)v5X@Ovb_!0RVYjg;~56k*;J*3Z+EA-6QtP7(AtmmFSDbPkSx85;A zrUStf|KOA_OqR?FhQ=98kCW(`8!+U+BY;cfk%ht#A6-Q5-Ve9rmSXi&)fKH19bi^v z1%#oIc~Tg;IkWp86T10N?|$T1Oda2eWxa6oqu?IvGZ zRb-4a_3!?Bwpxt#%q&$sCJ}kG`hF~;Gb}Lhis*tq=Jc#Jb&R=(Xwx3Dw0l&bg(I(V zQ@VzH6|C`~!*2YMC}@a@69jq+UPe;dJ#0)Gb7|CSkR>VDH1;)A8HT@-13%slfDR0(JYK_1NwX43Mfl#zPF4^pa%vu&_6VPcjaLT7)XPLoDzremyDzbQd~j_Q!$hDE(!E)@wK#shK1dUX|lM`nlv^H>@| z1dU!E7vd3MM<`qlSrI3^3jc(-BK;M*Ui-U8c&*rpKF_a|(hqP6}^BkSB}D}yBe4QY*BS%GX&nO61WwSjLC zKMM~V;2<(f8|!~f635vZZK6};37ur}x<=-`1N0x*s=Gn5<2bJB- z^Ru#T|C?BM(2aAwTQ!54%FS-`^c*Gf6fxI0eRTI5`2L!o?h{9hxEmfOI%jP(!N_hZ ziN+wf7Kc5XZBf*Q`Ij+qa;yQ6ky&)XiV#y2{dOGq{yd~)EYmQTTm+y`*8#N7qarzM zAh=AhMvW0Q8{)~Nd&%yKdZy7ys2M(xH!@ccvd@RP4=06EC&IdWc*NH^H;!7cV*Xwx zjW_BrJp*MO0W1k?O<87}OvqV-9u~LQHdVmeh1GN!gW~7<>tP?ns!~MtsqS(nPcaYq zIua6%IW;UhwQv$Qjc3|!j8^}&UwDB8D!SfO!{-fQnR3c_P-nX&0qqZ3^HCy(#DZ3{ z8nuC|Dpx@-W`0>rSY7#kI#G5&77eGOJWVT_ss}lee|C^-BHq08R3oB2vO;MCq3vAz@QJ zVaqb`f*tn?#rsmpqur4%bHsE{qS|`($O7%`bU|eX*{H#?L4Sx?i>HM0;=C~!n;-s> zqh&fAaZ>=O?=Y}SV9w)zWB~XJ*riOLGe<~PW~4d3g0M1u9y8-vhb?3GOU!GCvDcl; z7f6OL3CY6s3+*XR=+_T*W+N*MQv*KiN)yByDn8#VUu1u|a+%=y`rvd2W{3yfW_QPq z?svgNZnmppa62d$-Pp)sp?+#!rSdncug5OfA`@P_w1YC6{b z?&wsao-DSNIq1z>XxfpHQ2nwzGZ0#qN z{}pO^t0jc~g`drqMF1-zogi&SLjNT6jPxN9<3G*FYWwE@QqhkkM^mF6v=p4dT#Jn9 z6j2ayj+bp`8@@YXCHXz(KmWq`@thc8^b9f)+7Mnblp%!$%pW0HhjKiIb1kQgsYq?`MNd+AIH(d*`YFrAWADHq}R z4O(tf+t1aD#nA_{ABPI&M-bEFU7WGU-piD7ebm=ZezxYua6sU7h<9if!CKmm=7>@! zlSEw8-Y%frGoj7^Sws#d(uT>6)0%|V%B9Xrkh^GUjAlu7X+R5=`wbQZ5_o>@t*0Aq z2CiE@VlG~6Z*5%0l|_yx$&~pYMox@Ds-bS%#~Uk+mfZlv1x-EZ<%N|S7XoXAgr91t z9y<2S^itM_%zIgu6_TzLifc|l$tDzI{9XuNVqfFVZs~qkUkK_|eRibueQ3?oKoTor zlBQCK4Z~DDwqV!+)51h;C9b zD9A(vO}7~4Q%!6ed$~f_(dwJ{cH-)q@g17oak|MOAqgD#kVn7 z(;`>ht>zYo&e7n^P+5SST0Yrg2pE`by6q-)30w3Y`6UNrLo{{a+{(9cC3+;!dAC)8 zl8@@#_iU63i=KewvXTUcTy^!uql(+MJEKi1-8*DkBUmJr#Vuy5NSCEU%X{eN#W0OU z^zGKY?WalyHI3X32@8eX+J^7YVGI3(-hf&Z9;!UD%~$9}F_PFyi#Q$AhXZOoHTT4c zy)#$M$?(PQp%gsz*vJ&gMP!hwEr&!qyppWplgos7KJix3K+2OAzD;i7Yy5(JHjdGGF=IcUIZB02j&8`H1}w$2{c}Oe5t(tQb&@{8)rM zPmJ@pgVXx`1F%fcL%v-V#KY55K410&JOO9IryjdY&hXE6InTExo*$YBGnh6rR%Jdh zkLMA5z6wP65OYX$WIewUDS!D!T7%jw2R-a}S`h#K9tU+pP#MDdq+<~l6hsHdArhe} zeyZ?%TXf8%w20Ui+d6`TYZ9^haoCc-wdWi=D}$2vuDEC~apLaj#jW6v;;kTZ!uR>f z6n{#g)eZQJ&ih4##O0C97Ce7!qnXS$(@G>6G^I}E*6sO z@wD|*hY|xPRwWJzaRtpuU96q}rk##JeVh{}?~ne{7-0!S|K-im$h{X+P&qb*aWtoR zSC?flBfsf)m+Xg6y!NwKyKgeD!;)nsMgc@ZcJFOFksw5OmvM5Z*-DOa4VREuuo)AH z-g#G-b+ETMHz@XC;Ivjl|2wH}eEk`ZsEQKLhXvMd)qORgJ0CM}R7iV|m$gNSgx3>J@8^+q zf6Qk+KFT9f3mj6fsc7b0`#f(p*+%;l)CZoYJ?`vicz)F!iup&n@QcYHALZY|eyPvR zbH?3S4-{YCQ0orx5x!Qy0r~HHV-4Njljk3DI=TjL@GXR#ehlz@r3^EjuNN+PlB+?J z1((;f2A5Y5CN)VOhpQ^(r5%a#D9mrXNgN_2sb6c(bIZzKRSU{>4i}G&1TYa*-YL75 zeL&O4a-DgLmxy^<-i99#rljs4{MG=Zq~ZM0MuFk&niJ#Kvt(al?28OcQR9zpmGi!z zGtEQyYD{(KIs<9(Tr6LpQ00j6yy8(?V2<8TY0(6}TI!wMNb?4#Y?9OHqtjVg<4u;~ z-%01V$v;T*QW^~Z*kA>@Kt|R(jZ`C@yHZKws)ZWXz~Ig$8C4r{g~LvNSD58%f@@T3 zb&1EAb%K=)J9s}#8f#2sh#S*y%AFoz3@@viYo19Vb*gb;*PL#yeOz)*s#^4F>1b(# z=i}8eJogN-?s?=pV5vSvsLe_qzwH`z&4Ta`JD0yzWzcBRo|ZRFd%RFLT58m9SK^x; z6us34yZnQb6+<@5;4~wUIQnbgCnY9A!DTxN!gS}%$%Y6{LFydksL1myT}PhT{IUfi zzl5_^k#ub#&FTC>3PGvo9Cc+XLD_5UP*d-2Sx7AUf%C$gXRK?}$gK1+uiLEAFbKV4 z@d@a#_UN?-6uqlYk`Nspo{P9eP6M9WD7+U;!g}R9zvqIO7c9b=oE31GXkfzvywQq^Ykxl7lA*&B?AnZi-3w ztSdD$8@pq|Gnm2sZx{#fb&;6{d2`#6a@D!Km2-F8n(p*GMoCNyP3s)TA?-pzm*jd% zS^@3E`AnKBfEFeKZ75|b(`sDu_2LbR*2~Sr4JYSDyDm{tQ8WnfW-uki ziW9VST>ur+uT6vBh2?zNUEqCCh2`bHvyOeFI-16h-rh@&bFbg0-hGalv(8zC8tvB%%xk3tyXn#DZu zXHt{1#*Y1hYqapu)K%sz^>qNaG)Aim{i9}5dWCpjmemM);?Kp^o1He2MD&}0_Vp$E z#oLkQ#w=-qXB65244Umy>)9g;|B|l1-mw|!0o4d*I<>~nSnLWUt$M}&mY8&vZBY|1 z^FhUY)LnIc7Dl6xhY`p|PI6nLC^FU>FQ5Vj)d8y^>s2*}ZNMD7Sym3r(}EW-i!>T* z_S>U%Rz!JvT*3lFBd?)M94wv1sG+1cEQcu}`I}GKaP+jcB=DF-T6_9fJ)L=;TdlVi zr^=myu4t=Xlb|&gj^U;diyY!6e*z96OsFVk^M(ka>0J}q{0XNa0B#a)5a zb&|{Z*2vV#GNqS{#m&@wBa#fyIOgiXqn^if6C3nULIDPXun4XqOjtad#fkXP#C?&O z_2gU25q{6pa%C_i)Mq)mkv%)a8C;%Eo&@4Jv85u&TuaHR*E2i5Mw7Kvw#hb*S6sCZ zaF=a3-_3xl=8a%^8ebbqK#s6v==)>M;m77J!)7#@&A!fE(3TvvN+PWxgJ^t-ZN79u zP(h$vvP*XtazUhGDs9R?o-Ys?7aKP~RK!Wka=pW(Zs#XX?HRlPJf8z6Av$P=f1uA ze*AvyOl#mq>0G$5-97SevLRrPhQ1zVp!hJXv@AY5zDmxO0#rqf;}#jEBd_Pvbf!p) z9?p7;9hG|A5{ajeid$lXoSyh zqRDxEQ{p@sFp@K>l{mmnn&|QM0WbQJKywubBj?9dHcFp&ETt1EXi;D^2IDRMqQkK0mp0~cs+Q_7Ocy+=`l~s z+4IwI;XLS!iH;^t8<7x_quVAlzl(L2jkSF%h;Ldq=;0}K7rH${x^GzY$a88ubfe9{ z(Cbth57XB%u#Xg1xp&*dL6lDsx+;{OUwliSi)okJ8wsIXP?bYK^; z@bIs5J}ug6^yRL*Qz<17?uE+07B6M)zxR}aL#j_OH&BoBs6G=9Ys)Z3XXG=x~tVu9!gSL_wrXwj#OtJ~O zS@uIk{%|ICZN44R^OLWX0~--4D7h zf6sbCxb#0Grk}sh+Qa@OkE(W(bB;pF2H9sz!E^Q;jhu zsP4~)i=^pZ0_jPo%SFu!IcioSFlyzEa82VKC?#v_=>wghJCa#=5 zQxCMoq2rRVLf$BQYYBbjtemSn?bZW4X|NQ@cth~B2a&y>1K@|lz|O*x&s2Of1@U41 z&kp*llhB}?MxdX-Ihluyi#L(|_=0_4yIm8KB2Q9&^f?%^S(XR$$;<|GO|*omuC)h{Yy z`IiPnar=O~PQ$KdxNEZ8=R|*s+dRKWtlY#@uMcvu zR1F%@tTJXn>btQ!-nG!+M6{4(8Y!iXIC>kO^KdGJHw(kFcq+7U=M)XE7tE~aWzyQ!z1xwOp46-2|*o}atA8506*;*4tanxUiN;znvpE!n1HNBg=SJe@V8B-D_1mnIWmfrx4|OK77fw zVPP+b4H^bC%6T-uxL{t(P*bjOC^8?scd`uyFzkV}o5*Y0yL$IRgCsFd0)omR0~O4NjZHMsQ`{> zi^;bxB+nu6VeM=FD0y0(u*t+TLqm7%6w!_m=wv%erMGeSs@KgvKMR-z{}?;3y?6=4Za3PBi) zvC}ho^wtHt+Kqj%f|aAAX*37!(sAKM-}8)b@=4?M4vsA@!cl1CF)w-mCw|UI$oNLh zQU1%DmEcLzR{wnWXsYXF zhwEkT=90Sy zP75U<^kG73V64TZ)g)AVO`FBdZ(`y!*HU}5D~Fxw8jD-R9f2^$6Jvc$D`XA+(+h1&!t4{Y0>z?P-8H7|A^oNeSTx^F6qi@}yR?>Y4HV!$L5 zoS*yoM%~R?rcIjj9wv{4%_!+A>&oqIGU$-5USf5jP%(rrTTt!0Hn!@OYAHt#)IBu) zQ_D7G;smkGSMqQ+Y68E!S*6{aTXM`3*0++;$V%zBJGFUl7k1O(Ej#|; z#=?)<8S=Mv;m^|Ek4@Z5M_p3E1+ThecD}l-Ah2L;Q(Z5LMA31uZx5<_k0uY;h#IncDI?I3l|oh zLr1QrYm`jr+p$=B`tt6&rKOtMDRk`Zq_VB^_(maADs68oM9sa#KB> zSfu*$WWqbFB8caZZ>8$AG?`#{HsyuLcFv-)&A#CDiUps_kayp3O?l3VwrQ#Hw~I3! zJ1>{ph;7TP+v@gYlSgT+P)8WGMMvi$iQ$@54cw_-N8D*WRPtz%78Cc8q7VbV?)F)2 zoSE%F44?&bawWAaOI@`U*rz@>%C-@Ea>Z(lg|%94E)gb2i_1QBg3ZWDu*o&axrI`g zi(Q~|=N|vI6KS|Udj9A1X+`H3UX9~GyN=AB@*gjGcT^iXqxq;{ID>9e*(4SEol;k8Q13ze_k`6ip(C z(~)BM76n(pUt9L*S?hIzg@xfK;d&tj_jvRrTlhJpm{^#jHM6zBx%)&MJ}D-4hmZ1X zmu$=FEK3$XzSs`*KHDYf(zCliqgbk_@J4e2I~@Dmi7Z^W9;upEa6lI~?u%CQM6c zKJvwn!Kn~9x!;rkszWct=~In_Y*Wck6G9g*y8dD&L6T{@nP9Rsz@I zPNMr=gjg;Z-pa9%`IDit{JN5}^9%u?7Z335ZnZnu$^`=t)smVrf}?zI@x5ykT+LJh_E`Qv893Ak;&ituaH^K22a!hw$5RmpUThbFU1dJ+wh(#y!~y5M!B889J4 zwAzIoV2fjhut633W9vl&u{jdS-tv7ZbjOu{>qHnZHr0Hy2IOmb*Unl1DIRg97l|>_ z^Yfk!b46}d8=%ayxzSL06I#%-3I%1Ir;|Yt548PXRU;eDOQl`jY4`&Tkzb zeAJ!spX?A~FwHvYehGe3$Zr}a^KTcz3{_SwM&Y3+_GDlr1Ru(OMIPK`qj^4&+RnSC zGl+a;)=<33WVcg2-YDP9P1b`v9HdplPV{LH`+XyqVW4gUsb zMAiW*4v1xDlO{$4@#(Bg)C~_hp|HEPgLC3HVD#X>NSAFLHnU`P#ySeYThiPYYm{E= z>b6&t+9!8FWvtp#i0bi|9W*aP z7}0jpaM{62oQQdX{ue~*a{nt!mg+pQT@6`2w{TDwL7~n7Z#YeC?(whohuu*odl~L3 zCI{SM_E<;I(twZo7Uw(U$VlB^dIw)jl^0$&xc6Oh9xVeIq-XF*&+;h%s;vMF*r?|T4{PEk_#b=nAcT{Zy?@!* zc{9??c1K$s;(*VNj2!8;4{ot4SR$_o_Y5Y|)fb5IAKKEvNi(F;T+r&ChB!;vf@u@6 z;jW$0hTZY^H2YwUE&CsSTcmDy>95aG-!=I}*J6`ZO%xud9uTeeXqfD($6e-e9>#)% zGaiIJ=ovO0&s;T~O7}-Kyuy6tv$zkSE+nE5AurG@8AZ9`VHZqgB`tNPO<)L%?LzE= z!~&2DDcT)ZZ zB1SXf=rsm+W~inIa{hTaVk|m|EcC|u#DXSVVWMcZqH56n>jGBsJ>NT3LSJu17U>uP zkUxTkn!vg5!Nt$}uAy|%6$rD6fmKa({7h1lx3o5P4nzIcw>xmmfA2 zV7lqyn5R>uVgUbuCTeL!*%{vm5?fiME@9W7oPW`Kq_gHLq&nW$hZ;c4#i8%D4oi zyeUrxc0fTtFsoJ}kN?CB`7+KXK%BTP8Tr}rD3$9t*`ib0wI<(u+xfdzUo zR)4^oQ5t+M@1)reWOMdvC}5AJ#sI=xZ#OX4^ohSqfV^>#gs0w)UakZVxqi9s=f__t|v4?Z|gudCRktQ>kYYgH*K4`JQ)o^b+Ffdq=3IdIL`BIcUS#vo3y&M;j8!h ziN`CJ#-P~yyw?sf^XclK=Ti@_!zN>jYfmpR?5uGr&C`u(g%R~&*|+K1x5@4Efdj^p zCv*LCl*zY2vWHnrU$OUf&l5nB_O?m-&l%tnl2}jDI34eQO= z1^G?q@0*;e`ti6n7-LT{Z%Wx6`~8!1iyL`Zs!rdYaZX<^G4DKF5#e^Nimpj^79CBk zDFlEXGpjDvIxjEgK2IQ~(xg8dF1pIg#rB|6z7{j@bd85e-J5O zwb-A!ks^-sE6eEf`ZVR7D{3c?$HabAeOoBF{3j+zeq#HJlQ&&T*V=hN zHMOjJJRnk}H$gf|kzNErdVtWI(jo?=cLJfKfOL>vrI%2oR}qljgMfm9NRcj0lqO9C zy?D-j?<5=!a_@VyR#w*DS?m8>v-j-zGBbO=zpP-DArrH%)1nVrTO$zYoH=X)HMo%*2K=6Ar-2b_aU~7cf>`LxzU(}0%aL38%F7xN}K8J8@wJB6gzNf*A zmX%H%Ay28>{4B*Z`X!U0R;8i*E%geEguu(-a@MPD3Je~}^wx8=vj^m(jD$fX#zn&Y z9@{<4rt4f6s#HH?Hr(92R&kvKT}WUP=E?GoJUB)8UX`w-#+E17UsciL`gU6VtC2L=&=z55pr0;q5kHd$9;A)M7Mc$2cqa(};9L<^Sj~`W6;tUc;7{B3r-K*W*5uCi#_+-9!6vwjz zm#T9!8WKyqX{sp~^p0tjSE*59dMXhrYxAJ*rq~!iO@jX?bvmI>_e$`Umkzz>>S;n! z-i~UJOuKq!4{q8;(rHpwQXrI9@2xWD01*dEx3PVlxfGBeh0cH))qLWY}@Xu_K{BsSVqZk_v%3!P7C8Iw-qHhV=^9?YKl+Nnf7Qw|sh=TYlP{d1?*`Nmw|}9S!o#LgPyQ|}(YnAZ+;~f% zpO2L3^Nshwv{uho2T5$P2DkjF!IT|hmoJ(hU<-W2sD5&(&Kt8YN!0|Ww$9X}hQ?Pu z(WX$q44jh_;2G8%Xx-A4c(K1YI^(Kt7RPi0dg_I-^mN}KYF!yGPWtP8!5{WL%?uX< ze>!=QkZJpAuUQAW!COregeLnsi#7LkW=Cf-tcfV+S&LKpmy82iZY0gTnQ8HW%u_2+ zO#kfETElwUBCUV7->|0mrHf!(*Lso!=ztnpRs;JEsq+)nH-dBYy9UhD&NjJq@z9Wg zm-xEuD-?|O8b;ZJzG0e+j0Utnc{0(n=O<%fXjE~(EaJIAI3Ze2uiNq;{?Gcm&+SFg z0RX!HBz^h+G{|4+6@gApW_HH5e`}Z8Q3FaHQbgSrsy{pRK$%iwnjES!rNPyq41TP@ z+sj_hl36C8H;sf!u__7Y?zxkQ{gD%sSX>zQ>*mpJ>fPP1t49|AYKBp5edbQ|;+xS7inM75=MzWo~9lk9GuA{67ZK8aS*Vf-391mUdt ziegVoB7Q6`ag^fJsbJ)Y7~e4?D&=Q;Bd8~DPaG9AZ$!paw>TidiZ!-<4bB8#!8vv^{%t>`OF#vIiX9W~zE##TVZc?$6>WW9;m?zY z7+S<5_Xd76{0R4bzdBGqBa?r@Zq#V7E}N$nJH>R>*x7URX|wX0&+s)nx)$Kxj*x~w z*GBUnJ;EopI0>fXD`tJ>gc$GXH$coNx=we`uAHAq4j4b`AAh! z7)f{#pBL@OEE~zyy~@z9$3HXkb1jK*T5r%vtMs( zCY7-H8}%W@h}WI*rxc0irTzWicD>f^GpB$4SkMI|4jPVnZkfKQiz#xj(XM+p?A%+F zx5d_HQ)3&9RkK8;TF#E0*iQU`g2~olx`L`*Z)ZYPQc!XVP-(i4g~Vw&Q~1 zbAC{LRh`Lw8&Lu@Z+~ivgwT7n`i!|>MsV~=-{yk@C|L7Z!sN6|^1Q?S_?FGexwv3M zt{5^SaXZrbTSeC{t;W{sNnh@bI+7BwSB@g^e zG72%O=JxZ~!CM;oDsDHyQDAr@`HP8YMS45U9yM;GV28sYQ323`vfjGEb%U=%O2mmL zQaExYWEMZjy+s|rBiH)-L|62uBVjq-qIqF8-n3Dtro_acIFasz{7r9l=Gu1zy}o3P zg|h;AF1+6iNbFY>X6%AO<~P|p3dQpzzGtk73KzFCUBTo+d3DsZ0ATR zYKmUIm>e<$jgry^bNZ?^I_XNs0^OLe2WLSgXDlOr3fw(l_NTK8^?f#Oo*PWNxqWs* z6{*I%G0b|OVh0^8g5h3y`t}oQ+NE>idgXW7#FHWR*g}QN3Uj75iHk3(DtGnB?*CL) ze(2m>ZgO#2BW`%ELDq6!a=elUmwiFhvm7F(K}hAk8=~OVlOD7^;$h~N$}Y0jp1o?~ z_V~%cr~JUk4_T?)js*UltUEcg*OVRwiKPE%B~tWxWlO>5)a)-L6K-eByVn>78-gs) zXi?xe@!2Ph5xfo%rkIls*~V4j|IpjqO4gyNGK7%FTQm+yjq$KqOeFRmxWl}V z*m|WTC+JzyauaKG8>U%0u-R>@RlTXU2 z9%^o7;o@j?N_t|iIJ@IN2t)`A_~4zWx0i`>*&Rc?eD1(~7J$*L2^d|~e!kJsb`4E* zkoA-X8%2RF;2~b~U}twHg*QMl9#-R)pC@h`mpYdI)85t1eDD1_y(F3;T@8+BeSNRF zM2Lr8ouTz@2r(`*Opensp{PVdb7a^Q#KE8TKDoW1jTih>u1do^5I0AMuQP!}8}H^d zojlV(ojCr1wyi$gE>EiG54vZS1wm~%IVy2ZkZh%@-X!i~lGh{2&>219%w*L8^H`7$ zh2((`(Z*7Bih<#PSs-zUb$xuSvz`|C)1CZx>_eLCoDHRg%y)}cUWq?!lVNaITNTLD z&{m@TIB>UZTbc(Aex3R$_mT}no}pq|tFT{%)_hMV#yY}&))yyW@26RBvM1t8z0fcx zDJ3+$H(}*)?oOEwIORMzt!WvpdG5*yc08ftML> z2S}}_l=Lo&_^yEu6?_;ecl{}#^Ew8mLTlPh0uc1da0Ei7+hXaf#KTcgkzkqqett^3?tieFEnT>uM=H=I# z<*aW{Zg~B&8^>bSEDK$vi}k#GdSPj3%~FNA{6zJmiK|jAE>i-b;ncG3N70h7@g*TW zES*lh?ntmdk(@#8bBc#S_ykwdiPdeR=&kYKIjQ4O0tP;O^O7X$p9AtYZRc(LZ;2&R z+!ti_H z8G-TCbg&IX*?yh>z>E2L0*Uod9^w6L(dL;M<+A+%e@PgEl}2nnR9$4hN>pNHhNI$> zpSo8~hs{Ryg(yQe8c*7rkc|R=Nq<$L4^Yl2OFOO|kXP)UgDvSxsYs=-@$T(a;DD_rcg zOQGMDy2vly*3wAQVc+s!+VFoAkN9pL%VMQTxb*Sk>xb*!Zr|1(?lnF7(X>P1gJ1WG z*Y&{J{_D$DA2Rwm>#G%9MCzE*zI9Jt_k^1ErckNb;HZ5S(XBIe(4&bDckqlx1c%#> zW7{e#g|^A~s<5X$;WxTFI&#aQr_i9vTXW7d34#Gv&Ws!#dr5 z(R<6U0zq`c+1Puyp_X*Qqt;}R~0mXqU6%?U%mAmG;RF<$%$mrRPic{D0>mOubpG1CRcAPc`GEK5Hw8nws}*KopUL3W zYRXyi{8*Etf;Z4AXS(QEeOb*PIE_ru7OTfh8yCL;T6HXPy|EpSAP6!WQc*8`@Md9j z-F)J~o0Xs5P z$@!TmvRT@#gZvEw)>wb~q;}CE94vL_BE~P=&`8UJLGN3yo|jnBzj5WcLrEsWFJtD_ zkYw2w-4nj+9&tQsiZ7^U7x3-nEn(=xVg{)xuw=R_H4b)tD-UFJ@XBVaX5a`XBz>Jk zzGNAfc$9ENB0F<-2RG->17<#0rul#*= zgwjP7n#R4dp>|u}J?237&d^ZoU2l7uwIpwjcu5|fd2&L7_F}xYpVLO)v0UtAsj_xw zw^!ba3kM2W&QoZGz7^}MsIFV8E>12{lzS2HI$J7D__CG>u{c4O)*(8q-8V_X-6(I0 z+igitF#~#H$3Z_MLpX(y|JI|V4cQNXvzJ*K_j$8$r>es;QXtvYMNrSz6*Z9J>yMh7 ziMU%#N5}HM1v4}E9bK!swM;*U_`-e=ffv=Mn9O=urB^f632dIakIUkE$+ab~Sb=M7 z2LaWp&n$0Tm~Kax^tJ*4C%*`=ypZnt%c`OYmqrU2~v<>W%u5lVLg-% zq~UJanXF@(S$O!G`H)3=zlkY8M>WTS+%770nE5l2iL>>PmS1vahZ$6>M`zH==IWPEj2_mYXOBEcG)E;AC`>-D-Ee+t7rU zEIgt^=WizQcZ8rsn3?CLu zx0_rZ_iV7*HicHdf3KL6q46j%uprRCIp4}%xYm7mk27NK#qGE4e7IJ)19y43!ok8G zSBgbgVeC2{)Ma2@kje0#v^#ovU^i^>wTlv$j}D|Q)l#OG>!CyE6L~&U<%_Jg9wZjF z9#4J*V%#S0iSr}6eFx|`#`L;Ngj%Kz)<|8oQ5jgd%7j>po`SqJz!>d(dx16hGe~gl z+6!j)3w>S`oSrTeQ(5ci#kscSk9(|)7N*#Vy-RZ1-5B+*gda3p<rKide*Vjg~c!qg5B)uZV?)P6qfZW>wVY(5>JD z0L`ob0MT*GLM^HwEg+YonjDXcq6|n)6ZF54>ZFWarG&T?;Fskd;P@=E3@-taj4Dt~ zQ4XZ3#jPs$k5X}l55SDazc?I!_)w%8j-~$D<_zd);bLb7bq2Ycn4M;%agx)KNRkXa zj^ocR{5b>hSCJ(;i*u?=$4PP1>?Q`iWAPe{KRqP=DukqGiT~S_5NPgf=J+Sb={KaX zc|Hox<0|N5{b|YmR~aHd2T9t@+}_bl&dSlrS<}V%-@pF`iR=$Aj)HW&at@^OdHfbn zGLcQaQLEX-a1Q2qeCSUikRAh9C&iKHfq7BHqa#lhKi}fdNeuGj80t-pi9Ho_ ojw$Ao4CLX=bri;Y!dZ-em~_)nzK99NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail