diff --git a/build-logic/src/main/kotlin/codebook.gradle.kts b/build-logic/src/main/kotlin/codebook.gradle.kts index cb41e9b..ecae1af 100644 --- a/build-logic/src/main/kotlin/codebook.gradle.kts +++ b/build-logic/src/main/kotlin/codebook.gradle.kts @@ -34,7 +34,7 @@ repositories { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } withSourcesJar() } diff --git a/build.gradle.kts b/build.gradle.kts index 71c323f..15ef1ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { implementation(libs.bundles.asm) implementation(libs.unpick.format) - implementation(libs.unpick.cli) + implementation(libs.unpick) implementation(platform(libs.hypo.platform)) implementation(libs.bundles.hypo.full) diff --git a/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java b/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java index c3e0530..a3d9b2d 100644 --- a/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java +++ b/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java @@ -175,10 +175,7 @@ static final class ParamMappingsOptions { private @Nullable UnpickOptions unpick; static final class UnpickOptions { - @CommandLine.ArgGroup( - heading = - "%n%nUnpick requires unpick definitions. When specifying unpick definitions, unpick constants are also required.%n", - multiplicity = "1") + @CommandLine.ArgGroup(heading = "%n%nUnpick requires unpick definitions.%n", multiplicity = "1") private @Nullable UnpickDefinitionsOptions unpickDefinitions; static final class UnpickDefinitionsOptions { @@ -200,32 +197,6 @@ static final class UnpickDefinitionsOptions { description = "A download URL for the unpick definitions to use for the unpick process.") private @Nullable URI unpickUri; } - - @CommandLine.ArgGroup( - heading = - "%n%nUnpick requires a constants jar. When specifying unpick constants, unpick definitions are also required.%n", - multiplicity = "1") - private @Nullable ConstantsJarOptions constantsJar; - - static final class ConstantsJarOptions { - @CommandLine.Option( - names = "--constants-coords", - paramLabel = "", - description = "The Maven coordinates for the constants jar to use for the unpick process.") - private @Nullable String constantsCoords; - - @CommandLine.Option( - names = {"--constants-file"}, - paramLabel = "", - description = "The constants jar to use for the unpick process.") - private @Nullable Path constantsFile; - - @CommandLine.Option( - names = "--constants-uri", - paramLabel = "", - description = "A download URL for the constants jar to use for the unpick process.") - private @Nullable URI constantsUri; - } } @CommandLine.Option( @@ -440,7 +411,7 @@ private CodeBookContext createContext() { p -> new Coords(p.paramsCoords, null, "zip", this.paramsMavenBaseUrl)); final @Nullable CodeBookResource unpickDefinitions = this.getResource( - "unpick_definitions.jar", + "definitions.unpick", this.unpick != null ? this.unpick.unpickDefinitions : null, d -> d.unpickFile, d -> d.unpickUri, @@ -452,19 +423,6 @@ private CodeBookContext createContext() { return new Coords(d.unpickCoords, "constants", null, this.unpickMavenBaseUrl); }); - final @Nullable CodeBookResource constantJar = this.getResource( - "unpick_constants.jar", - this.unpick != null ? this.unpick.constantsJar : null, - c -> c.constantsFile, - c -> c.constantsUri, - c -> { - if (this.unpickMavenBaseUrl == null) { - throw new UserErrorException( - "Cannot define unpick constants Maven coordinates without also setting --unpick-maven-base-url"); - } - return new Coords(c.constantsCoords, "constants", null, this.unpickMavenBaseUrl); - }); - @Nullable Reports reports = null; if (this.reports != null && this.reports.reportsDir != null) { final Set reportsToGenerate; @@ -488,7 +446,6 @@ private CodeBookContext createContext() { .mappings(mappings) .paramMappings(paramMappings) .unpickDefinitions(unpickDefinitions) - .constantsJar(constantJar) .outputJar(this.outputJar) .overwrite(this.forceWrite) .input(input) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f3f7a14..afba3ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ palantir = "2.50.0" indra = "4.0.0" slf4j = "2.0.7" lorenz = "0.5.7" -unpick = "2.3.0" +unpick = "3.0.0-beta.13" asm = "9.9" feather = "1.1.0" recordBuilder = "37" @@ -40,7 +40,7 @@ asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } unpick-format = { module = "net.fabricmc.unpick:unpick-format-utils", version.ref = "unpick" } -unpick-cli = { module = "net.fabricmc.unpick:unpick-cli", version.ref = "unpick" } +unpick = { module = "net.fabricmc.unpick:unpick", version.ref = "unpick" } hypo-platform = "dev.denwav.hypo:hypo-platform:2.3.0" hypo-model = { module = "dev.denwav.hypo:hypo-model" } diff --git a/src/main/java/io/papermc/codebook/CodeBook.java b/src/main/java/io/papermc/codebook/CodeBook.java index 873be47..3722372 100644 --- a/src/main/java/io/papermc/codebook/CodeBook.java +++ b/src/main/java/io/papermc/codebook/CodeBook.java @@ -78,8 +78,8 @@ private void exec(final Path tempDir) { final var book = List.of( ExtractVanillaJarPage.class, RemapJarPage.class, - UnpickPage.class, InspectJarPage.class, + UnpickPage.class, FixJarPage.class, RemapLvtPage.class); @@ -144,18 +144,15 @@ private Module createInitialModule(final Path tempDir) { final @Nullable Path unpickDefinitions; if (this.ctx.unpickDefinitions() != null) { - unpickDefinitions = this.ctx.unpickDefinitions().resolveResourceFile(tempDir); + if (this.ctx.unpickDefinitions().equals(this.ctx.paramMappings())) { + unpickDefinitions = paramMappingsFile; + } else { + unpickDefinitions = this.ctx.unpickDefinitions().resolveResourceFile(tempDir); + } } else { unpickDefinitions = null; } - final @Nullable Path constantsJar; - if (this.ctx.constantsJar() != null) { - constantsJar = this.ctx.constantsJar().resolveResourceFile(tempDir); - } else { - constantsJar = null; - } - return new AbstractModule() { @Override protected void configure() { @@ -177,12 +174,6 @@ protected void configure() { this.bind(CodeBookPage.UnpickDefinitions.KEY).toProvider(Providers.of(null)); } - if (constantsJar != null) { - this.bind(CodeBookPage.ConstantsJar.KEY).toInstance(constantsJar); - } else { - this.bind(CodeBookPage.ConstantsJar.KEY).toProvider(Providers.of(null)); - } - if (CodeBook.this.ctx.reports() != null) { this.bind(CodeBookPage.Report.KEY).toInstance(CodeBook.this.ctx.reports()); this.install(CodeBook.this.ctx.reports()); diff --git a/src/main/java/io/papermc/codebook/config/CodeBookContext.java b/src/main/java/io/papermc/codebook/config/CodeBookContext.java index 0ef96bb..cd2a2cf 100644 --- a/src/main/java/io/papermc/codebook/config/CodeBookContext.java +++ b/src/main/java/io/papermc/codebook/config/CodeBookContext.java @@ -39,7 +39,6 @@ public record CodeBookContext( @Nullable @org.jetbrains.annotations.Nullable CodeBookResource mappings, @Nullable @org.jetbrains.annotations.Nullable CodeBookResource paramMappings, @Nullable @org.jetbrains.annotations.Nullable CodeBookResource unpickDefinitions, - @Nullable @org.jetbrains.annotations.Nullable CodeBookResource constantsJar, @NotNull Path outputJar, boolean overwrite, @NotNull CodeBookInput input, diff --git a/src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java b/src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java new file mode 100644 index 0000000..acbfe39 --- /dev/null +++ b/src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java @@ -0,0 +1,73 @@ +/* + * codebook is a remapper utility for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * Contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 3 only, no later versions. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +package io.papermc.codebook.pages; + +import dev.denwav.hypo.asm.AsmClassData; +import dev.denwav.hypo.core.HypoContext; +import dev.denwav.hypo.model.HypoModelUtil; +import dev.denwav.hypo.model.data.ClassData; +import io.papermc.codebook.exceptions.UnexpectedException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public abstract class AsmProcessorPage extends CodeBookPage { + + protected final HypoContext context; + + protected AsmProcessorPage(final HypoContext context) { + this.context = context; + } + + @Override + public void exec() { + this.processClasses(); + } + + protected void processClasses() { + final var tasks = new ArrayList>(); + for (final ClassData classData : this.context.getProvider().allClasses()) { + final var task = this.context.getExecutor().submit(() -> { + try { + this.processClass((AsmClassData) classData); + } catch (final Exception e) { + throw HypoModelUtil.rethrow(e); + } + }); + tasks.add(task); + } + + try { + for (final Future task : tasks) { + task.get(); + } + } catch (final ExecutionException e) { + throw new UnexpectedException("Failed to process classes", e.getCause()); + } catch (final InterruptedException e) { + throw new UnexpectedException("Class processing interrupted", e); + } + } + + protected abstract void processClass(final AsmClassData classData) throws IOException; +} diff --git a/src/main/java/io/papermc/codebook/pages/CodeBookPage.java b/src/main/java/io/papermc/codebook/pages/CodeBookPage.java index 07a7010..ffbe452 100644 --- a/src/main/java/io/papermc/codebook/pages/CodeBookPage.java +++ b/src/main/java/io/papermc/codebook/pages/CodeBookPage.java @@ -138,13 +138,6 @@ public void to(final @Nullable T value) { Key KEY = Key.get(Path.class, UnpickDefinitions.class); } - @Qualifier - @Target(ElementType.PARAMETER) - @Retention(RetentionPolicy.RUNTIME) - public @interface ConstantsJar { - Key KEY = Key.get(Path.class, ConstantsJar.class); - } - @Qualifier @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/io/papermc/codebook/pages/FixJarPage.java b/src/main/java/io/papermc/codebook/pages/FixJarPage.java index bd846b6..0c40b50 100644 --- a/src/main/java/io/papermc/codebook/pages/FixJarPage.java +++ b/src/main/java/io/papermc/codebook/pages/FixJarPage.java @@ -28,69 +28,31 @@ import dev.denwav.hypo.asm.AsmMethodData; import dev.denwav.hypo.core.HypoContext; import dev.denwav.hypo.hydrate.generic.HypoHydration; -import dev.denwav.hypo.model.HypoModelUtil; import dev.denwav.hypo.model.data.ClassData; import dev.denwav.hypo.model.data.ClassKind; import dev.denwav.hypo.model.data.FieldData; import dev.denwav.hypo.model.data.MethodData; import dev.denwav.hypo.model.data.Visibility; -import io.papermc.codebook.exceptions.UnexpectedException; import jakarta.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; -public final class FixJarPage extends CodeBookPage { - - private final HypoContext context; +public final class FixJarPage extends AsmProcessorPage { @Inject public FixJarPage(@Hypo final HypoContext context) { - this.context = context; + super(context); } @Override - public void exec() { - try { - this.fixJar(); - } catch (final IOException e) { - throw new UnexpectedException("Failed to fix jar", e); - } - } - - private void fixJar() throws IOException { - final var tasks = new ArrayList>(); - for (final ClassData classData : this.context.getProvider().allClasses()) { - final var task = this.context.getExecutor().submit(() -> { - try { - this.processClass((AsmClassData) classData); - } catch (final IOException e) { - throw HypoModelUtil.rethrow(e); - } - }); - tasks.add(task); - } - - try { - for (final Future task : tasks) { - task.get(); - } - } catch (final ExecutionException e) { - throw new UnexpectedException("Failed to fix jar", e.getCause()); - } catch (final InterruptedException e) { - throw new UnexpectedException("Jar fixing interrupted", e); - } - } - - private void processClass(final AsmClassData classData) throws IOException { + protected void processClass(final AsmClassData classData) throws IOException { OverrideAnnotationAdder.addAnnotations(classData); EmptyRecordFixer.fixClass(classData); RecordFieldAccessFixer.fixClass(classData); diff --git a/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java b/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java index 1c25079..98105f0 100644 --- a/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java +++ b/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java @@ -24,31 +24,26 @@ import dev.denwav.hypo.asm.AsmClassData; import dev.denwav.hypo.core.HypoContext; -import dev.denwav.hypo.model.HypoModelUtil; -import dev.denwav.hypo.model.data.ClassData; import io.papermc.codebook.exceptions.UnexpectedException; import io.papermc.codebook.lvt.LvtNamer; import io.papermc.codebook.report.Reports; import jakarta.inject.Inject; import java.io.IOException; -import java.util.ArrayList; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import org.cadixdev.lorenz.MappingSet; import org.checkerframework.checker.nullness.qual.Nullable; -public final class RemapLvtPage extends CodeBookPage { +public final class RemapLvtPage extends AsmProcessorPage { - private final HypoContext context; private final @Nullable MappingSet paramMappings; private final Reports reports; + private @Nullable LvtNamer lvtNamer; @Inject public RemapLvtPage( @Hypo final HypoContext hypoContext, @ParamMappings @Nullable final MappingSet paramMappings, @Report final Reports reports) { - this.context = hypoContext; + super(hypoContext); this.paramMappings = paramMappings; this.reports = reports; } @@ -59,37 +54,19 @@ public void exec() { return; } - final LvtNamer lvtNamer; try { - lvtNamer = new LvtNamer(this.context, this.paramMappings, this.reports); + this.lvtNamer = new LvtNamer(this.context, this.paramMappings, this.reports); } catch (final IOException e) { throw new UnexpectedException("Failed to create LVT namer", e); } - this.remapLvt(lvtNamer); + this.processClasses(); } - private void remapLvt(final LvtNamer lvtNamer) { - final ArrayList> tasks = new ArrayList<>(); - for (final ClassData classData : this.context.getProvider().allClasses()) { - final var task = this.context.getExecutor().submit(() -> { - try { - lvtNamer.processClass((AsmClassData) classData); - } catch (final Exception e) { - throw HypoModelUtil.rethrow(e); - } - }); - tasks.add(task); - } - - try { - for (final Future task : tasks) { - task.get(); - } - } catch (final ExecutionException e) { - throw new UnexpectedException("Failed to remap LVT", e.getCause()); - } catch (final InterruptedException e) { - throw new UnexpectedException("LVT remap interrupted", e); + @Override + protected void processClass(final AsmClassData classData) throws IOException { + if (this.lvtNamer != null) { + this.lvtNamer.processClass(classData); } } } diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 8dcad1f..a9b32b9 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -22,58 +22,176 @@ package io.papermc.codebook.pages; -import io.papermc.codebook.exceptions.UnexpectedException; -import io.papermc.codebook.util.IOUtil; +import daomephsta.unpick.api.ConstantUninliner; +import daomephsta.unpick.api.classresolvers.ClassResolvers; +import daomephsta.unpick.api.classresolvers.IClassResolver; +import daomephsta.unpick.api.constantgroupers.ConstantGroupers; +import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Reader; +import daomephsta.unpick.constantmappers.datadriven.tree.ForwardingUnpickV3Visitor; +import daomephsta.unpick.constantmappers.datadriven.tree.GroupDefinition; +import daomephsta.unpick.constantmappers.datadriven.tree.expr.Expression; +import daomephsta.unpick.constantmappers.datadriven.tree.expr.ExpressionVisitor; +import daomephsta.unpick.constantmappers.datadriven.tree.expr.FieldExpression; +import dev.denwav.hypo.asm.AsmClassData; +import dev.denwav.hypo.core.HypoContext; +import dev.denwav.hypo.model.data.ClassData; import jakarta.inject.Inject; +import java.io.BufferedReader; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.tree.ClassNode; -public final class UnpickPage extends CodeBookPage { +public final class UnpickPage extends AsmProcessorPage { - private final Path inputJar; private final List classpath; - private final Path tempDir; private final @Nullable Path unpickDefinitions; - private final @Nullable Path constantsJar; + private @MonotonicNonNull ConstantUninliner uninliner; @Inject public UnpickPage( - @InputJar final Path inputJar, + @Hypo final HypoContext context, @ClasspathJars final List classpath, - @TempDir final Path tempDir, - @UnpickDefinitions final @Nullable Path unpickDefinitions, - @ConstantsJar final @Nullable Path constantsJar) { - this.inputJar = inputJar; + @UnpickDefinitions final @Nullable Path unpickDefinitions) { + super(context); this.classpath = classpath; - this.tempDir = tempDir; this.unpickDefinitions = unpickDefinitions; - this.constantsJar = constantsJar; } + @Override public void exec() { - if (this.unpickDefinitions == null || this.constantsJar == null) { + if (this.unpickDefinitions == null) { return; } - final Path outputJar = this.tempDir.resolve("unpicked.jar"); + boolean isZip; + try (final ZipFile zf = new ZipFile(this.unpickDefinitions.toFile())) { + isZip = true; + } catch (final ZipException e) { + isZip = false; + } catch (final IOException e) { + throw new UncheckedIOException(e); + } - final var args = new ArrayList(); - args.addAll(List.of( - IOUtil.absolutePathString(this.inputJar), - IOUtil.absolutePathString(outputJar), - IOUtil.absolutePathString(this.unpickDefinitions), - IOUtil.absolutePathString(this.constantsJar))); - args.addAll(this.classpath.stream().map(IOUtil::absolutePathString).toList()); + final List zips = new ArrayList<>(); - try { - daomephsta.unpick.cli.Main.main(args.toArray(new String[0])); - } catch (final IOException e) { - throw new UnexpectedException("Failed to run unpick", e); + if (isZip) { + try (final FileSystem definitionsFs = FileSystems.newFileSystem(this.unpickDefinitions)) { + final Path definitionsPath = definitionsFs.getPath("extras/definitions.unpick"); + this.unpick(definitionsPath, zips); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } else { + try { + this.unpick(this.unpickDefinitions, zips); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } } + } + + private void unpick(final Path definitionsPath, final List zips) throws IOException { + IClassResolver classResolver = new IClassResolver() { + @Override + public @Nullable ClassNode resolveClass(final String internalName) { + try { + final @Nullable ClassData cls = + UnpickPage.this.context.getContextProvider().findClass(internalName); + if (cls instanceof final AsmClassData asmClassData) { + return asmClassData.getNode(); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + return null; + } + }; + + try (final BufferedReader definitionsReader = Files.newBufferedReader(definitionsPath)) { + for (final Path classpathJar : this.classpath) { + final ZipFile zip = new ZipFile(classpathJar.toFile()); + zips.add(zip); + classResolver = classResolver.chain(ClassResolvers.jar(zip)); + } + + classResolver = classResolver.chain(ClassResolvers.classpath()); - this.bind(InputJar.KEY).to(outputJar); + this.uninliner = ConstantUninliner.builder() + .grouper(ConstantGroupers.dataDriven() + .classResolver(classResolver) + .mappingSource(visitor -> { + try { + new UnpickV3Reader(definitionsReader) + .accept(new ForwardingUnpickV3Visitor(visitor) { + // Filter out any groups where all constants reference missing classes + // (client classes when applying to the server or outdated definitions) + @Override + public void visitGroupDefinition( + final GroupDefinition groupDefinition) { + final List constants = + new ArrayList<>(groupDefinition.constants()); + for (final Expression constant : groupDefinition.constants()) { + constant.accept(new ExpressionVisitor() { + @Override + public void visitFieldExpression( + final FieldExpression fieldExpression) { + try { + final @Nullable ClassData clsData = UnpickPage.this + .context + .getContextProvider() + .findClass(fieldExpression.className); + if (clsData == null) { + constants.remove(constant); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + }); + } + if (!constants.isEmpty()) { + super.visitGroupDefinition( + GroupDefinition.Builder.from(groupDefinition) + .setConstants(constants) + .build()); + } + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }) + .build()) + .classResolver(classResolver) + .build(); + + this.processClasses(); + } finally { + for (final ZipFile zip : zips) { + try { + zip.close(); + } catch (final IOException e) { + // Ignore + } + } + } + } + + @Override + protected void processClass(final AsmClassData classData) { + if (this.uninliner == null) { + return; + } + this.uninliner.transform(classData.getNode()); } }