Use reflection in Ant task to invoke compiler

This will allow a more controlled management of the runtime that the compiler
is linked against. Incidentally this also allows Ant task to use any of
compiler arguments via <compilerarg> because Ant task is now just a facade for
the CLI compiler.

The test "wrongArguments" is deleted because the full compiler usage is now
printed out on a wrong <compilerarg>, and this will become inconvenient to
update with each change in compiler arguments

 #KT-5618 Fixed
This commit is contained in:
Alexander Udalov
2014-12-11 22:32:14 +03:00
parent 3bde619ca3
commit 14dab749a2
10 changed files with 102 additions and 89 deletions
+1 -5
View File
@@ -7,11 +7,7 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="cli" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="backend" />
<orderEntry type="module" module-name="util" />
<orderEntry type="library" name="ant-1.7" level="project" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
</component>
</module>
@@ -17,13 +17,10 @@
package org.jetbrains.jet.buildtools.ant
import org.apache.tools.ant.types.Path
import org.jetbrains.jet.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.jet.cli.js.K2JSCompiler
import java.io.File
public class Kotlin2JsTask : KotlinCompilerBaseTask<K2JSCompilerArguments>() {
override val arguments = K2JSCompilerArguments()
override val compiler = K2JSCompiler()
public class Kotlin2JsTask : KotlinCompilerBaseTask() {
override val compilerFqName = "org.jetbrains.jet.cli.js.K2JSCompiler"
public var library: Path? = null
public var outputPrefix: File? = null
@@ -48,19 +45,31 @@ public class Kotlin2JsTask : KotlinCompilerBaseTask<K2JSCompilerArguments>() {
}
override fun fillSpecificArguments() {
arguments.outputFile = output!!.canonicalPath
arguments.noStdlib = noStdlib
args.add("-output")
args.add(output!!.canonicalPath)
// TODO: write test
library?.let {
arguments.libraryFiles = it.list().map { File(it).canonicalPath }.copyToArray()
args.add("-library-files")
args.add(it.list().map { File(it).canonicalPath }.join(separator = ","))
}
arguments.outputPrefix = outputPrefix?.canonicalPath
arguments.outputPostfix = outputPostfix?.canonicalPath
outputPrefix?.let {
args.add("-output-prefix")
args.add(it.canonicalPath)
}
arguments.main = main
arguments.sourceMap = sourceMap
outputPostfix?.let {
args.add("-output-postfix")
args.add(it.canonicalPath)
}
main?.let {
args.add("-main")
args.add(it)
}
if (noStdlib) args.add("-no-stdlib")
if (sourceMap) args.add("-source-map")
}
}
@@ -18,14 +18,11 @@ package org.jetbrains.jet.buildtools.ant
import org.apache.tools.ant.types.Path
import org.apache.tools.ant.types.Reference
import org.jetbrains.jet.cli.jvm.K2JVMCompiler
import org.jetbrains.jet.cli.common.arguments.K2JVMCompilerArguments
import java.io.File
import java.io.File.pathSeparator
public class Kotlin2JvmTask : KotlinCompilerBaseTask<K2JVMCompilerArguments>() {
override val arguments = K2JVMCompilerArguments()
override val compiler = K2JVMCompiler()
public class Kotlin2JvmTask : KotlinCompilerBaseTask() {
override val compilerFqName = "org.jetbrains.jet.cli.jvm.K2JVMCompiler"
public var externalAnnotations: Path? = null
public var includeRuntime: Boolean = true
@@ -60,21 +57,27 @@ public class Kotlin2JvmTask : KotlinCompilerBaseTask<K2JVMCompilerArguments>() {
}
override fun fillSpecificArguments() {
arguments.destination = output!!.canonicalPath
args.add("-d")
args.add(output!!.canonicalPath)
val classpath = arrayListOf<String>()
compileClasspath?.let { classpath.addAll(it.list()) }
arguments.freeArgs?.forEach {
src!!.list().forEach {
val file = File(it)
if (file.isDirectory() || file.extension != "kt") {
classpath.add(it)
}
}
arguments.classpath = classpath.join(pathSeparator)
args.add("-classpath")
args.add(classpath.join(pathSeparator))
arguments.annotations = externalAnnotations?.list()?.join(pathSeparator)
arguments.noStdlib = noStdlib
arguments.includeRuntime = includeRuntime
externalAnnotations?.let {
args.add("-annotations")
args.add(it.list().join(pathSeparator))
}
if (noStdlib) args.add("-no-stdlib")
if (includeRuntime) args.add("-include-runtime")
}
}
@@ -21,22 +21,49 @@ import org.apache.tools.ant.types.Path
import org.apache.tools.ant.types.Reference
import java.io.File
import org.apache.tools.ant.BuildException
import org.jetbrains.jet.cli.common.ExitCode
import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.jet.cli.common.CLICompiler
import org.apache.tools.ant.types.Commandline
import com.sampullara.cli.Args
import org.jetbrains.jet.cli.common.messages.PrintingMessageCollector
import org.jetbrains.jet.cli.common.messages.MessageRenderer
import org.jetbrains.jet.config.Services
import java.io.PrintStream
import org.apache.tools.ant.AntClassLoader
import java.lang.ref.SoftReference
import org.apache.tools.ant.Project
import java.net.JarURLConnection
object CompilerClassLoaderHolder {
private var classLoaderRef = SoftReference<ClassLoader?>(null)
synchronized fun getOrCreateClassLoader(project: Project?): ClassLoader {
val cached = classLoaderRef.get()
if (cached != null) return cached
val myLoader = javaClass.getClassLoader()
if (myLoader !is AntClassLoader) return myLoader
// Find path of kotlin-ant.jar in the filesystem and find kotlin-compiler.jar in the same directory
val resourcePath = "/" + javaClass.getName().replace('.', '/') + ".class"
val jarConnection = javaClass.getResource(resourcePath).openConnection() as? JarURLConnection
?: throw UnsupportedOperationException("Kotlin compiler Ant task should be loaded from the JAR file")
val antTaskJarPath = File(jarConnection.getJarFileURL().toURI())
val compilerJarPath = File(antTaskJarPath.getParent(), "kotlin-compiler.jar")
if (!compilerJarPath.exists()) {
throw IllegalStateException("kotlin-compiler.jar is not found in the directory of Kotlin Ant task")
}
val classLoader = AntClassLoader(myLoader, project, Path(project, compilerJarPath.path), /* parentFirst = */ false)
classLoaderRef = SoftReference(classLoader)
return classLoader
}
}
/**
* Base class for Kotlin compiler Ant tasks.
* http://evgeny-goldin.org/javadoc/ant/tutorial-writing-tasks.html
*/
public abstract class KotlinCompilerBaseTask<T : CommonCompilerArguments> : Task() {
protected abstract val arguments: T
protected abstract val compiler: CLICompiler<T>
public abstract class KotlinCompilerBaseTask : Task() {
protected abstract val compilerFqName: String
protected val args: MutableList<String> = arrayListOf()
public var src: Path? = null
public var output: File? = null
@@ -73,21 +100,15 @@ public abstract class KotlinCompilerBaseTask<T : CommonCompilerArguments> : Task
private fun fillArguments() {
val sourcePaths = src ?: throw BuildException("\"src\" should be specified")
arguments.freeArgs = sourcePaths.list().map { File(it).canonicalPath }
args.addAll(sourcePaths.list().map { File(it).canonicalPath })
output ?: throw BuildException("\"output\" should be specified")
arguments.suppressWarnings = nowarn
arguments.verbose = verbose
arguments.version = printVersion
if (nowarn) args.add("-nowarn")
if (verbose) args.add("-verbose")
if (printVersion) args.add("-version")
val args = additionalArguments.flatMap { it.getParts().toList() }
try {
Args.parse(arguments, args.copyToArray())
}
catch (e: IllegalArgumentException) {
throw BuildException(e.getMessage())
}
args.addAll(additionalArguments.flatMap { it.getParts().toList() })
fillSpecificArguments()
}
@@ -95,13 +116,17 @@ public abstract class KotlinCompilerBaseTask<T : CommonCompilerArguments> : Task
final override fun execute() {
fillArguments()
log("Compiling ${arguments.freeArgs} => [${output!!.canonicalPath}]");
val compilerClass = CompilerClassLoaderHolder.getOrCreateClassLoader(project).loadClass(compilerFqName)
val compiler = compilerClass.newInstance()
val exec = compilerClass.getMethod("execFullPathsInMessages", javaClass<PrintStream>(), javaClass<Array<String>>())
val collector = PrintingMessageCollector(System.err, MessageRenderer.PLAIN, arguments.verbose)
val exitCode = compiler.exec(collector, Services.EMPTY, arguments)
log("Compiling ${src!!.list().toList()} => [${output!!.canonicalPath}]");
if (exitCode != ExitCode.OK) {
throw BuildException("Compilation finished with exit code $exitCode")
val exitCode = exec(compiler, System.err, args.copyToArray())
// TODO: support failOnError attribute of javac
if ((exitCode as Enum<*>).ordinal() != 0) {
throw BuildException("Compile failed; see the compiler error output for details.")
}
}
}
+1 -2
View File
@@ -624,7 +624,6 @@
</src>
<compilerarg value="-Xlint:all"/>
<classpath>
<fileset dir="${kotlin-home}/lib" includes="kotlin-compiler.jar"/>
<file file="${bootstrap.runtime}"/>
<fileset dir="${dependencies.dir}/ant-1.7/lib" includes="ant.jar"/>
</classpath>
@@ -642,7 +641,7 @@
<attribute name="Implementation-Title" value="${manifest.impl.title.kotlin.compiler.ant.task}"/>
<attribute name="Implementation-Version" value="${build.number}"/>
<attribute name="Class-Path" value="kotlin-compiler.jar"/>
<attribute name="Class-Path" value="kotlin-runtime.jar"/>
</manifest>
</jar>
</target>
@@ -58,6 +58,12 @@ public abstract class CLICompiler<A extends CommonCompilerArguments> {
return exec(errStream, services, MessageRenderer.TAGS, args);
}
@SuppressWarnings("UnusedDeclaration") // Used via reflection in KotlinCompilerBaseTask
@NotNull
public ExitCode execFullPathsInMessages(@NotNull PrintStream errStream, @NotNull String[] args) {
return exec(errStream, Services.EMPTY, MessageRenderer.PLAIN, args);
}
@Nullable
private A parseArguments(@NotNull PrintStream errStream, @NotNull MessageRenderer messageRenderer, @NotNull String[] args) {
try {
@@ -17,11 +17,13 @@
package org.jetbrains.kotlin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.utils.UtilsPackage;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import java.io.File;
import java.util.Arrays;
public class AntTaskJvmTest extends AntTaskBaseTest {
private static final String JVM_OUT_FILE = "hello.jar";
@@ -44,7 +46,11 @@ public class AntTaskJvmTest extends AntTaskBaseTest {
}
private static String getClassPathForAnt() {
return getCompilerLib() + File.separator + "kotlin-ant.jar" + File.pathSeparator + getKotlinRuntimePath();
return UtilsPackage.join(Arrays.asList(
getCompilerLib() + File.separator + "kotlin-ant.jar",
getCompilerLib() + File.separator + "kotlin-compiler.jar",
getKotlinRuntimePath()
), File.pathSeparator);
}
private static String getIdeaSdkHome() {
@@ -61,11 +67,6 @@ public class AntTaskJvmTest extends AntTaskBaseTest {
doJvmAntTest();
}
@Test
public void wrongArguments() throws Exception {
doAntTest(FAILED);
}
@Test
public void jvmClasspath() throws Exception {
doJvmAntTest();
@@ -1,13 +0,0 @@
OUT:
Buildfile: [TestData]/build.xml
build:
ERR:
BUILD FAILED
[TestData]/build.xml:5: Invalid argument: -option-never-to-be-supported
Total time: [time]
Return code: 1
@@ -1,9 +0,0 @@
<project name="Ant Task Test" default="build">
<taskdef resource="org/jetbrains/jet/buildtools/ant/antlib.xml" classpath="${kotlin.lib}/kotlin-ant.jar"/>
<target name="build">
<kotlinc src="${test.data}/hello.kt" output="${temp}/hello.jar">
<compilerarg value="-option-never-to-be-supported"/>
</kotlinc>
</target>
</project>
@@ -1,4 +0,0 @@
package hello
fun main(args : Array<String>) {
}