diff --git a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java index 511ab15..b918c84 100644 --- a/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java +++ b/cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java @@ -68,6 +68,7 @@ import joptsimple.OptionSet; import org.netbeans.api.actions.Savable; import org.netbeans.api.java.classpath.ClassPath; +import org.netbeans.api.java.classpath.JavaClassPathConstants; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.ModificationResult; import org.netbeans.modules.editor.tools.storage.api.ToolPreferences; @@ -108,6 +109,7 @@ import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.netbeans.spi.java.hints.Hint.Kind; import org.netbeans.spi.java.hints.HintContext; +import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation; import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; @@ -408,20 +410,43 @@ private static GroupOptions setupGroupParser(OptionParser parser) { return new GroupOptions(parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), - parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT)); + parser.accepts("add-exports", "javac's addd-exports option").withRequiredArg().ofType(String.class), + parser.accepts("add-modules", "javac's add-modules option").withRequiredArg().ofType(String.class), + parser.accepts("limit-modules", "javac's limit-modules option").withRequiredArg().ofType(String.class), + parser.accepts("module-path", "module path").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class), + parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT), + parser.accepts("system", "system modules").withRequiredArg().ofType(File.class)); } private static final class GroupOptions { private final ArgumentAcceptingOptionSpec classpath; private final ArgumentAcceptingOptionSpec bootclasspath; private final ArgumentAcceptingOptionSpec sourcepath; + private final ArgumentAcceptingOptionSpec addExports; + private final ArgumentAcceptingOptionSpec addModules; + private final ArgumentAcceptingOptionSpec limitModules; + private final ArgumentAcceptingOptionSpec modulePath; private final ArgumentAcceptingOptionSpec source; - - public GroupOptions(ArgumentAcceptingOptionSpec classpath, ArgumentAcceptingOptionSpec bootclasspath, ArgumentAcceptingOptionSpec sourcepath, ArgumentAcceptingOptionSpec source) { + private final ArgumentAcceptingOptionSpec system; + + public GroupOptions(ArgumentAcceptingOptionSpec classpath, + ArgumentAcceptingOptionSpec bootclasspath, + ArgumentAcceptingOptionSpec sourcepath, + ArgumentAcceptingOptionSpec addExports, + ArgumentAcceptingOptionSpec addModules, + ArgumentAcceptingOptionSpec limitModules, + ArgumentAcceptingOptionSpec modulePath, + ArgumentAcceptingOptionSpec source, + ArgumentAcceptingOptionSpec system) { this.classpath = classpath; this.bootclasspath = bootclasspath; this.sourcepath = sourcepath; + this.limitModules = limitModules; + this.modulePath = modulePath; this.source = source; + this.addExports = addExports; + this.addModules = addModules; + this.system = system; } } @@ -1008,9 +1033,14 @@ private static final class WarningsAndErrors { private static final class RootConfiguration { private final List rootFolders; private final ClassPath bootCP; + private final ClassPath systemCP; private final ClassPath compileCP; + private final ClassPath modulePathCP; private final ClassPath sourceCP; private final ClassPath binaryCP; + private final String addExports; + private final String addModules; + private final String limitModules; private final String sourceLevel; public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOException { @@ -1029,9 +1059,14 @@ public RootConfiguration(OptionSet parsed, GroupOptions groupOptions) throws IOE } this.bootCP = createClassPath(parsed.has(groupOptions.bootclasspath) ? parsed.valuesOf(groupOptions.bootclasspath) : null, Utils.createDefaultBootClassPath()); + this.systemCP = createClassPath(parsed.has(groupOptions.system) ? parsed.valuesOf(groupOptions.bootclasspath) : null, null); //XXX: this needs to be a JRTFS! this.compileCP = createClassPath(parsed.has(groupOptions.classpath) ? parsed.valuesOf(groupOptions.classpath) : null, ClassPath.EMPTY); + this.modulePathCP = createClassPath(parsed.has(groupOptions.modulePath) ? parsed.valuesOf(groupOptions.modulePath) : null, ClassPath.EMPTY); //XXX: expand directories(?) this.sourceCP = createClassPath(parsed.has(groupOptions.sourcepath) ? parsed.valuesOf(groupOptions.sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0]))); - this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP); + this.binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP, systemCP, modulePathCP); + this.addExports = parsed.has(groupOptions.addExports) ? parsed.valueOf(groupOptions.addExports) : null; + this.addModules = parsed.has(groupOptions.addModules) ? parsed.valueOf(groupOptions.addModules) : null;; + this.limitModules = parsed.has(groupOptions.limitModules) ? parsed.valueOf(groupOptions.limitModules) : null;; this.sourceLevel = parsed.valueOf(groupOptions.source); } @@ -1088,8 +1123,12 @@ public ClassPath findClassPath(FileObject file, String type) { if (rootConfiguration.sourceCP.findOwnerRoot(file) != null) { if (ClassPath.BOOT.equals(type)) { return rootConfiguration.bootCP; + } else if (JavaClassPathConstants.MODULE_BOOT_PATH.equals(type)) { + return rootConfiguration.systemCP; } else if (ClassPath.COMPILE.equals(type)) { return rootConfiguration.compileCP; + } else if (JavaClassPathConstants.MODULE_COMPILE_PATH.equals(type)) { + return rootConfiguration.modulePathCP; } else if (ClassPath.SOURCE.equals(type)) { return rootConfiguration.sourceCP; } @@ -1099,6 +1138,50 @@ public ClassPath findClassPath(FileObject file, String type) { } } + @ServiceProvider(service=CompilerOptionsQueryImplementation.class, position=100) + public static final class CompilerOptionsQueryImpl implements CompilerOptionsQueryImplementation { + private final Result result = new Result() { + @Override + public List getArguments() { + RootConfiguration rootConfiguration = currentRootConfiguration.get(); + + if (rootConfiguration == null) { + return List.of(); + } + + List result = new ArrayList<>(); + + if (rootConfiguration.addModules != null) { + result.add("--add-modules"); + result.add(rootConfiguration.addModules); + } + + if (rootConfiguration.limitModules != null) { + result.add("--limit-modules"); + result.add(rootConfiguration.limitModules); + } + + if (rootConfiguration.addExports != null) { + result.add("--add-exports"); + result.add(rootConfiguration.addExports); + } + + return result; + } + @Override + public void addChangeListener(ChangeListener cl) { + } + @Override + public void removeChangeListener(ChangeListener cl) { + } + }; + + @Override + public Result getOptions(FileObject fo) { + return result; + } + } + @ServiceProvider(service=SourceLevelQueryImplementation2.class, position=100) public static final class SourceLevelQueryImpl implements SourceLevelQueryImplementation2 { diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java index e64f6d4..f1b9fbb 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateTool.java @@ -21,6 +21,7 @@ import java.util.regex.Pattern; import javax.annotation.processing.Processor; import org.netbeans.modules.jackpot30.cmdline.Main.BCPFallBack; +import org.netbeans.modules.jackpot30.cmdline.Main.CompilerOptionsQueryImpl; import org.netbeans.modules.jackpot30.cmdline.Main.SourceLevelQueryImpl; import org.netbeans.modules.jackpot30.cmdline.lib.CreateStandaloneJar; import org.netbeans.modules.jackpot30.cmdline.lib.CreateStandaloneJar.Info; @@ -31,6 +32,7 @@ import org.netbeans.modules.java.platform.DefaultJavaPlatformProvider; import org.netbeans.modules.project.ui.OpenProjectsTrampolineImpl; import org.netbeans.spi.java.classpath.ClassPathProvider; +import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation; import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2; /** @@ -45,13 +47,14 @@ public CreateTool(String name) { @Override protected Info computeInfo() { - return new Info().addAdditionalRoots(Main.class.getName(), DeclarativeHintsTestBase.class.getName(), OpenProjectsTrampolineImpl.class.getName(), J2SEProject.class.getName(), DefaultJavaPlatformProvider.class.getName(), PatternConvertorImpl.class.getName(), BCPFallBack.class.getName(), "org.slf4j.impl.StaticLoggerBinder") + return new Info().addAdditionalRoots(Main.class.getName(), DeclarativeHintsTestBase.class.getName(), OpenProjectsTrampolineImpl.class.getName(), J2SEProject.class.getName(), DefaultJavaPlatformProvider.class.getName(), PatternConvertorImpl.class.getName(), BCPFallBack.class.getName(), "org.slf4j.impl.StaticLoggerBinder", CompilerOptionsQueryImpl.class.getName()) .addAdditionalResources("org/netbeans/modules/java/hints/resources/Bundle.properties", "org/netbeans/modules/java/hints/declarative/resources/Bundle.properties") .addAdditionalLayers("org/netbeans/modules/java/hints/resources/layer.xml", "org/netbeans/modules/java/hints/declarative/resources/layer.xml") .addMetaInfRegistrations(new MetaInfRegistration(org.netbeans.modules.project.uiapi.OpenProjectsTrampoline.class, OpenProjectsTrampolineImpl.class)) .addMetaInfRegistrations(new MetaInfRegistration(ClassPathProvider.class.getName(), BCPFallBack.class.getName(), 9999)) .addMetaInfRegistrations(new MetaInfRegistration(ClassPathProvider.class.getName(), Main.ClassPathProviderImpl.class.getName(), 100)) .addMetaInfRegistrations(new MetaInfRegistration(SourceLevelQueryImplementation2.class.getName(), SourceLevelQueryImpl.class.getName(), 100)) + .addMetaInfRegistrations(new MetaInfRegistration(CompilerOptionsQueryImplementation.class.getName(), CompilerOptionsQueryImpl.class.getName(), 100)) .addMetaInfRegistrationToCopy(PatternConvertor.class.getName()) .addExcludePattern(Pattern.compile("junit\\.framework\\..*")) .setEscapeJavaxLang(); diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java index 3119a08..d3c6915 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/CreateToolTest.java @@ -71,11 +71,14 @@ protected void reallyRunCompiler(File workingDir, int exitcode, String[] output, int actualExitCode = p.waitFor(); - assertEquals(exitcode, actualExitCode); - outCopy.doJoin(); errCopy.doJoin(); + + assertEquals(exitcode, actualExitCode); } catch (Throwable t) { + System.err.println(output[0]); + System.err.println(output[1]); + throw new IOException(t); } } diff --git a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java index a709ae6..4db71a5 100644 --- a/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java +++ b/cmdline/tool/test/unit/src/org/netbeans/modules/jackpot30/cmdline/MainTest.java @@ -30,7 +30,11 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -38,6 +42,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.junit.runner.Result; import org.netbeans.junit.NbTestCase; @@ -55,6 +60,7 @@ public class MainTest extends NbTestCase { private static final String TEST_HINT = "Usage of [Collection|Map].size() == 0"; + private static final String BASE_64_PREFIX = "BASE64: "; public MainTest(String name) { super(name); @@ -1064,6 +1070,147 @@ public void testSource17() throws Exception { "--source", "17"); } + public void testModulePath() throws Exception { + clearWorkDir(); + + List maFiles = List.of( + writeToPath("src/ma/module-info.java", + """ + module ma { + exports api; + } + """), + writeToPath("src/ma/api/Api.java", + """ + package api; + import java.util.Collection; + public class Api { + public static Collection get() { + return null; + } + } + """), + writeToPath("src/ma/impl/Impl.java", + """ + package impl; + import java.util.Collection; + public class Impl { + public static Collection get() { + return null; + } + } + """)); + File classesOutput = new File(getWorkDir(), "classes/ma"); + + classesOutput.getParentFile().mkdirs(); + + try (StandardJavaFileManager fm = + ToolProvider.getSystemJavaCompiler() + .getStandardFileManager(null, null, null)) { + Boolean compilationResult = + ToolProvider.getSystemJavaCompiler() + .getTask(null, null, null, List.of("-d", classesOutput.getAbsolutePath()), null, + fm.getJavaFileObjectsFromFiles(maFiles)) + .call(); + if (compilationResult == null || !compilationResult) { + throw new AssertionError("Cannot compile the module!"); + } + } + + String golden = + """ + package test; + import api.Api; + import impl.Impl; + public class Test { + private void test() { + boolean b1 = Api.get().isEmpty(); + boolean b2 = Impl.get().isEmpty(); + } + } + """; + + List filesAndOptions = new ArrayList<>(); + + filesAndOptions.add("src/test/Test.java"); + filesAndOptions.add(""" + package test; + import api.Api; + import impl.Impl; + public class Test { + private void test() { + boolean b1 = Api.get().size() == 0; + boolean b2 = Impl.get().size() == 0; + } + } + """); + filesAndOptions.addAll(listFiles(classesOutput)); + filesAndOptions.add(null); + filesAndOptions.add("--apply"); + filesAndOptions.add("--hint"); + filesAndOptions.add(TEST_HINT); + filesAndOptions.add("--source"); filesAndOptions.add("17"); + filesAndOptions.add("--module-path"); filesAndOptions.add(classesOutput.getAbsolutePath()); + filesAndOptions.add("--add-modules"); filesAndOptions.add("ma"); + filesAndOptions.add("--add-exports"); filesAndOptions.add("ma/impl=ALL-UNNAMED"); + + doRunCompiler(golden, + null, + null, + filesAndOptions.toArray(String[]::new)); + } + + public void testLimitModules() throws Exception { + clearWorkDir(); + + String golden = + """ + package test; + import com.sun.tools.javac.*; + public class Test { + private boolean test() { + return Main2.test().isEmpty(); + } + } + """; + + List filesAndOptions = new ArrayList<>(); + + filesAndOptions.add("src/test/Test.java"); + filesAndOptions.add(""" + package test; + import com.sun.tools.javac.*; + public class Test { + private boolean test() { + return Main2.test().size() == 0; + } + } + """); + filesAndOptions.add("src/com/sun/tools/javac/Main2.java"); + filesAndOptions.add(""" + package com.sun.tools.javac; + import java.util.List; + public class Main2 { + public static List test() { + return List.of(); + } + } + """); + filesAndOptions.add(null); + filesAndOptions.add(DONT_APPEND_PATH); + filesAndOptions.add("--apply"); + filesAndOptions.add("--hint"); + filesAndOptions.add(TEST_HINT); + filesAndOptions.add("--source"); filesAndOptions.add("17"); + filesAndOptions.add("--limit-modules"); filesAndOptions.add("java.base"); + filesAndOptions.add("${workdir}/src"); + + doRunCompiler(golden, + null, + null, + filesAndOptions.toArray(String[]::new)); + } + private static final String DONT_APPEND_PATH = new String("DONT_APPEND_PATH"); private static final String IGNORE = new String("IGNORE"); @@ -1094,11 +1241,7 @@ private void doRunCompiler(Validator fileContentValidator, Validator stdOutValid clearWorkDir(); for (int cntr = 0; cntr < fileAndContent.size(); cntr += 2) { - File target = new File(getWorkDir(), fileAndContent.get(cntr)); - - target.getParentFile().mkdirs(); - - TestUtils.copyStringToFile(target, fileAndContent.get(cntr + 1)); + writeToPath(fileAndContent.get(cntr), fileAndContent.get(cntr + 1)); } File wd = getWorkDir(); @@ -1130,14 +1273,20 @@ private void doRunCompiler(Validator fileContentValidator, Validator stdOutValid reallyRunCompiler(wd, exitcode, output, options.toArray(new String[0])); - if (fileContentValidator != null) { - fileContentValidator.validate(TestUtils.copyFileToString(source)); - } - if (stdOutValidator != null) { - stdOutValidator.validate(output[0].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); - } - if (stdErrValidator != null) { - stdErrValidator.validate(output[1].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); + try { + if (fileContentValidator != null) { + fileContentValidator.validate(TestUtils.copyFileToString(source)); + } + if (stdOutValidator != null) { + stdOutValidator.validate(output[0].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); + } + if (stdErrValidator != null) { + stdErrValidator.validate(output[1].replaceAll(Pattern.quote(wd.getAbsolutePath()), Matcher.quoteReplacement("${workdir}"))); + } + } catch (Throwable t) { + System.err.println(output[0]); + System.err.println(output[1]); + throw t; } } @@ -1266,6 +1415,38 @@ private static Validator equivalentValidator(final String expected) { }; } + private File writeToPath(String relativePath, String content) throws Exception { + File target = new File(getWorkDir(), relativePath); + + target.getParentFile().mkdirs(); + + if (content.startsWith(BASE_64_PREFIX)) { + Files.write(target.toPath(), Base64.getDecoder().decode(content.substring(BASE_64_PREFIX.length()))); + } else { + TestUtils.copyStringToFile(target, content); + } + + return target; + } + + private List listFiles(File directory) throws IOException { + Path base = getWorkDir().toPath(); + List result = new ArrayList<>(); + + Files.walk(directory.toPath()) + .filter(Files::isRegularFile) + .forEach(p -> { + try { + result.add(base.relativize(p).toString()); + result.add(BASE_64_PREFIX + Base64.getEncoder().encodeToString(Files.readAllBytes(p))); + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + }); + + return result; + } + private static interface Validator { public void validate(String content); }