|
| 1 | +/* |
| 2 | + * Copyright (c) Forge Development LLC and contributors |
| 3 | + * SPDX-License-Identifier: LGPL-2.1-only |
| 4 | + */ |
| 5 | +package net.minecraftforge.gradle; |
| 6 | + |
| 7 | +import org.gradle.api.Action; |
| 8 | +import org.gradle.api.Project; |
| 9 | +import org.gradle.api.file.ConfigurableFileCollection; |
| 10 | +import org.gradle.api.file.DirectoryProperty; |
| 11 | +import org.gradle.api.file.RegularFile; |
| 12 | +import org.gradle.api.logging.Logger; |
| 13 | +import org.gradle.api.logging.Logging; |
| 14 | +import org.gradle.api.model.ObjectFactory; |
| 15 | +import org.gradle.api.provider.Property; |
| 16 | +import org.gradle.jvm.toolchain.JavaLauncher; |
| 17 | +import org.gradle.process.ExecOperations; |
| 18 | + |
| 19 | +import java.io.IOException; |
| 20 | +import java.util.ArrayList; |
| 21 | +import java.util.List; |
| 22 | +import java.util.Queue; |
| 23 | +import java.util.concurrent.CompletableFuture; |
| 24 | +import java.util.concurrent.ConcurrentLinkedQueue; |
| 25 | +import java.util.concurrent.ExecutionException; |
| 26 | +import java.util.concurrent.Future; |
| 27 | +import java.util.concurrent.TimeUnit; |
| 28 | +import java.util.concurrent.TimeoutException; |
| 29 | +import java.util.function.Consumer; |
| 30 | + |
| 31 | +final class MavenizerAction implements Future<RegularFile> { |
| 32 | + private static final Logger LOGGER = Logging.getLogger(MavenizerAction.class); |
| 33 | + |
| 34 | + final ConfigurableFileCollection classpath; |
| 35 | + final Property<JavaLauncher> javaLauncher; |
| 36 | + final Property<String> mainClass; |
| 37 | + final DirectoryProperty caches; |
| 38 | + final DirectoryProperty output; |
| 39 | + final Property<String> module; |
| 40 | + final Property<String> version; |
| 41 | + final Property<MinecraftMappings> mappings; |
| 42 | + |
| 43 | + private final CapturingLogger capturingLogger; |
| 44 | + private final CapturingLogger capturingError; |
| 45 | + private final CompletableFuture<RegularFile> future; |
| 46 | + |
| 47 | + private final ExecOperations execOperations; |
| 48 | + |
| 49 | + MavenizerAction(ObjectFactory objects, ExecOperations execOperations, Action<? super MavenizerAction> action) { |
| 50 | + this.execOperations = execOperations; |
| 51 | + |
| 52 | + this.classpath = objects.fileCollection(); |
| 53 | + this.javaLauncher = objects.property(JavaLauncher.class); |
| 54 | + this.mainClass = objects.property(String.class); |
| 55 | + this.caches = objects.directoryProperty(); |
| 56 | + this.output = objects.directoryProperty(); |
| 57 | + this.module = objects.property(String.class); |
| 58 | + this.version = objects.property(String.class); |
| 59 | + this.mappings = objects.property(MinecraftMappings.class); |
| 60 | + |
| 61 | + this.capturingLogger = new CapturingLogger(LOGGER::lifecycle); |
| 62 | + this.capturingError = new CapturingLogger(LOGGER::error); |
| 63 | + |
| 64 | + action.execute(this); |
| 65 | + |
| 66 | + this.classpath.finalizeValue(); |
| 67 | + this.javaLauncher.finalizeValue(); |
| 68 | + this.mainClass.finalizeValue(); |
| 69 | + this.caches.finalizeValue(); |
| 70 | + this.output.finalizeValue(); |
| 71 | + this.module.finalizeValue(); |
| 72 | + this.version.finalizeValue(); |
| 73 | + this.mappings.finalizeValue(); |
| 74 | + |
| 75 | + this.future = CompletableFuture.supplyAsync(this::invoke); |
| 76 | + } |
| 77 | + |
| 78 | + @Override |
| 79 | + public boolean cancel(boolean mayInterruptIfRunning) { |
| 80 | + return this.future.cancel(mayInterruptIfRunning); |
| 81 | + } |
| 82 | + |
| 83 | + @Override |
| 84 | + public boolean isCancelled() { |
| 85 | + return this.future.isCancelled(); |
| 86 | + } |
| 87 | + |
| 88 | + @Override |
| 89 | + public boolean isDone() { |
| 90 | + return this.future.isDone(); |
| 91 | + } |
| 92 | + |
| 93 | + @Override |
| 94 | + public RegularFile get() throws InterruptedException, ExecutionException { |
| 95 | + return this.future.get(); |
| 96 | + } |
| 97 | + |
| 98 | + @Override |
| 99 | + public RegularFile get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { |
| 100 | + return this.future.get(timeout, unit); |
| 101 | + } |
| 102 | + |
| 103 | + void releaseLog() { |
| 104 | + this.capturingLogger.release(); |
| 105 | + this.capturingError.release(); |
| 106 | + } |
| 107 | + |
| 108 | + private RegularFile invoke() { |
| 109 | + String group; |
| 110 | + String name; |
| 111 | + { |
| 112 | + var split = module.get().split(":"); |
| 113 | + if (split.length != 2) { |
| 114 | + // TODO use problems API |
| 115 | + throw new IllegalArgumentException("Invalid Minecraft dependency module name: " + module.get()); |
| 116 | + } |
| 117 | + group = split[0]; |
| 118 | + name = split[1]; |
| 119 | + } |
| 120 | + |
| 121 | + try (var stdOut = Util.toLog(this.capturingLogger); |
| 122 | + var stdErr = Util.toLog(this.capturingError)) { |
| 123 | + this.execOperations.javaexec(spec -> { |
| 124 | + spec.setStandardOutput(stdOut); |
| 125 | + spec.setErrorOutput(stdErr); |
| 126 | + |
| 127 | + spec.setClasspath(classpath); |
| 128 | + spec.setExecutable(javaLauncher.get().getExecutablePath()); |
| 129 | + spec.getMainClass().set(mainClass); |
| 130 | + |
| 131 | + spec.setArgs(getArgs()); |
| 132 | + }).rethrowFailure(); |
| 133 | + } catch (IOException e) { |
| 134 | + throw new RuntimeException(e); |
| 135 | + } |
| 136 | + |
| 137 | + // TODO This assumes the placement of the metadata.zip file |
| 138 | + // It might be better to specify a location to output it, rather than include it as an artifact |
| 139 | + return output.file(Util.artifactPath(group, name, version.get(), "metadata", "zip")).get(); |
| 140 | + } |
| 141 | + |
| 142 | + private List<String> getArgs() { |
| 143 | + var args = new ArrayList<>(List.of( |
| 144 | + "--maven", |
| 145 | + "--cache", caches.get().getAsFile().getAbsolutePath(), |
| 146 | + "--output", output.get().getAsFile().getAbsolutePath(), |
| 147 | + "--jdk-cache", caches.dir("jdks").get().getAsFile().getAbsolutePath(), |
| 148 | + "--artifact", module.get(), |
| 149 | + "--version", version.get(), |
| 150 | + "--global-auxiliary-variants" |
| 151 | + )); |
| 152 | + |
| 153 | + if ("parchment".equals(mappings.get().channel())) { |
| 154 | + args.add("--parchment"); |
| 155 | + args.add(mappings.get().version()); |
| 156 | + } |
| 157 | + |
| 158 | + return args; |
| 159 | + } |
| 160 | + |
| 161 | + private static final class CapturingLogger implements Consumer<String> { |
| 162 | + private final Queue<String> lines = new ConcurrentLinkedQueue<>(); |
| 163 | + private final Consumer<? super String> logger; |
| 164 | + private boolean capturing = true; |
| 165 | + |
| 166 | + private CapturingLogger(Consumer<? super String> logger) { |
| 167 | + this.logger = logger; |
| 168 | + } |
| 169 | + |
| 170 | + @Override |
| 171 | + public void accept(String s) { |
| 172 | + if (capturing) { |
| 173 | + lines.add(s); |
| 174 | + } else { |
| 175 | + logger.accept(s); |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + private void release() { |
| 180 | + if (!capturing) return; |
| 181 | + |
| 182 | + lines.forEach(logger); |
| 183 | + capturing = false; |
| 184 | + } |
| 185 | + } |
| 186 | +} |
0 commit comments