OpenJDK / jigsaw / jake / langtools
changeset 3372:2c1838c6e03c
8140767: adding -XaddReads option to javac
author | jlahoda |
---|---|
date | Tue, 10 Nov 2015 14:19:14 -0800 |
parents | 76aeac001089 |
children | 7c66438d4d8a 1669922f641d |
files | src/jdk.compiler/share/classes/com/sun/tools/javac/code/Directive.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/ModuleFinder.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties test/tools/javac/modules/AddReadsTest.java |
diffstat | 8 files changed, 453 insertions(+), 40 deletions(-) [+] |
line wrap: on
line diff
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Directive.java Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Directive.java Tue Nov 10 14:19:14 2015 -0800 @@ -55,7 +55,8 @@ public enum RequiresFlag { PUBLIC(0x0020), SYNTHETIC(0x1000), - MANDATED(0x8000); + MANDATED(0x8000), + EXTRA(0x10000); // overkill? move to ClassWriter? public static int value(Set<RequiresFlag> s) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ModuleFinder.java Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ModuleFinder.java Tue Nov 10 14:19:14 2015 -0800 @@ -370,7 +370,7 @@ msym.requires = requires.toList(); msym.uses = List.nil(); msym.directives = directives.toList(); - msym.flags_field |= Flags.AUTOMATIC_MODULE; + msym.flags_field |= Flags.AUTOMATIC_MODULE | Flags.ACYCLIC; } catch (IOException ex) { throw new IllegalStateException(ex); }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Tue Nov 10 14:19:14 2015 -0800 @@ -25,17 +25,14 @@ package com.sun.tools.javac.code; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.stream.Collectors; import javax.lang.model.element.ElementVisitor; import javax.tools.JavaFileObject; -import com.sun.tools.javac.code.Directive.ExportsDirective; import com.sun.tools.javac.code.Scope.WriteableScope; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.Completer; @@ -448,9 +445,14 @@ Source source = Source.instance(context); Options options = Options.instance(context); boolean noModules = options.isSet("noModules"); - java_base = source.allowModules() && !noModules //moved up here to avoid bootstraping issues related to DocEnv - ? enterModule(names.java_base) - : noModule; + if (source.allowModules() && !noModules) { + java_base = enterModule(names.java_base); + //avoid completing java.base during the Symtab initialization + java_base.completer = Completer.NULL_COMPLETER; + java_base.visiblePackages = Collections.emptySet(); + } else { + java_base = noModule; + } // Get the initial completer for ModuleSymbols from Modules moduleCompleter = Modules.instance(context).getCompleter(); @@ -557,6 +559,10 @@ List.<Type>nil(), methodClass), arrayClass); arrayClass.members().enter(arrayCloneMethod); + + if (java_base != noModule) + java_base.completer = sym -> moduleCompleter.complete(sym); //bootstrap issues + } /** Define a new class given its name and owner. @@ -601,24 +607,50 @@ return msym.unnamedPackage; } - if (msym != noModule && msym != java_base) { //avoid completing java_base too early - msym.complete(); + if (msym == noModule) { + return enterPackage(msym, flatName); + } + + msym.complete(); + + for (PackageSymbol pack : msym.visiblePackages) { + if (pack.fullname == flatName) { + return pack; + } + } + + PackageSymbol pack = getPackage(msym, flatName); + + if (pack != null && pack.exists()) + return pack; - for (PackageSymbol pack : msym.visiblePackages) { - if (pack.fullname == flatName) { - return pack; - } + boolean dependsOnUnnamed = msym.requires != null && + msym.requires.stream() + .map(rd -> rd.module) + .anyMatch(mod -> mod == unnamedModule); + + if (dependsOnUnnamed) { + //msyms depends on the unnamed module, for which we generally don't know + //the list of packages it "exports" ahead of time. So try to lookup the package in the + //current module, and in the unnamed module and see if it exists in one of them + PackageSymbol unnamedPack = getPackage(unnamedModule, flatName); + + if (unnamedPack != null && unnamedPack.exists()) + return unnamedPack; + + pack = enterPackage(msym, flatName); + pack.complete(); + if (pack.exists()) + return pack; + + unnamedPack = enterPackage(unnamedModule, flatName); + unnamedPack.complete(); + if (unnamedPack.exists()) { + msym.visiblePackages.add(unnamedPack); + return unnamedPack; } - if ((msym.flags() & AUTOMATIC_MODULE) != 0) { - //automatic modules depend on the unname module, for which we generally don't know - //the list of packages it "exports" ahead of time. So try to lookup the package in the - //unnamed module, and use it if it exists: - PackageSymbol pack = enterPackage(unnamedModule, flatName); - pack.complete(); - if (pack.exists()) - return pack; - } + return pack; } return enterPackage(msym, flatName);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Modules.java Tue Nov 10 14:19:14 2015 -0800 @@ -28,6 +28,7 @@ import java.io.IOException; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -83,6 +84,8 @@ import static com.sun.tools.javac.code.Flags.UNATTRIBUTED; import static com.sun.tools.javac.code.Kinds.Kind.MDL; import static com.sun.tools.javac.code.TypeTag.CLASS; +import com.sun.tools.javac.tree.JCTree.JCDirective; +import com.sun.tools.javac.tree.JCTree.Tag; import static com.sun.tools.javac.tree.JCTree.Tag.MODULEDEF; /** @@ -112,6 +115,8 @@ private final String addExportsOpt; private Map<ModuleSymbol, Set<ExportsDirective>> addExports; + private final String addReadsOpt; + private Map<ModuleSymbol, Set<RequiresDirective>> addReads; public static Modules instance(Context context) { Modules instance = context.get(Modules.class); @@ -145,6 +150,7 @@ jniWriter.multiModuleMode = multiModuleMode; addExportsOpt = options.get(Option.XADDEXPORTS); + addReadsOpt = options.get(Option.XADDREADS); } int depth = -1; @@ -287,9 +293,11 @@ log.useSource(prev); } } - syms.unnamedModule.completer = getUnnamedModuleCompleter(); - syms.unnamedModule.sourceLocation = StandardLocation.SOURCE_PATH; - syms.unnamedModule.classLocation = StandardLocation.CLASS_PATH; + if (syms.unnamedModule.sourceLocation == null) { + syms.unnamedModule.completer = getUnnamedModuleCompleter(); + syms.unnamedModule.sourceLocation = StandardLocation.SOURCE_PATH; + syms.unnamedModule.classLocation = StandardLocation.CLASS_PATH; + } defaultModule = syms.unnamedModule; } else { if (defaultModule == null) { @@ -331,6 +339,12 @@ Assert.check(rootModules.isEmpty()); } + if (defaultModule != syms.unnamedModule) { + syms.unnamedModule.completer = getUnnamedModuleCompleter(); + syms.unnamedModule.sourceLocation = StandardLocation.SOURCE_PATH; + syms.unnamedModule.classLocation = StandardLocation.CLASS_PATH; + } + for (JCCompilationUnit tree: trees) { tree.modle = defaultModule; } @@ -416,6 +430,7 @@ try { tree.defs.head.accept(v); completeModule(msym); + checkCyclicDependencies((JCModuleDecl) tree.defs.head); } finally { log.useSource(prev); msym.flags_field &= ~UNATTRIBUTED; @@ -459,8 +474,6 @@ ModuleSymbol msym = lookupModule(tree.moduleName); if (msym.kind != MDL) { log.error(tree.moduleName.pos(), "module.not.found", msym); - } else if ((msym.flags_field & UNATTRIBUTED) != 0) { - log.error(tree.moduleName.pos(), "cyclic.requires", msym); } else if (allRequires.contains(msym)) { log.error(tree.moduleName.pos(), "duplicate.requires", msym); } else { @@ -567,6 +580,7 @@ l.head.accept(this); } + @SuppressWarnings("unchecked") public void visitModuleDef(JCModuleDecl tree) { msym.directives = List.nil(); msym.provides = List.nil(); @@ -578,6 +592,8 @@ if (msym.requires.nonEmpty() && msym.requires.head.flags.contains(RequiresFlag.MANDATED)) msym.directives = msym.directives.prepend(msym.requires.head); + + msym.directives = msym.directives.appendList(List.from(addReads.getOrDefault(msym, Collections.emptySet()))); } public void visitExports(JCExports tree) { @@ -635,6 +651,11 @@ private void completeModule(ModuleSymbol msym) { Assert.checkNonNull(msym.requires); + + initAddReads(); + + msym.requires = msym.requires.appendList(List.from(addReads.getOrDefault(msym, Collections.emptySet()))); + Set<ModuleSymbol> readable = new HashSet<>(); Set<ModuleSymbol> requiresPublic = new HashSet<>(); if ((msym.flags() & Flags.AUTOMATIC_MODULE) == 0) { @@ -657,10 +678,8 @@ readable.addAll(s); requiresPublic.addAll(s); - //ensure unnamed modules are added (these are not requires public): - msym.requires.stream() - .map(rd -> { rd.module.complete(); return rd.module;}) - .forEach(readable::add); + //ensure the unnamed module is added (it is not requires public): + readable.add(syms.unnamedModule); } requiresPublicCache.put(msym, requiresPublic); initVisiblePackages(msym, readable); @@ -674,9 +693,7 @@ Set<ModuleSymbol> requiresPublic = requiresPublicCache.get(msym); if (requiresPublic == null) { - Assert.check((msym.flags() & Flags.AUTOMATIC_MODULE) != 0, () -> "no entry in cache for " + msym); - - //the module graph may contain cycles involving automatic modules + //the module graph may contain cycles involving automatic modules or -XaddReads edges requiresPublic = new HashSet<>(); Set<ModuleSymbol> seen = new HashSet<>(); @@ -689,10 +706,18 @@ continue; requiresPublic.add(current); current.complete(); - Assert.checkNonNull(current.requires, () -> current + ".requires == null; " + msym); - for (RequiresDirective rd : current.requires) { - if (rd.isPublic()) - todo = todo.prepend(rd.module); + Iterable<? extends RequiresDirective> requires; + if (current != syms.unnamedModule) { + Assert.checkNonNull(current.requires, () -> current + ".requires == null; " + msym); + requires = current.requires; + for (RequiresDirective rd : requires) { + if (rd.isPublic()) + todo = todo.prepend(rd.module); + } + } else { + for (ModuleSymbol mod : syms.getAllModules()) { + todo = todo.prepend(mod); + } } } @@ -783,6 +808,68 @@ } } + private void initAddReads() { + if (addReads != null) + return; + + addReads = new LinkedHashMap<>(); + + if (addReadsOpt == null) + return; + + for (String s : addReadsOpt.split(",")) { + if (s.isEmpty()) + continue; + int equals = s.indexOf('='); + if (equals == -1) { + // TODO: error: invalid target + continue; + } + String targetName = s.substring(0, equals); + ModuleSymbol msym = syms.enterModule(names.fromString(targetName)); + String source = s.substring(equals + 1); + ModuleSymbol sourceModule; + if (source.equals("ALL-UNNAMED")) { + sourceModule = syms.unnamedModule; + } else { + if (!SourceVersion.isName(source)) { + // TODO: error: invalid module name + continue; + } + sourceModule = syms.enterModule(names.fromString(source)); + } + addReads.computeIfAbsent(msym, m -> new HashSet<>()) + .add(new RequiresDirective(sourceModule, EnumSet.of(RequiresFlag.EXTRA))); + } + } + + private void checkCyclicDependencies(JCModuleDecl mod) { + for (JCDirective d : mod.directives) { + if (!d.hasTag(Tag.REQUIRES)) + continue; + JCRequires rd = (JCRequires) d; + Set<ModuleSymbol> nonSyntheticDeps = new HashSet<>(); + List<ModuleSymbol> queue = List.of(rd.directive.module); + while (queue.nonEmpty()) { + ModuleSymbol current = queue.head; + queue = queue.tail; + if (!nonSyntheticDeps.add(current)) + continue; + if ((current.flags() & Flags.ACYCLIC) != 0) + continue; + Assert.checkNonNull(current.requires, () -> current.toString()); + for (RequiresDirective dep : current.requires) { + if (!dep.flags.contains(RequiresFlag.EXTRA)) + queue = queue.prepend(dep.module); + } + } + if (nonSyntheticDeps.contains(mod.sym)) { + log.error(rd.moduleName.pos(), "cyclic.requires", rd.directive.module); + } + mod.sym.flags_field |= Flags.ACYCLIC; + } + } + // DEBUG private String toString(ModuleSymbol msym) { return msym.name + "["
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Tue Nov 10 14:19:14 2015 -0800 @@ -983,7 +983,11 @@ ModuleSymbol m = c.modle; int alenIdx = writeAttr(names.Module); - List<RequiresDirective> requires = m.requires; + ListBuffer<RequiresDirective> requires = new ListBuffer<>(); + for (RequiresDirective r: m.requires) { + if (!r.flags.contains(RequiresFlag.EXTRA)) + requires.add(r); + } databuf.appendChar(requires.size()); for (RequiresDirective r: requires) { databuf.appendChar(pool.put(r.module.name));
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Option.java Tue Nov 10 14:19:14 2015 -0800 @@ -550,6 +550,19 @@ } }, + XADDREADS("-XaddReads:", "opt.arg.addReads", "opt.addReads", EXTENDED, BASIC) { + @Override + public boolean process(OptionHelper helper, String option) { + String prev = helper.get(XADDREADS); + if (prev != null) { + helper.error("err.option.too.many", XADDREADS.text); + } + String p = option.substring(option.indexOf(':') + 1); + helper.put(XADDREADS.text, p); + return false; + } + }, + XMODULE("-Xmodule:", "opt.arg.module", "opt.module", EXTENDED, BASIC) { @Override public boolean process(OptionHelper helper, String option) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties Thu Nov 05 10:49:43 2015 -0800 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties Tue Nov 10 14:19:14 2015 -0800 @@ -286,6 +286,12 @@ <exports> is a comma-separated list of <module-name>/<package-name> javac.opt.arg.addExports=\ <exports> +javac.opt.addReads=\ + Specify additional modules to be considered as required by given modules, where \ + <additional-reads> is a comma-separated list of <target-module>/<required-module>. \ + <required-module> may be ALL-UNNAMED to require the unnamed module. +javac.opt.arg.addReads=\ + <additional-reads> javac.opt.module=\ Specify a module to which the classes being compiled belong javac.opt.arg.module=\
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/modules/AddReadsTest.java Tue Nov 10 14:19:14 2015 -0800 @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2015, 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 the -XaddReads option + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build ToolBox ModuleTestBase + * @run main AddReadsTest + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.ModuleElement.RequiresDirective; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; + +public class AddReadsTest extends ModuleTestBase { + + public static void main(String... args) throws Exception { + new AddReadsTest().runTests(); + } + + @Test + void testAddReads(Path base) throws Exception { + Path src = base.resolve("src"); + Path src_m1 = src.resolve("m1"); + tb.writeJavaFiles(src_m1, + "module m1 { exports api; }", + "package api; public class Api { }"); + Path src_m2 = src.resolve("m2"); + tb.writeJavaFiles(src_m2, + "module m2 { }", + "package test; public class Test extends api.Api { }"); + Path classes = base.resolve("classes"); + tb.createDirectories(classes); + + String log = tb.new JavacTask() + .options("-XDrawDiagnostics", + "-modulesourcepath", src.toString()) + .outdir(classes) + .files(findJavaFiles(src)) + .run(ToolBox.Expect.FAIL) + .writeAll() + .getOutput(ToolBox.OutputKind.DIRECT); + + if (!log.contains("Test.java:1:44: compiler.err.not.def.access.package.cant.access: api.Api, api")) + throw new Exception("expected output not found"); + + //test add dependencies: + tb.new JavacTask() + .options("-XaddReads:m2=m1", + "-modulesourcepath", src.toString(), + "-processor", VerifyRequires.class.getName()) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + + String decompiled = tb.new JavapTask() + .options("-verbose", classes.resolve("m2").resolve("module-info.class").toString()) + .run() + .getOutput(ToolBox.OutputKind.DIRECT); + + if (decompiled.contains("m1")) { + throw new Exception("Incorrectly refers to m1 module."); + } + + //cyclic dependencies OK when created through addReads: + tb.new JavacTask() + .options("-XaddReads:m2=m1,m1=m2", + "-modulesourcepath", src.toString()) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + + tb.writeJavaFiles(src_m2, + "module m2 { requires m1; }"); + + tb.new JavacTask() + .options("-XaddReads:m1=m2", + "-modulesourcepath", src.toString()) + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + } + + @SupportedAnnotationTypes("*") + public static final class VerifyRequires extends AbstractProcessor { + + @Override + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { + ModuleElement m2Module = processingEnv.getElementUtils().getModuleElement("m2"); + if (m2Module == null) { + throw new AssertionError("Cannot find the m2 module!"); + } + boolean foundM1 = false; + for (RequiresDirective rd : ElementFilter.requiresIn(m2Module.getDirectives())) { + foundM1 |= rd.getDependency().getSimpleName().contentEquals("m1"); + } + if (!foundM1) { + throw new AssertionError("Cannot find the dependency on m1 module!"); + } + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + } + + @Test + void testAddReadsUnnamedModule(Path base) throws Exception { + Path jar = prepareTestJar(base); + + Path moduleSrc = base.resolve("module-src"); + Path m1 = moduleSrc.resolve("m1"); + + Path classes = base.resolve("classes"); + + Files.createDirectories(classes); + + tb.writeJavaFiles(m1, + "module m1 { }", + "package impl; public class Impl { api.Api api; }"); + + tb.new JavacTask() + .options("-classpath", jar.toString(), + "-XaddReads:m1=ALL-UNNAMED", + "-XDrawDiagnostics") + .outdir(classes) + .files(findJavaFiles(moduleSrc)) + .run() + .writeAll(); + } + + @Test + void testAddReadsUnnamedModulePackageConflict(Path base) throws Exception { + Path jar = prepareTestJar(base); + + Path moduleSrc = base.resolve("module-src"); + Path m1 = moduleSrc.resolve("m1"); + + Path classes = base.resolve("classes"); + + Files.createDirectories(classes); + + tb.writeJavaFiles(m1, + "module m1 { }", + "package api; public class Api { public static void test() { } }", + "package impl; public class Impl { { api.Api.test(); } }"); + + tb.new JavacTask() + .options("-classpath", jar.toString(), + "-modulesourcepath", moduleSrc.toString(), + "-XaddReads:m1=ALL-UNNAMED", + "-XDrawDiagnostics") + .outdir(classes) + .files(m1.resolve("impl").resolve("Impl.java")) + .run() + .writeAll(); + } + + @Test + void testAddReadsUnnamedToJavaBase(Path base) throws Exception { + Path jar = prepareTestJar(base); + Path src = base.resolve("src"); + Path classes = base.resolve("classes"); + + Files.createDirectories(classes); + + tb.writeJavaFiles(src, + "package impl; public class Impl { api.Api a; }"); + + tb.new JavacTask() + .options("-classpath", jar.toString(), + "-XaddReads:java.base=ALL-UNNAMED", + "-Xmodule:java.base") + .outdir(classes) + .files(src.resolve("impl").resolve("Impl.java")) + .run() + .writeAll(); + } + + @Test + void testAddReadsToJavaBase(Path base) throws Exception { + Path src = base.resolve("src"); + Path classes = base.resolve("classes"); + + Files.createDirectories(classes); + + tb.writeJavaFiles(src, + "package impl; public class Impl { javax.swing.JButton b; }"); + + tb.new JavacTask() + .options("-XaddReads:java.base=java.desktop", + "-Xmodule:java.base") + .outdir(classes) + .files(findJavaFiles(src)) + .run() + .writeAll(); + } + + private Path prepareTestJar(Path base) throws Exception { + Path legacySrc = base.resolve("legacy-src"); + tb.writeJavaFiles(legacySrc, + "package api; public abstract class Api {}"); + Path legacyClasses = base.resolve("legacy-classes"); + Files.createDirectories(legacyClasses); + + String log = tb.new JavacTask() + .options() + .outdir(legacyClasses) + .files(findJavaFiles(legacySrc)) + .run() + .writeAll() + .getOutput(ToolBox.OutputKind.DIRECT); + + if (!log.isEmpty()) { + throw new Exception("unexpected output: " + log); + } + + Path lib = base.resolve("lib"); + + Files.createDirectories(lib); + + Path jar = lib.resolve("test-api-1.0.jar"); + + tb.new JarTask(jar) + .baseDir(legacyClasses) + .files("api/Api.class") + .run(); + + return jar; + } +}