8156962: javac should support options specified in _JAVAC_OPTIONS
Reviewed-by: vromero
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary Test -addmods and -limitmods; also test the "enabled" modules.
* @library /tools/lib
* @modules
* jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.code
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/com.sun.tools.javac.model
* jdk.compiler/com.sun.tools.javac.processing
* jdk.compiler/com.sun.tools.javac.util
* jdk.jdeps/com.sun.tools.javap
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask toolbox.JavaTask ModuleTestBase
* @run main AddLimitMods
*/
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.TypeElement;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import toolbox.JarTask;
import toolbox.JavacTask;
import toolbox.JavaTask;
import toolbox.Task;
public class AddLimitMods extends ModuleTestBase {
public static void main(String... args) throws Exception {
AddLimitMods t = new AddLimitMods();
t.runTests();
}
@Test
public void testManual(Path base) throws Exception {
Path moduleSrc = base.resolve("module-src");
Path m1 = moduleSrc.resolve("m1");
tb.writeJavaFiles(m1,
"module m1 { requires m2; requires m3; }");
Path m2 = moduleSrc.resolve("m2");
tb.writeJavaFiles(m2,
"module m2 { requires m3; exports m2; }",
"package m2; public class M2 {}");
Path m3 = moduleSrc.resolve("m3");
tb.writeJavaFiles(m3,
"module m3 { exports m3; }",
"package m3; public class M3 {}");
Path modulePath = base.resolve("module-path");
Files.createDirectories(modulePath);
new JavacTask(tb)
.options("-modulesourcepath", moduleSrc.toString())
.outdir(modulePath)
.files(findJavaFiles(m3))
.run()
.writeAll();
new JavacTask(tb)
.options("-modulesourcepath", moduleSrc.toString())
.outdir(modulePath)
.files(findJavaFiles(m2))
.run()
.writeAll();
//real test
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-XDshouldStopPolicyIfNoError=FLOW",
"-limitmods", "java.base")
.outdir(modulePath)
.files(findJavaFiles(m1))
.run(Task.Expect.FAIL)
.writeAll();
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-XDshouldStopPolicyIfNoError=FLOW",
"-limitmods", "java.base",
"-addmods", "m2")
.outdir(modulePath)
.files(findJavaFiles(m1))
.run(Task.Expect.FAIL)
.writeAll();
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-XDshouldStopPolicyIfNoError=FLOW",
"-limitmods", "java.base",
"-addmods", "m2,m3")
.outdir(modulePath)
.files(findJavaFiles(m1))
.run()
.writeAll();
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-XDshouldStopPolicyIfNoError=FLOW",
"-limitmods", "m2")
.outdir(modulePath)
.files(findJavaFiles(m1))
.run()
.writeAll();
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-XDshouldStopPolicyIfNoError=FLOW",
"-limitmods", "m3")
.outdir(modulePath)
.files(findJavaFiles(m1))
.run(Task.Expect.FAIL)
.writeAll();
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-XDshouldStopPolicyIfNoError=FLOW",
"-limitmods", "m3",
"-addmods", "m2")
.outdir(modulePath)
.files(findJavaFiles(m1))
.run()
.writeAll();
}
@Test
public void testObservableForUnnamed(Path base) throws Exception {
Path src = base.resolve("src");
tb.writeJavaFiles(src,
"package test;\n" +
"@javax.annotation.Generated(\"test\")\n" +
"public class Test {\n" +
" com.sun.tools.javac.Main m;\n" +
" javax.xml.bind.JAXBException e;\n" +
"}\n");
Path out = base.resolve("out");
Files.createDirectories(out);
for (Entry<String[], String> variant : variants) {
System.err.println("running variant: options=" + Arrays.asList(variant.getKey()) + ", expected log: " + variant.getValue());
List<String> options = new ArrayList<>();
options.add("-XDrawDiagnostics");
options.addAll(Arrays.asList(variant.getKey()));
String log = new JavacTask(tb)
.options(options.toArray(new String[0]))
.outdir(out)
.files(findJavaFiles(src))
.run(variant.getValue() == null ? Task.Expect.SUCCESS : Task.Expect.FAIL)
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
log = log.replace(System.getProperty("line.separator"), "\n");
if (variant.getValue() != null && !log.equals(variant.getValue())) {
throw new AssertionError();
}
}
}
private static final List<Entry<String[], String>> variants = Arrays.asList(
new SimpleEntry<String[], String>(new String[] {},
"Test.java:2:18: compiler.err.doesnt.exist: javax.annotation\n"
+ "Test.java:5:19: compiler.err.doesnt.exist: javax.xml.bind\n"
+ "2 errors\n"),
new SimpleEntry<String[], String>(new String[] {"-addmods", "java.annotations.common,java.xml.bind"},
null),
new SimpleEntry<String[], String>(new String[] {"-limitmods", "java.xml.ws,jdk.compiler"},
null),
new SimpleEntry<String[], String>(new String[] {"-addmods", "ALL-SYSTEM"},
null)
);
@Test
public void testAllModulePath(Path base) throws Exception {
if (Files.isDirectory(base))
tb.cleanDirectory(base);
Path moduleSrc = base.resolve("module-src");
Path m1 = moduleSrc.resolve("m1");
tb.writeJavaFiles(m1,
"module m1 { exports api; }",
"package api; public class Api { }");
Path modulePath = base.resolve("module-path");
Files.createDirectories(modulePath);
new JavacTask(tb)
.options("-modulesourcepath", moduleSrc.toString())
.outdir(modulePath)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll();
Path cpSrc = base.resolve("cp-src");
tb.writeJavaFiles(cpSrc, "package test; public class Test { api.Api api; }");
Path cpOut = base.resolve("cp-out");
Files.createDirectories(cpOut);
new JavacTask(tb)
.options("-modulepath", modulePath.toString())
.outdir(cpOut)
.files(findJavaFiles(cpSrc))
.run(Task.Expect.FAIL)
.writeAll();
new JavacTask(tb)
.options("-modulepath", modulePath.toString(),
"-addmods", "ALL-MODULE-PATH")
.outdir(cpOut)
.files(findJavaFiles(cpSrc))
.run()
.writeAll();
List<String> actual;
List<String> expected = Arrays.asList(
"- compiler.err.addmods.all.module.path.invalid",
"1 error");
actual = new JavacTask(tb)
.options("-modulesourcepath", moduleSrc.toString(),
"-XDrawDiagnostics",
"-addmods", "ALL-MODULE-PATH")
.outdir(modulePath)
.files(findJavaFiles(moduleSrc))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
if (!Objects.equals(actual, expected)) {
throw new IllegalStateException("incorrect errors; actual=" + actual + "; expected=" + expected);
}
actual = new JavacTask(tb)
.options("-Xmodule:java.base",
"-XDrawDiagnostics",
"-addmods", "ALL-MODULE-PATH")
.outdir(cpOut)
.files(findJavaFiles(cpSrc))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
if (!Objects.equals(actual, expected)) {
throw new IllegalStateException("incorrect errors; actual=" + actual + "; expected=" + expected);
}
actual = new JavacTask(tb, Task.Mode.CMDLINE)
.options("-source", "8", "-target", "8",
"-XDrawDiagnostics",
"-addmods", "ALL-MODULE-PATH")
.outdir(cpOut)
.files(findJavaFiles(cpSrc))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
if (!actual.contains("javac: option -addmods not allowed with target 1.8")) {
throw new IllegalStateException("incorrect errors; actual=" + actual);
}
tb.writeJavaFiles(cpSrc, "module m1 {}");
actual = new JavacTask(tb)
.options("-XDrawDiagnostics",
"-addmods", "ALL-MODULE-PATH")
.outdir(cpOut)
.files(findJavaFiles(cpSrc))
.run(Task.Expect.FAIL)
.writeAll()
.getOutputLines(Task.OutputKind.DIRECT);
if (!Objects.equals(actual, expected)) {
throw new IllegalStateException("incorrect errors; actual=" + actual + "; expected=" + expected);
}
}
@Test
public void testRuntime2Compile(Path base) throws Exception {
Path classpathSrc = base.resolve("classpath-src");
Path classpathOut = base.resolve("classpath-out");
tb.writeJavaFiles(classpathSrc,
generateCheckAccessibleClass("cp.CP"));
Files.createDirectories(classpathOut);
new JavacTask(tb)
.outdir(classpathOut)
.files(findJavaFiles(classpathSrc))
.run()
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
Path automaticSrc = base.resolve("automatic-src");
Path automaticOut = base.resolve("automatic-out");
tb.writeJavaFiles(automaticSrc,
generateCheckAccessibleClass("automatic.Automatic"));
Files.createDirectories(automaticOut);
new JavacTask(tb)
.outdir(automaticOut)
.files(findJavaFiles(automaticSrc))
.run()
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
Path modulePath = base.resolve("module-path");
Files.createDirectories(modulePath);
Path automaticJar = modulePath.resolve("automatic.jar");
new JarTask(tb, automaticJar)
.baseDir(automaticOut)
.files("automatic/Automatic.class")
.run();
Path moduleSrc = base.resolve("module-src");
Path m1 = moduleSrc.resolve("m1");
tb.writeJavaFiles(m1,
"module m1 { exports api; }",
"package api; public class Api { public void test() { } }");
new JavacTask(tb)
.options("-modulesourcepath", moduleSrc.toString())
.outdir(modulePath)
.files(findJavaFiles(moduleSrc))
.run()
.writeAll()
.getOutput(Task.OutputKind.DIRECT);
int index = 0;
for (String moduleInfo : MODULE_INFO_VARIANTS) {
for (String[] options : OPTIONS_VARIANTS) {
index++;
System.err.println("running check: " + moduleInfo + "; " + Arrays.asList(options));
Path m2Runtime = base.resolve(index + "-runtime").resolve("m2");
Path out = base.resolve(index + "-runtime").resolve("out").resolve("m2");
Files.createDirectories(out);
StringBuilder testClassNamed = new StringBuilder();
testClassNamed.append("package test;\n" +
"public class Test {\n" +
" public static void main(String... args) throws Exception {\n");
for (Entry<String, String> e : MODULES_TO_CHECK_TO_SAMPLE_CLASS.entrySet()) {
testClassNamed.append(" System.err.println(\"visible:" + e.getKey() + ":\" + java.lang.reflect.Layer.boot().findModule(\"" + e.getKey() + "\").isPresent());\n");
}
testClassNamed.append(" Class<?> cp = Class.forName(Test.class.getClassLoader().getUnnamedModule(), \"cp.CP\");\n");
testClassNamed.append(" cp.getDeclaredMethod(\"runMe\").invoke(null);\n");
testClassNamed.append(" Class<?> automatic = Class.forName(java.lang.reflect.Layer.boot().findModule(\"automatic\").get(), \"automatic.Automatic\");\n");
testClassNamed.append(" automatic.getDeclaredMethod(\"runMe\").invoke(null);\n");
testClassNamed.append(" }\n" +
"}");
tb.writeJavaFiles(m2Runtime, moduleInfo, testClassNamed.toString());
new JavacTask(tb)
.options("-modulepath", modulePath.toString())
.outdir(out)
.files(findJavaFiles(m2Runtime))
.run()
.writeAll();
boolean success;
String output;
try {
output = new JavaTask(tb)
.vmOptions(augmentOptions(options,
Collections.emptyList(),
"-modulepath", modulePath.toString() + File.pathSeparator + out.getParent().toString(),
"-classpath", classpathOut.toString(),
"-XaddReads:m2=ALL-UNNAMED,automatic",
"-m", "m2/test.Test"))
.run()
.writeAll()
.getOutput(Task.OutputKind.STDERR);
success = true;
} catch (Task.TaskError err) {
success = false;
output = "";
}
Path m2 = base.resolve(String.valueOf(index)).resolve("m2");
tb.writeJavaFiles(m2,
moduleInfo,
"package test;\n" +
"public class Test {}\n");
List<String> auxOptions = success ? Arrays.asList(
"-processorpath", System.getProperty("test.class.path"),
"-processor", CheckVisibleModule.class.getName(),
"-Aoutput=" + output,
"-XDaccessInternalAPI=true"
) : Collections.emptyList();
new JavacTask(tb)
.options(augmentOptions(options,
auxOptions,
"-modulepath", modulePath.toString(),
"-classpath", classpathOut.toString(),
"-XDshouldStopPolicyIfNoError=FLOW"))
.outdir(modulePath)
.files(findJavaFiles(m2))
.run(success ? Task.Expect.SUCCESS : Task.Expect.FAIL)
.writeAll();
}
}
}
private String generateCheckAccessibleClass(String fqn) {
String packageName = fqn.substring(0, fqn.lastIndexOf('.'));
String simpleName = fqn.substring(fqn.lastIndexOf('.') + 1);
StringBuilder checkClassesAccessible = new StringBuilder();
checkClassesAccessible.append("package " + packageName + ";" +
"public class " + simpleName + " {" +
" public static void runMe() throws Exception {");
for (Entry<String, String> e : MODULES_TO_CHECK_TO_SAMPLE_CLASS.entrySet()) {
checkClassesAccessible.append("try {");
checkClassesAccessible.append("Class.forName(\"" + e.getValue() + "\").newInstance();");
checkClassesAccessible.append("System.err.println(\"" + fqn + ":" + e.getKey() + ":true\");");
checkClassesAccessible.append("} catch (Exception ex) {");
checkClassesAccessible.append("System.err.println(\"" + fqn + ":" + e.getKey() + ":false\");");
checkClassesAccessible.append("}");
}
checkClassesAccessible.append(" }\n" +
"}");
return checkClassesAccessible.toString();
}
private static final Map<String, String> MODULES_TO_CHECK_TO_SAMPLE_CLASS = new LinkedHashMap<>();
static {
MODULES_TO_CHECK_TO_SAMPLE_CLASS.put("m1", "api.Api");
MODULES_TO_CHECK_TO_SAMPLE_CLASS.put("m2", "test.Test");
MODULES_TO_CHECK_TO_SAMPLE_CLASS.put("java.base", "java.lang.Object");
MODULES_TO_CHECK_TO_SAMPLE_CLASS.put("java.compiler", "javax.tools.ToolProvider");
MODULES_TO_CHECK_TO_SAMPLE_CLASS.put("jdk.compiler", "com.sun.tools.javac.Main");
};
@SupportedAnnotationTypes("*")
@SupportedOptions("output")
public static final class CheckVisibleModule extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
String expected = processingEnv.getOptions().get("output");
Set<String> expectedElements = new HashSet<>(Arrays.asList(expected.split(System.getProperty("line.separator"))));
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
Symtab syms = Symtab.instance(context);
for (Entry<String, String> e : MODULES_TO_CHECK_TO_SAMPLE_CLASS.entrySet()) {
String module = e.getKey();
ModuleElement mod = processingEnv.getElementUtils().getModuleElement(module);
String visible = "visible:" + module + ":" + (mod != null);
if (!expectedElements.contains(visible)) {
throw new AssertionError("actual: " + visible + "; expected: " + expected);
}
JavacElements javacElements = JavacElements.instance(context);
ClassSymbol unnamedClass = javacElements.getTypeElement(syms.unnamedModule, e.getValue());
String unnamed = "cp.CP:" + module + ":" + (unnamedClass != null);
if (!expectedElements.contains(unnamed)) {
throw new AssertionError("actual: " + unnamed + "; expected: " + expected);
}
ModuleElement automaticMod = processingEnv.getElementUtils().getModuleElement("automatic");
ClassSymbol automaticClass = javacElements.getTypeElement(automaticMod, e.getValue());
String automatic = "automatic.Automatic:" + module + ":" + (automaticClass != null);
if (!expectedElements.contains(automatic)) {
throw new AssertionError("actual: " + automatic + "; expected: " + expected);
}
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
public String[] augmentOptions(String[] options, List<String> auxOptions, String... baseOptions) {
List<String> all = new ArrayList<>();
all.addAll(Arrays.asList(options));
all.addAll(Arrays.asList(baseOptions));
all.addAll(auxOptions);
return all.toArray(new String[0]);
}
private static final String[] MODULE_INFO_VARIANTS = {
"module m2 { exports test; }",
"module m2 { requires m1; exports test; }",
"module m2 { requires jdk.compiler; exports test; }",
};
private static final String[][] OPTIONS_VARIANTS = {
{"-addmods", "automatic"},
{"-addmods", "m1,automatic"},
{"-addmods", "jdk.compiler,automatic"},
{"-addmods", "m1,jdk.compiler,automatic"},
{"-addmods", "ALL-SYSTEM,automatic"},
{"-limitmods", "java.base", "-addmods", "automatic"},
{"-limitmods", "java.base", "-addmods", "ALL-SYSTEM,automatic"},
{"-limitmods", "m2", "-addmods", "automatic"},
{"-limitmods", "jdk.compiler", "-addmods", "automatic"},
};
}