diff --git a/src/main/java/rife/bld/instrument/ModuleMainClassAdapter.java b/src/main/java/rife/bld/instrument/ModuleMainClassAdapter.java new file mode 100644 index 0000000..2fe5673 --- /dev/null +++ b/src/main/java/rife/bld/instrument/ModuleMainClassAdapter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2001-2024 Geert Bevin (gbevin[remove] at uwyn dot com) + * Licensed under the Apache License, Version 2.0 (the "License") + */ +package rife.bld.instrument; + +import rife.asm.*; + +/** + * This utility class will modify a Java module {@code module-info.class} to add + * a module main class to its attributes. + * + * @author Geert Bevin (gbevin[remove] at uwyn dot com) + * @since 2.1 + */ +public class ModuleMainClassAdapter extends ClassVisitor implements Opcodes { + private final String mainClass_; + + /** + * Performs the actual modification of the module info class's bytecode. + * + * @param origBytes the bytes of the module class that should be modified + * @param mainClass the main class of the module + * @return the modified bytes + * @since 2.1 + */ + public static byte[] addModuleMainClassToBytes(byte[] origBytes, String mainClass) { + var cw = new ClassWriter(0); + new ClassReader(origBytes).accept(new ModuleMainClassAdapter(mainClass, cw), 0); + return cw.toByteArray(); + } + + private ModuleMainClassAdapter(String mainClass, ClassVisitor writer) { + super(ASM9, writer); + mainClass_ = mainClass.replace('.', '/'); + } + + @Override + public ModuleVisitor visitModule(String name, int access, String version) { + var module_visitor = super.visitModule(name, access, version); + module_visitor.visitMainClass(mainClass_); + return module_visitor; + } +} \ No newline at end of file diff --git a/src/main/java/rife/bld/operations/CompileOperation.java b/src/main/java/rife/bld/operations/CompileOperation.java index 651ff16..6b600cf 100644 --- a/src/main/java/rife/bld/operations/CompileOperation.java +++ b/src/main/java/rife/bld/operations/CompileOperation.java @@ -5,6 +5,7 @@ package rife.bld.operations; import rife.bld.BaseProject; +import rife.bld.instrument.ModuleMainClassAdapter; import rife.bld.operations.exceptions.ExitStatusException; import rife.tools.FileUtils; @@ -34,6 +35,7 @@ public class CompileOperation extends AbstractOperation { private final List testSourceDirectories_ = new ArrayList<>(); private final JavacOptions compileOptions_ = new JavacOptions(); private final List> diagnostics_ = new ArrayList<>(); + private String moduleMainClass_; /** * Performs the compile operation. @@ -135,6 +137,12 @@ protected void executeBuildSources(List classpath, List modulePa diagnostics_.addAll(diagnostics.getDiagnostics()); executeProcessDiagnostics(diagnostics); } + var module_info_class = new File(destination, "module-info.class"); + if (module_info_class.exists() && moduleMainClass() != null) { + var orig_bytes = FileUtils.readBytes(module_info_class); + var transformed_bytes = ModuleMainClassAdapter.addModuleMainClassToBytes(orig_bytes, moduleMainClass()); + FileUtils.writeBytes(transformed_bytes, module_info_class); + } } } @@ -175,7 +183,8 @@ public CompileOperation fromProject(BaseProject project) { .compileMainModulePath(project.compileMainModulePath()) .compileTestModulePath(project.compileTestModulePath()) .mainSourceFiles(project.mainSourceFiles()) - .testSourceFiles(project.testSourceFiles()); + .testSourceFiles(project.testSourceFiles()) + .moduleMainClass(project.mainClass()); if (project.javaRelease() != null && !compileOptions().containsRelease()) { compileOptions().release(project.javaRelease()); } @@ -428,6 +437,18 @@ public CompileOperation compileOptions(List options) { return this; } + /** + * Provides the main class to use if this compilation includes @{code module-info.java}. + * + * @param name the main class of the module + * @return this operation instance + * @since 2.1 + */ + public CompileOperation moduleMainClass(String name) { + moduleMainClass_ = name; + return this; + } + /** * Retrieves the main build destination directory. * @@ -565,4 +586,15 @@ public JavacOptions compileOptions() { public List> diagnostics() { return diagnostics_; } + + + /** + * Retrieves the main class to use if this compilation includes @{code module-info.java}. + * + * @return the main class to use for the module + * @since 2.1 + */ + public String moduleMainClass() { + return moduleMainClass_; + } } diff --git a/src/test/java/rife/bld/operations/TestRunOperation.java b/src/test/java/rife/bld/operations/TestRunOperation.java index 5be7391..fa14130 100644 --- a/src/test/java/rife/bld/operations/TestRunOperation.java +++ b/src/test/java/rife/bld/operations/TestRunOperation.java @@ -199,7 +199,8 @@ public static void main(String[] arguments) var compile_operation = new CompileOperation() .buildMainDirectory(build_main) .compileMainClasspath(List.of(build_main.getAbsolutePath())) - .mainSourceFiles(List.of(source_file1, source_file2)); + .mainSourceFiles(List.of(source_file1, source_file2)) + .moduleMainClass("pkg.Source1"); compile_operation.execute(); assertTrue(compile_operation.diagnostics().isEmpty()); @@ -209,12 +210,11 @@ public static void main(String[] arguments) .sourceDirectories(List.of(build_main)) .destinationDirectory(destination_dir) .destinationFileName(destination_name) - .manifestAttribute(Attributes.Name.MAIN_CLASS, "pkg.Source1") .execute(); var output = new StringBuilder(); var run_operation = new RunOperation() - .module("pkg/pkg.Source1") + .module("pkg") .modulePath(new File(destination_dir, destination_name).getAbsolutePath()) .outputProcessor(s -> { output.append(s);