diff --git a/.idea/libraries/test_lib.xml b/.idea/libraries/test_lib.xml index 732f2d2d418..5b92e91060a 100644 --- a/.idea/libraries/test_lib.xml +++ b/.idea/libraries/test_lib.xml @@ -5,13 +5,13 @@ - + @@ -21,4 +21,4 @@ - + \ No newline at end of file diff --git a/src/com/facebook/buck/android/KotlinAndroidLibraryCompiler.java b/src/com/facebook/buck/android/KotlinAndroidLibraryCompiler.java index eaf565294ef..4b65b597bf8 100644 --- a/src/com/facebook/buck/android/KotlinAndroidLibraryCompiler.java +++ b/src/com/facebook/buck/android/KotlinAndroidLibraryCompiler.java @@ -43,8 +43,6 @@ public CompileToJarStepFactory compileToJar( JavacOptions javacOptions, BuildRuleResolver resolver) { return new KotlincToJarStepFactory( - kotlinBuckConfig.getKotlinCompiler().get(), - ImmutableList.of(), - ANDROID_CLASSPATH_FROM_CONTEXT); + kotlinBuckConfig.getKotlinc(), ImmutableList.of(), ANDROID_CLASSPATH_FROM_CONTEXT); } } diff --git a/src/com/facebook/buck/jvm/kotlin/AbstractKotlincVersion.java b/src/com/facebook/buck/jvm/kotlin/AbstractKotlincVersion.java new file mode 100644 index 00000000000..cbb0dc44c09 --- /dev/null +++ b/src/com/facebook/buck/jvm/kotlin/AbstractKotlincVersion.java @@ -0,0 +1,31 @@ +/* + * Copyright 2016-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.facebook.buck.jvm.kotlin; + +import com.facebook.buck.util.immutables.BuckStyleImmutable; +import org.immutables.value.Value; + +@Value.Immutable +@BuckStyleImmutable +abstract class AbstractKotlincVersion { + @Value.Parameter + public abstract String getVersionString(); + + @Override + public String toString() { + return getVersionString(); + } +} diff --git a/src/com/facebook/buck/jvm/kotlin/BUCK b/src/com/facebook/buck/jvm/kotlin/BUCK index d3acd356868..d7357fad833 100644 --- a/src/com/facebook/buck/jvm/kotlin/BUCK +++ b/src/com/facebook/buck/jvm/kotlin/BUCK @@ -6,6 +6,8 @@ java_immutables_library( ], visibility = ["PUBLIC"], deps = [ + "//src/com/facebook/buck/android:apkmodule", + "//src/com/facebook/buck/android:packageable", "//src/com/facebook/buck/cli:config", "//src/com/facebook/buck/io:executable-finder", "//src/com/facebook/buck/io:io", @@ -23,13 +25,20 @@ java_immutables_library( "//src/com/facebook/buck/rules:rules", "//src/com/facebook/buck/rules:source_path", "//src/com/facebook/buck/rules/args:args", + "//src/com/facebook/buck/rules/keys:keys", "//src/com/facebook/buck/rules/macros:macros", "//src/com/facebook/buck/shell:steps", "//src/com/facebook/buck/step:step", "//src/com/facebook/buck/util:exceptions", + "//src/com/facebook/buck/util:io", + "//src/com/facebook/buck/util:process_executor", + "//src/com/facebook/buck/util:util", + "//src/com/facebook/buck/util/environment:platform", + "//src/com/facebook/buck/util/immutables:immutables", "//src/com/facebook/buck/versions:versions", "//third-party/java/guava:guava", "//third-party/java/infer-annotations:infer-annotations", + "//third-party/java/jackson:jackson-annotations", "//third-party/java/jsr:jsr305", ], ) diff --git a/src/com/facebook/buck/jvm/kotlin/ExternalKotlinc.java b/src/com/facebook/buck/jvm/kotlin/ExternalKotlinc.java new file mode 100644 index 00000000000..40fcef4c9fb --- /dev/null +++ b/src/com/facebook/buck/jvm/kotlin/ExternalKotlinc.java @@ -0,0 +1,175 @@ +/* + * Copyright 2016-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.facebook.buck.jvm.kotlin; + +import static com.google.common.collect.Iterables.transform; + +import com.facebook.buck.io.ProjectFilesystem; +import com.facebook.buck.model.BuildTarget; +import com.facebook.buck.rules.BuildRule; +import com.facebook.buck.rules.RuleKeyObjectSink; +import com.facebook.buck.rules.SourcePath; +import com.facebook.buck.rules.SourcePathResolver; +import com.facebook.buck.rules.SourcePathRuleFinder; +import com.facebook.buck.step.ExecutionContext; +import com.facebook.buck.util.Console; +import com.facebook.buck.util.DefaultProcessExecutor; +import com.facebook.buck.util.ProcessExecutor; +import com.facebook.buck.util.ProcessExecutorParams; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedSet; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +public class ExternalKotlinc implements Kotlinc { + + private static final KotlincVersion DEFAULT_VERSION = KotlincVersion.of("unknown version"); + + private final Path pathToKotlinc; + private final Supplier version; + + public ExternalKotlinc(final Path pathToKotlinc) { + this.pathToKotlinc = pathToKotlinc; + + this.version = + Suppliers.memoize( + () -> { + ProcessExecutorParams params = + ProcessExecutorParams.builder() + .setCommand(ImmutableList.of(pathToKotlinc.toString(), "-version")) + .build(); + ProcessExecutor.Result result; + try { + result = createProcessExecutor().launchAndExecute(params); + } catch (InterruptedException | IOException e) { + throw new RuntimeException(e); + } + Optional stderr = result.getStderr(); + String output = stderr.orElse("").trim(); + if (Strings.isNullOrEmpty(output)) { + return DEFAULT_VERSION; + } else { + return KotlincVersion.of(output); + } + }); + } + + @Override + public void appendToRuleKey(RuleKeyObjectSink sink) { + if (DEFAULT_VERSION.equals(getVersion())) { + // What we really want to do here is use a VersionedTool, however, this will suffice for now. + sink.setReflectively("kotlinc", getShortName()); + } else { + sink.setReflectively("kotlinc.version", getVersion().toString()); + } + } + + @Override + public ImmutableCollection getDeps(SourcePathRuleFinder ruleFinder) { + return ruleFinder.filterBuildRuleInputs(getInputs()); + } + + @Override + public ImmutableCollection getInputs() { + return ImmutableSortedSet.of(); + } + + @Override + public ImmutableList getCommandPrefix(SourcePathResolver resolver) { + return ImmutableList.of(pathToKotlinc.toString()); + } + + @Override + public KotlincVersion getVersion() { + return version.get(); + } + + @Override + public int buildWithClasspath( + ExecutionContext context, + BuildTarget invokingRule, + ImmutableList options, + ImmutableSortedSet kotlinSourceFilePaths, + Path pathToSrcsList, + Optional workingDirectory, + ProjectFilesystem projectFilesystem) + throws InterruptedException { + + ImmutableList command = + ImmutableList.builder() + .add(pathToKotlinc.toString()) + .addAll( + transform( + kotlinSourceFilePaths, + path -> projectFilesystem.resolve(path).toAbsolutePath().toString())) + .build(); + + // Run the command + int exitCode = -1; + try { + ProcessExecutorParams params = + ProcessExecutorParams.builder() + .setCommand(command) + .setEnvironment(context.getEnvironment()) + .setDirectory(projectFilesystem.getRootPath().toAbsolutePath()) + .build(); + ProcessExecutor.Result result = context.getProcessExecutor().launchAndExecute(params); + exitCode = result.getExitCode(); + } catch (IOException e) { + e.printStackTrace(context.getStdErr()); + return exitCode; + } + + return exitCode; + } + + @VisibleForTesting + ProcessExecutor createProcessExecutor() { + return new DefaultProcessExecutor(Console.createNullConsole()); + } + + @Override + public String getDescription( + ImmutableList options, + ImmutableSortedSet kotlinSourceFilePaths, + Path pathToSrcsList) { + StringBuilder builder = new StringBuilder(getShortName()); + builder.append(" "); + Joiner.on(" ").appendTo(builder, options); + builder.append(" "); + builder.append("@").append(pathToSrcsList); + + return builder.toString(); + } + + @Override + public String getShortName() { + return pathToKotlinc.toString(); + } + + @Override + public ImmutableMap getEnvironment(SourcePathResolver resolver) { + return ImmutableMap.of(); + } +} diff --git a/src/com/facebook/buck/jvm/kotlin/JarBackedReflectedKotlinc.java b/src/com/facebook/buck/jvm/kotlin/JarBackedReflectedKotlinc.java new file mode 100644 index 00000000000..a51e64fb4f8 --- /dev/null +++ b/src/com/facebook/buck/jvm/kotlin/JarBackedReflectedKotlinc.java @@ -0,0 +1,199 @@ +/* + * Copyright 2017-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.facebook.buck.jvm.kotlin; + +import static com.google.common.collect.Iterables.transform; + +import com.facebook.buck.io.ProjectFilesystem; +import com.facebook.buck.model.BuildTarget; +import com.facebook.buck.rules.BuildRule; +import com.facebook.buck.rules.RuleKeyObjectSink; +import com.facebook.buck.rules.SourcePath; +import com.facebook.buck.rules.SourcePathResolver; +import com.facebook.buck.rules.SourcePathRuleFinder; +import com.facebook.buck.step.ExecutionContext; +import com.facebook.buck.util.ClassLoaderCache; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import java.io.File; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class JarBackedReflectedKotlinc implements Kotlinc { + + private static final String COMPILER_CLASS = "org.jetbrains.kotlin.cli.jvm.K2JVMCompiler"; + private static final String EXIT_CODE_CLASS = "org.jetbrains.kotlin.cli.common.ExitCode"; + private static final KotlincVersion VERSION = KotlincVersion.of("in memory"); + + private static final Function PATH_TO_URL = + p -> { + try { + return p.toUri().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + }; + + // Used to hang onto the KotlinDaemonShim for the life of the buckd process + private static final Map, Object> kotlinShims = new ConcurrentHashMap<>(); + + private final ImmutableSet compilerClassPath; + + JarBackedReflectedKotlinc(ImmutableSet compilerClassPath) { + this.compilerClassPath = compilerClassPath; + } + + @Override + public KotlincVersion getVersion() { + return VERSION; + } + + @Override + public String getDescription( + ImmutableList options, + ImmutableSortedSet javaSourceFilePaths, + Path pathToSrcsList) { + StringBuilder builder = new StringBuilder("kotlinc "); + Joiner.on(" ").appendTo(builder, options); + builder.append(" "); + builder.append("@").append(pathToSrcsList); + + return builder.toString(); + } + + @Override + public String getShortName() { + return "kotlinc"; + } + + @Override + public ImmutableList getCommandPrefix(SourcePathResolver resolver) { + throw new UnsupportedOperationException("In memory kotlinc may not be used externally"); + } + + @Override + public int buildWithClasspath( + ExecutionContext context, + BuildTarget invokingRule, + ImmutableList options, + ImmutableSortedSet kotlinSourceFilePaths, + Path pathToSrcsList, + Optional workingDirectory, + ProjectFilesystem projectFilesystem) + throws InterruptedException { + + ImmutableList args = + ImmutableList.builder() + .addAll(options) + .addAll( + transform( + kotlinSourceFilePaths, + path -> projectFilesystem.resolve(path).toAbsolutePath().toString())) + .build(); + + Set compilerIdPaths = + compilerClassPath.stream().map(Path::toFile).collect(Collectors.toSet()); + + try { + Object compilerShim = + kotlinShims.computeIfAbsent( + compilerIdPaths.stream().map(File::getAbsolutePath).collect(Collectors.toSet()), + k -> loadCompilerShim(context)); + + Method compile = compilerShim.getClass().getMethod("exec", PrintStream.class, String[].class); + + Class exitCodeClass = compilerShim.getClass().getClassLoader().loadClass(EXIT_CODE_CLASS); + + Method getCode = exitCodeClass.getMethod("getCode"); + + try (UncloseablePrintStream stdErr = new UncloseablePrintStream(context.getStdErr())) { + Object exitCode = compile.invoke(compilerShim, stdErr, args.toArray(new String[0])); + + return (Integer) getCode.invoke(exitCode); + } + + } catch (IllegalAccessException + | InvocationTargetException + | NoSuchMethodException + | ClassNotFoundException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public ImmutableCollection getDeps(SourcePathRuleFinder ruleFinder) { + return ruleFinder.filterBuildRuleInputs(getInputs()); + } + + @Override + public ImmutableCollection getInputs() { + return ImmutableSet.of(); + } + + @Override + public void appendToRuleKey(RuleKeyObjectSink sink) { + sink.setReflectively("kotlinc", "jar-backed") + .setReflectively("kotlinc.version", "in-memory") + .setReflectively("kotlinc.classpath", compilerClassPath.toString()); + } + + private Object loadCompilerShim(ExecutionContext context) { + try { + ClassLoaderCache classLoaderCache = context.getClassLoaderCache(); + classLoaderCache.addRef(); + + ClassLoader classLoader = + classLoaderCache.getClassLoaderForClassPath( + null /* parent classloader */, + ImmutableList.copyOf(compilerClassPath.stream().map(PATH_TO_URL).iterator())); + + return classLoader.loadClass(COMPILER_CLASS).newInstance(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public ImmutableMap getEnvironment(SourcePathResolver resolver) { + throw new UnsupportedOperationException("In memory kotlinc may not be used externally"); + } + + private static class UncloseablePrintStream extends PrintStream { + UncloseablePrintStream(PrintStream delegate) { + super(delegate); + } + + @Override + public void close() { + // ignore + } + } +} diff --git a/src/com/facebook/buck/jvm/kotlin/KotlinBuckConfig.java b/src/com/facebook/buck/jvm/kotlin/KotlinBuckConfig.java index c0bfc8d66ea..0c1ee979255 100644 --- a/src/com/facebook/buck/jvm/kotlin/KotlinBuckConfig.java +++ b/src/com/facebook/buck/jvm/kotlin/KotlinBuckConfig.java @@ -18,13 +18,8 @@ import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.io.ExecutableFinder; -import com.facebook.buck.model.Either; -import com.facebook.buck.rules.HashedFileTool; -import com.facebook.buck.rules.SourcePath; -import com.facebook.buck.rules.Tool; import com.facebook.buck.util.HumanReadableException; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -44,12 +39,18 @@ public KotlinBuckConfig(BuckConfig delegate) { this.delegate = delegate; } - /** - * Get the Tool instance for the Kotlin compiler. - * - * @return the Kotlin compiler Tool - */ - public Supplier getKotlinCompiler() { + public Kotlinc getKotlinc() { + if (isExternalCompilation()) { + return new ExternalKotlinc(getPathToCompilerBinary()); + } else { + ImmutableSet classpathEntries = + ImmutableSet.of(getPathToStdlibJar(), getPathToCompilerJar()); + + return new JarBackedReflectedKotlinc(classpathEntries); + } + } + + Path getPathToCompilerBinary() { Path compilerPath = getKotlinHome().resolve("kotlinc"); if (!Files.isExecutable(compilerPath)) { compilerPath = getKotlinHome().resolve(Paths.get("bin", "kotlinc")); @@ -58,8 +59,7 @@ public Supplier getKotlinCompiler() { } } - Path compiler = new ExecutableFinder().getExecutable(compilerPath, delegate.getEnvironment()); - return Suppliers.ofInstance(new HashedFileTool(compiler)); + return new ExecutableFinder().getExecutable(compilerPath, delegate.getEnvironment()); } /** @@ -67,58 +67,114 @@ public Supplier getKotlinCompiler() { * * @return the Kotlin runtime jar path */ - public Either getPathToRuntimeJar() { - Optional value = delegate.getValue(SECTION, "runtime_jar"); + Path getPathToStdlibJar() { + Path stdlib = getKotlinHome().resolve("kotlin-stdlib.jar"); + if (Files.isRegularFile(stdlib)) { + return stdlib.normalize(); + } - if (value.isPresent()) { - boolean isAbsolute = Paths.get(value.get()).isAbsolute(); - if (isAbsolute) { - return Either.ofRight(delegate.getPath(SECTION, "runtime_jar", false).get().normalize()); - } else { - return Either.ofLeft(delegate.getSourcePath(SECTION, "runtime_jar").get()); - } + stdlib = getKotlinHome().resolve(Paths.get("lib", "kotlin-stdlib.jar")); + if (Files.isRegularFile(stdlib)) { + return stdlib.normalize(); + } + + stdlib = getKotlinHome().resolve(Paths.get("libexec", "lib", "kotlin-stdlib.jar")); + if (Files.isRegularFile(stdlib)) { + return stdlib.normalize(); + } + + // Support for Kotlin < 1.1 ... kotlin-stdlib used to be kotlin-runtime. + stdlib = getKotlinHome().resolve("kotlin-runtime.jar"); + if (Files.isRegularFile(stdlib)) { + return stdlib.normalize(); + } + + stdlib = getKotlinHome().resolve(Paths.get("lib", "kotlin-runtime.jar")); + if (Files.isRegularFile(stdlib)) { + return stdlib.normalize(); + } + + stdlib = getKotlinHome().resolve(Paths.get("libexec", "lib", "kotlin-runtime.jar")); + if (Files.isRegularFile(stdlib)) { + return stdlib.normalize(); } - Path runtime = getKotlinHome().resolve("kotlin-runtime.jar"); - if (Files.isRegularFile(runtime)) { - return Either.ofRight(runtime.toAbsolutePath().normalize()); + throw new HumanReadableException( + "Could not resolve kotlin stdlib JAR location (kotlin home:" + getKotlinHome() + ")."); + } + + /** + * Get the path to the Kotlin compiler jar. + * + * @return the Kotlin compiler jar path + */ + Path getPathToCompilerJar() { + Path compiler = getKotlinHome().resolve("kotlin-compiler.jar"); + if (Files.isRegularFile(compiler)) { + return compiler.normalize(); } - runtime = getKotlinHome().resolve(Paths.get("lib", "kotlin-runtime.jar")); - if (Files.isRegularFile(runtime)) { - return Either.ofRight(runtime.toAbsolutePath().normalize()); + compiler = getKotlinHome().resolve(Paths.get("lib", "kotlin-compiler.jar")); + if (Files.isRegularFile(compiler)) { + return compiler.normalize(); } - throw new HumanReadableException("Could not resolve kotlin runtime JAR location."); + compiler = getKotlinHome().resolve(Paths.get("libexec", "lib", "kotlin-compiler.jar")); + if (Files.isRegularFile(compiler)) { + return compiler.normalize(); + } + + throw new HumanReadableException( + "Could not resolve kotlin compiler JAR location (kotlin home:" + getKotlinHome() + ")."); + } + + /** + * Determine whether external Kotlin compilation is being forced. The default is internal + * (in-process) execution, but this can be overridden in .buckconfig by setting the "external" + * property to "true". + * + * @return true is external compilation is requested, false otherwise + */ + private boolean isExternalCompilation() { + Optional value = delegate.getBoolean(SECTION, "external"); + return value.orElse(false); } + /** + * Find the Kotlin home (installation) directory by searching in this order:
+ * + *
    + *
  • If the "kotlin_home" directory is specified in .buckconfig then use it. + *
  • Check the environment for a KOTLIN_HOME variable, if defined then use it. + *
  • Resolve "kotlinc" with an ExecutableFinder, and if found then deduce the kotlin home + * directory from it. + *
+ * + * @return the Kotlin home path + */ private Path getKotlinHome() { if (kotlinHome != null) { return kotlinHome; } try { - // Check the buck configuration for a specified kotlin compliler - Optional value = delegate.getValue(SECTION, "compiler"); - boolean isAbsolute = (value.isPresent() && Paths.get(value.get()).isAbsolute()); - Optional compilerPath = delegate.getPath(SECTION, "compiler", !isAbsolute); - if (compilerPath.isPresent()) { - if (Files.isExecutable(compilerPath.get())) { - kotlinHome = compilerPath.get().toRealPath().getParent().normalize(); - if (kotlinHome != null && kotlinHome.endsWith(Paths.get("bin"))) { - kotlinHome = kotlinHome.getParent().normalize(); - } - return kotlinHome; + // Check the buck configuration for a specified kotlin home + Optional value = delegate.getValue(SECTION, "kotlin_home"); + + if (value.isPresent()) { + boolean isAbsolute = Paths.get(value.get()).isAbsolute(); + Optional homePath = delegate.getPath(SECTION, "kotlin_home", !isAbsolute); + if (homePath.isPresent() && Files.isDirectory(homePath.get())) { + return homePath.get().toRealPath().normalize(); } else { throw new HumanReadableException( - "Could not deduce kotlin home directory from path " + compilerPath.toString()); + "Kotlin home directory (" + homePath + ") specified in .buckconfig was not found."); } } else { // If the KOTLIN_HOME environment variable is specified we trust it String home = delegate.getEnvironment().get("KOTLIN_HOME"); if (home != null) { - kotlinHome = Paths.get(home).normalize(); - return kotlinHome; + return Paths.get(home).normalize(); } else { // Lastly, we try to resolve from the system PATH Optional compiler = diff --git a/src/com/facebook/buck/jvm/kotlin/DefaultKotlinLibraryBuilder.java b/src/com/facebook/buck/jvm/kotlin/KotlinLibraryBuilder.java similarity index 86% rename from src/com/facebook/buck/jvm/kotlin/DefaultKotlinLibraryBuilder.java rename to src/com/facebook/buck/jvm/kotlin/KotlinLibraryBuilder.java index d7a65d5e66a..4e0e46d5a9c 100644 --- a/src/com/facebook/buck/jvm/kotlin/DefaultKotlinLibraryBuilder.java +++ b/src/com/facebook/buck/jvm/kotlin/KotlinLibraryBuilder.java @@ -16,6 +16,8 @@ package com.facebook.buck.jvm.kotlin; +import static com.facebook.buck.jvm.java.BaseCompileToJarStepFactory.EMPTY_EXTRA_CLASSPATH; + import com.facebook.buck.jvm.java.CompileToJarStepFactory; import com.facebook.buck.jvm.java.DefaultJavaLibraryBuilder; import com.facebook.buck.jvm.java.JavaLibraryDescription; @@ -26,11 +28,11 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -public class DefaultKotlinLibraryBuilder extends DefaultJavaLibraryBuilder { +public class KotlinLibraryBuilder extends DefaultJavaLibraryBuilder { private final KotlinBuckConfig kotlinBuckConfig; private ImmutableList extraKotlincArguments = ImmutableList.of(); - public DefaultKotlinLibraryBuilder( + KotlinLibraryBuilder( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver buildRuleResolver, @@ -41,7 +43,7 @@ public DefaultKotlinLibraryBuilder( } @Override - public DefaultKotlinLibraryBuilder setArgs(JavaLibraryDescription.CoreArg args) { + public KotlinLibraryBuilder setArgs(JavaLibraryDescription.CoreArg args) { super.setArgs(args); KotlinLibraryDescription.CoreArg kotlinArgs = (KotlinLibraryDescription.CoreArg) args; @@ -58,8 +60,9 @@ protected class BuilderHelper extends DefaultJavaLibraryBuilder.BuilderHelper { @Override protected CompileToJarStepFactory buildCompileStepFactory() { return new KotlincToJarStepFactory( - Preconditions.checkNotNull(kotlinBuckConfig).getKotlinCompiler().get(), - extraKotlincArguments); + Preconditions.checkNotNull(kotlinBuckConfig).getKotlinc(), + extraKotlincArguments, + EMPTY_EXTRA_CLASSPATH); } } } diff --git a/src/com/facebook/buck/jvm/kotlin/KotlinLibraryDescription.java b/src/com/facebook/buck/jvm/kotlin/KotlinLibraryDescription.java index 2f3627fb276..dc04eca1b24 100644 --- a/src/com/facebook/buck/jvm/kotlin/KotlinLibraryDescription.java +++ b/src/com/facebook/buck/jvm/kotlin/KotlinLibraryDescription.java @@ -101,8 +101,8 @@ public BuildRule createBuildRule( } } - DefaultKotlinLibraryBuilder defaultKotlinLibraryBuilder = - new DefaultKotlinLibraryBuilder(targetGraph, params, resolver, cellRoots, kotlinBuckConfig) + KotlinLibraryBuilder defaultKotlinLibraryBuilder = + new KotlinLibraryBuilder(targetGraph, params, resolver, cellRoots, kotlinBuckConfig) .setArgs(args); // We know that the flavour we're being asked to create is valid, since the check is done when @@ -116,6 +116,7 @@ public BuildRule createBuildRule( if (!flavors.contains(JavaLibrary.MAVEN_JAR)) { return defaultKotlinLibrary; } else { + resolver.addToIndex(defaultKotlinLibrary); return MavenUberJar.create( defaultKotlinLibrary, Preconditions.checkNotNull(paramsWithMavenFlavor), diff --git a/src/com/facebook/buck/jvm/kotlin/KotlinTest.java b/src/com/facebook/buck/jvm/kotlin/KotlinTest.java deleted file mode 100644 index d247edd721d..00000000000 --- a/src/com/facebook/buck/jvm/kotlin/KotlinTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2016-present Facebook, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain - * a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package com.facebook.buck.jvm.kotlin; - -import com.facebook.buck.jvm.java.ForkMode; -import com.facebook.buck.jvm.java.JavaLibrary; -import com.facebook.buck.jvm.java.JavaRuntimeLauncher; -import com.facebook.buck.jvm.java.JavaTest; -import com.facebook.buck.jvm.java.TestType; -import com.facebook.buck.model.Either; -import com.facebook.buck.rules.BuildRuleParams; -import com.facebook.buck.rules.SourcePath; -import com.facebook.buck.rules.SourcePathResolver; -import com.facebook.buck.rules.args.Arg; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.logging.Level; - -@SuppressWarnings("PMD.TestClassWithoutTestCases") -public class KotlinTest extends JavaTest { - - public KotlinTest( - BuildRuleParams params, - SourcePathResolver pathResolver, - JavaLibrary compiledTestsLibrary, - ImmutableSet> additionalClasspathEntries, - Set labels, - Set contacts, - TestType testType, - JavaRuntimeLauncher javaRuntimeLauncher, - List vmArgs, - Map nativeLibsEnvironment, - Optional testRuleTimeoutMs, - Optional testCaseTimeoutMs, - ImmutableMap env, - boolean runTestSeparately, - ForkMode forkMode, - Optional stdOutLogLevel, - Optional stdErrLogLevel) { - - super( - params, - pathResolver, - compiledTestsLibrary, - additionalClasspathEntries, - labels, - contacts, - testType, - javaRuntimeLauncher, - vmArgs, - nativeLibsEnvironment, - testRuleTimeoutMs, - testCaseTimeoutMs, - env, - runTestSeparately, - forkMode, - stdOutLogLevel, - stdErrLogLevel); - } -} diff --git a/src/com/facebook/buck/jvm/kotlin/KotlinTestDescription.java b/src/com/facebook/buck/jvm/kotlin/KotlinTestDescription.java index 6d1f3eb9134..bb2285b6ca9 100644 --- a/src/com/facebook/buck/jvm/kotlin/KotlinTestDescription.java +++ b/src/com/facebook/buck/jvm/kotlin/KotlinTestDescription.java @@ -16,10 +16,10 @@ package com.facebook.buck.jvm.kotlin; +import com.facebook.buck.jvm.java.DefaultJavaLibrary; import com.facebook.buck.jvm.java.DefaultJavaLibraryBuilder; import com.facebook.buck.jvm.java.ForkMode; import com.facebook.buck.jvm.java.HasJavaAbi; -import com.facebook.buck.jvm.java.JavaLibrary; import com.facebook.buck.jvm.java.JavaOptions; import com.facebook.buck.jvm.java.JavaTest; import com.facebook.buck.jvm.java.JavacOptions; @@ -34,7 +34,6 @@ import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.ImplicitDepsInferringDescription; -import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TargetGraph; @@ -52,7 +51,6 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Maps; -import java.nio.file.Path; import java.util.Optional; import java.util.logging.Level; import org.immutables.value.Value; @@ -97,7 +95,7 @@ public BuildRule createBuildRule( params.withAppendedFlavor(JavaTest.COMPILED_TESTS_LIBRARY_FLAVOR); DefaultJavaLibraryBuilder defaultJavaLibraryBuilder = - new DefaultKotlinLibraryBuilder( + new KotlinLibraryBuilder( targetGraph, testsLibraryParams, resolver, cellRoots, kotlinBuckConfig) .setArgs(args) .setGeneratedSourceFolder(templateJavacOptions.getGeneratedSourceFolderName()); @@ -106,20 +104,20 @@ public BuildRule createBuildRule( return defaultJavaLibraryBuilder.buildAbi(); } - JavaLibrary testsLibrary = resolver.addToIndex(defaultJavaLibraryBuilder.build()); + DefaultJavaLibrary testsLibrary = resolver.addToIndex(defaultJavaLibraryBuilder.build()); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); Function toMacroArgFunction = MacroArg.toMacroArgFunction(MACRO_HANDLER, params.getBuildTarget(), cellRoots, resolver); - return new KotlinTest( + return new JavaTest( params.copyReplacingDeclaredAndExtraDeps( Suppliers.ofInstance(ImmutableSortedSet.of(testsLibrary)), Suppliers.ofInstance(ImmutableSortedSet.of())), pathResolver, testsLibrary, - ImmutableSet.>of(kotlinBuckConfig.getPathToRuntimeJar()), + ImmutableSet.of(Either.ofRight(kotlinBuckConfig.getPathToStdlibJar())), args.getLabels(), args.getContacts(), args.getTestType().orElse(TestType.JUNIT), diff --git a/src/com/facebook/buck/jvm/kotlin/Kotlinc.java b/src/com/facebook/buck/jvm/kotlin/Kotlinc.java new file mode 100644 index 00000000000..ef764133e11 --- /dev/null +++ b/src/com/facebook/buck/jvm/kotlin/Kotlinc.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.facebook.buck.jvm.kotlin; + +import com.facebook.buck.io.ProjectFilesystem; +import com.facebook.buck.model.BuildTarget; +import com.facebook.buck.rules.Tool; +import com.facebook.buck.step.ExecutionContext; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; +import java.nio.file.Path; +import java.util.Optional; + +public interface Kotlinc extends Tool { + + KotlincVersion getVersion(); + + int buildWithClasspath( + ExecutionContext context, + BuildTarget invokingRule, + ImmutableList options, + ImmutableSortedSet kotlinSourceFilePaths, + Path pathToSrcsList, + Optional workingDirectory, + ProjectFilesystem fileSystem) + throws InterruptedException; + + String getDescription( + ImmutableList options, + ImmutableSortedSet kotlinSourceFilePaths, + Path pathToSrcsList); + + String getShortName(); +} diff --git a/src/com/facebook/buck/jvm/kotlin/KotlincStep.java b/src/com/facebook/buck/jvm/kotlin/KotlincStep.java index 4df302b1569..d7783548ab5 100644 --- a/src/com/facebook/buck/jvm/kotlin/KotlincStep.java +++ b/src/com/facebook/buck/jvm/kotlin/KotlincStep.java @@ -13,73 +13,158 @@ * License for the specific language governing permissions and limitations * under the License. */ -// Copyright 2004-present Facebook. All Rights Reserved. - package com.facebook.buck.jvm.kotlin; import static com.google.common.collect.Iterables.transform; import com.facebook.buck.io.ProjectFilesystem; -import com.facebook.buck.rules.SourcePathResolver; -import com.facebook.buck.rules.Tool; -import com.facebook.buck.shell.ShellStep; +import com.facebook.buck.model.BuildTarget; import com.facebook.buck.step.ExecutionContext; +import com.facebook.buck.step.Step; +import com.facebook.buck.step.StepExecutionResult; +import com.facebook.buck.util.CapturingPrintStream; +import com.facebook.buck.util.Verbosity; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedSet; import java.io.File; +import java.io.IOException; import java.nio.file.Path; +import java.util.Optional; -public class KotlincStep extends ShellStep { - +public class KotlincStep implements Step { private static final String CLASSPATH_FLAG = "-cp"; private static final String DESTINATION_FLAG = "-d"; private static final String INCLUDE_RUNTIME_FLAG = "-include-runtime"; - private final Tool kotlinc; - private final SourcePathResolver resolver; - private final ImmutableSortedSet declaredClassPathEntries; + private final Kotlinc kotlinc; + private final ImmutableSortedSet combinedClassPathEntries; private final Path outputDirectory; private final ImmutableList extraArguments; private final ImmutableSortedSet sourceFilePaths; + private final ProjectFilesystem filesystem; + private final Path pathToSrcsList; + private final BuildTarget invokingRule; KotlincStep( - Tool kotlinc, - ImmutableList extraArguments, - SourcePathResolver resolver, + BuildTarget invokingRule, Path outputDirectory, ImmutableSortedSet sourceFilePaths, - ImmutableSortedSet declaredClassPathEntries, + Path pathToSrcsList, + ImmutableSortedSet combinedClassPathEntries, + Kotlinc kotlinc, + ImmutableList extraArguments, ProjectFilesystem filesystem) { - super(filesystem.getRootPath()); - this.kotlinc = kotlinc; - this.resolver = resolver; - this.declaredClassPathEntries = declaredClassPathEntries; + this.invokingRule = invokingRule; this.outputDirectory = outputDirectory; - this.extraArguments = extraArguments; this.sourceFilePaths = sourceFilePaths; + this.pathToSrcsList = pathToSrcsList; + this.kotlinc = kotlinc; + this.combinedClassPathEntries = combinedClassPathEntries; + this.extraArguments = extraArguments; + this.filesystem = filesystem; } @Override public String getShortName() { - return Joiner.on(" ").join(kotlinc.getCommandPrefix(resolver)); + return getKotlinc().getShortName(); + } + + @Override + public StepExecutionResult execute(ExecutionContext context) + throws IOException, InterruptedException { + Verbosity verbosity = + context.getVerbosity().isSilent() ? Verbosity.STANDARD_INFORMATION : context.getVerbosity(); + + try (CapturingPrintStream stdout = new CapturingPrintStream(); + CapturingPrintStream stderr = new CapturingPrintStream(); + ExecutionContext firstOrderContext = + context.createSubContext(stdout, stderr, Optional.of(verbosity))) { + + int declaredDepsBuildResult = + kotlinc.buildWithClasspath( + firstOrderContext, + invokingRule, + getOptions(context, combinedClassPathEntries), + sourceFilePaths, + pathToSrcsList, + Optional.empty(), + filesystem); + + String firstOrderStderr = stderr.getContentsAsString(Charsets.UTF_8); + Optional returnedStderr; + if (declaredDepsBuildResult != 0) { + returnedStderr = Optional.of(firstOrderStderr); + } else { + returnedStderr = Optional.empty(); + } + return StepExecutionResult.of(declaredDepsBuildResult, returnedStderr); + } + } + + @VisibleForTesting + Kotlinc getKotlinc() { + return kotlinc; } @Override - protected ImmutableList getShellCommandInternal(ExecutionContext context) { - final ImmutableList.Builder command = - ImmutableList.builder().addAll(kotlinc.getCommandPrefix(resolver)); - - String classpath = - Joiner.on(File.pathSeparator).join(transform(declaredClassPathEntries, Object::toString)); - command - .add(INCLUDE_RUNTIME_FLAG) - .add(CLASSPATH_FLAG) - .add(classpath.isEmpty() ? "''" : classpath) - .add(DESTINATION_FLAG) - .add(outputDirectory.toString()); - - command.addAll(extraArguments).addAll(transform(sourceFilePaths, Object::toString)); - return command.build(); + public String getDescription(ExecutionContext context) { + return getKotlinc() + .getDescription( + getOptions(context, getClasspathEntries()), sourceFilePaths, pathToSrcsList); + } + + /** + * Returns a list of command-line options to pass to javac. These options reflect the + * configuration of this javac command. + * + * @param context the ExecutionContext with in which javac will run + * @return list of String command-line options. + */ + @VisibleForTesting + ImmutableList getOptions( + ExecutionContext context, ImmutableSortedSet buildClasspathEntries) { + return getOptions(filesystem, outputDirectory, buildClasspathEntries); + } + + private ImmutableList getOptions( + ProjectFilesystem filesystem, + Path outputDirectory, + ImmutableSortedSet buildClasspathEntries) { + + final ImmutableList.Builder builder = ImmutableList.builder(); + + builder.add(INCLUDE_RUNTIME_FLAG); + + if (!buildClasspathEntries.isEmpty()) { + builder.add( + CLASSPATH_FLAG, + Joiner.on(File.pathSeparator) + .join( + transform( + buildClasspathEntries, + path -> filesystem.resolve(path).toAbsolutePath().toString()))); + } + + builder.add(DESTINATION_FLAG, filesystem.resolve(outputDirectory).toString()); + + if (!extraArguments.isEmpty()) { + builder.addAll(extraArguments); + } + + return builder.build(); + } + + /** @return The classpath entries used to invoke javac. */ + @VisibleForTesting + ImmutableSortedSet getClasspathEntries() { + return combinedClassPathEntries; + } + + @VisibleForTesting + ImmutableSortedSet getSrcs() { + return sourceFilePaths; } } diff --git a/src/com/facebook/buck/jvm/kotlin/KotlincToJarStepFactory.java b/src/com/facebook/buck/jvm/kotlin/KotlincToJarStepFactory.java index cd42d70e79f..98c0cd06899 100644 --- a/src/com/facebook/buck/jvm/kotlin/KotlincToJarStepFactory.java +++ b/src/com/facebook/buck/jvm/kotlin/KotlincToJarStepFactory.java @@ -35,16 +35,12 @@ public class KotlincToJarStepFactory extends BaseCompileToJarStepFactory { - private final Tool kotlinc; + private final Kotlinc kotlinc; private final ImmutableList extraArguments; private final Function> extraClassPath; - public KotlincToJarStepFactory(Tool kotlinc, ImmutableList extraArguments) { - this(kotlinc, extraArguments, EMPTY_EXTRA_CLASSPATH); - } - public KotlincToJarStepFactory( - Tool kotlinc, + Kotlinc kotlinc, ImmutableList extraArguments, Function> extraClassPath) { this.kotlinc = kotlinc; @@ -54,7 +50,7 @@ public KotlincToJarStepFactory( @Override public void createCompileStep( - BuildContext context, + BuildContext buildContext, ImmutableSortedSet sourceFilePaths, BuildTarget invokingRule, SourcePathResolver resolver, @@ -68,18 +64,21 @@ public void createCompileStep( /* out params */ ImmutableList.Builder steps, BuildableContext buildableContext) { + steps.add( new KotlincStep( - kotlinc, - extraArguments, - resolver, + invokingRule, outputDirectory, sourceFilePaths, + pathToSrcsList, ImmutableSortedSet.naturalOrder() .addAll( - Optional.ofNullable(extraClassPath.apply(context)).orElse(ImmutableList.of())) + Optional.ofNullable(extraClassPath.apply(buildContext)) + .orElse(ImmutableList.of())) .addAll(declaredClasspathEntries) .build(), + kotlinc, + extraArguments, filesystem)); } diff --git a/test/com/facebook/buck/ide/intellij/DefaultIjModuleFactoryTest.java b/test/com/facebook/buck/ide/intellij/DefaultIjModuleFactoryTest.java index aa3825dd513..30c5f22bf7a 100644 --- a/test/com/facebook/buck/ide/intellij/DefaultIjModuleFactoryTest.java +++ b/test/com/facebook/buck/ide/intellij/DefaultIjModuleFactoryTest.java @@ -48,7 +48,7 @@ import com.facebook.buck.jvm.java.JavaLibraryBuilder; import com.facebook.buck.jvm.java.JavaTestBuilder; import com.facebook.buck.jvm.java.JvmLibraryArg; -import com.facebook.buck.jvm.kotlin.KotlinLibraryBuilder; +import com.facebook.buck.jvm.kotlin.FauxKotlinLibraryBuilder; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.rules.BuildRuleResolver; @@ -376,7 +376,7 @@ public void testKotlinLibrary() { IjModuleFactory factory = createIjModuleFactory(); TargetNode kotlinLib = - KotlinLibraryBuilder.createBuilder( + FauxKotlinLibraryBuilder.createBuilder( BuildTargetFactory.newInstance("//kotlin/com/example/base:base")) .addSrc(Paths.get("kotlin/com/example/base/File.kt")) .build(); diff --git a/test/com/facebook/buck/jvm/kotlin/KotlinLibraryBuilder.java b/test/com/facebook/buck/jvm/kotlin/FauxKotlinLibraryBuilder.java similarity index 82% rename from test/com/facebook/buck/jvm/kotlin/KotlinLibraryBuilder.java rename to test/com/facebook/buck/jvm/kotlin/FauxKotlinLibraryBuilder.java index dc1d53fec5a..47120182b48 100644 --- a/test/com/facebook/buck/jvm/kotlin/KotlinLibraryBuilder.java +++ b/test/com/facebook/buck/jvm/kotlin/FauxKotlinLibraryBuilder.java @@ -26,29 +26,29 @@ import com.google.common.hash.HashCode; import java.nio.file.Path; -public class KotlinLibraryBuilder +public class FauxKotlinLibraryBuilder extends AbstractNodeBuilder< KotlinLibraryDescriptionArg.Builder, KotlinLibraryDescriptionArg, KotlinLibraryDescription, BuildRule> { private final ProjectFilesystem projectFilesystem; - protected KotlinLibraryBuilder( + protected FauxKotlinLibraryBuilder( BuildTarget target, ProjectFilesystem projectFilesystem, HashCode hashCode) { super(new KotlinLibraryDescription(null), target, projectFilesystem, hashCode); this.projectFilesystem = projectFilesystem; } - public static KotlinLibraryBuilder createBuilder(BuildTarget target) { - return new KotlinLibraryBuilder(target, new FakeProjectFilesystem(), null); + public static FauxKotlinLibraryBuilder createBuilder(BuildTarget target) { + return new FauxKotlinLibraryBuilder(target, new FakeProjectFilesystem(), null); } - public KotlinLibraryBuilder addSrc(SourcePath path) { + public FauxKotlinLibraryBuilder addSrc(SourcePath path) { getArgForPopulating().addSrcs(path); return this; } - public KotlinLibraryBuilder addSrc(Path path) { + public FauxKotlinLibraryBuilder addSrc(Path path) { return addSrc(new PathSourcePath(projectFilesystem, path)); } } diff --git a/test/com/facebook/buck/jvm/kotlin/KotlinBuckConfigTest.java b/test/com/facebook/buck/jvm/kotlin/KotlinBuckConfigTest.java index c46c0c5cf60..d3d17e3baad 100644 --- a/test/com/facebook/buck/jvm/kotlin/KotlinBuckConfigTest.java +++ b/test/com/facebook/buck/jvm/kotlin/KotlinBuckConfigTest.java @@ -17,15 +17,14 @@ import static java.io.File.pathSeparator; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.io.MoreFiles; import com.facebook.buck.io.ProjectFilesystem; -import com.facebook.buck.rules.PathSourcePath; -import com.facebook.buck.testutil.integration.ProjectWorkspace; import com.facebook.buck.testutil.integration.TemporaryPaths; -import com.facebook.buck.testutil.integration.TestDataHelper; import com.facebook.buck.util.HumanReadableException; import com.google.common.collect.ImmutableMap; import java.io.IOException; @@ -40,67 +39,107 @@ public class KotlinBuckConfigTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); - private ProjectWorkspace workspace; + private Path testDataDirectory; @Before public void setUp() throws InterruptedException, IOException { KotlinTestAssumptions.assumeUnixLike(); - workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "kotlin_compiler_test", tmp); - workspace.setUp(); + tmp.newFolder("faux_kotlin_home", "bin"); + tmp.newFolder("faux_kotlin_home", "libexec", "bin"); + tmp.newFolder("faux_kotlin_home", "libexec", "lib"); + tmp.newExecutableFile("faux_kotlin_home/bin/kotlinc"); + tmp.newExecutableFile("faux_kotlin_home/libexec/bin/kotlinc"); + tmp.newExecutableFile("faux_kotlin_home/libexec/lib/kotlin-compiler.jar"); + tmp.newExecutableFile("faux_kotlin_home/libexec/lib/kotlin-stdlib.jar"); + + testDataDirectory = tmp.getRoot(); } @Test - public void testFindsKotlinCompilerInPath() throws HumanReadableException, IOException { + public void testFindsKotlinCompilerInPathLibexec() throws HumanReadableException, IOException { // Get faux kotlinc binary location in project - Path kotlinPath = workspace.resolve("bin"); - Path kotlinCompiler = kotlinPath.resolve("kotlinc"); + Path kotlinHome = testDataDirectory.resolve("faux_kotlin_home/libexec/bin").normalize(); + Path kotlinCompiler = kotlinHome.resolve("kotlinc"); MoreFiles.makeExecutable(kotlinCompiler); BuckConfig buckConfig = FakeBuckConfig.builder() + .setSections(ImmutableMap.of("kotlin", ImmutableMap.of("external", "true"))) .setEnvironment( ImmutableMap.of( - "PATH", kotlinPath.toString() + pathSeparator + System.getenv("PATH"))) + "PATH", kotlinHome.toString() + pathSeparator + System.getenv("PATH"))) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - String command = kotlinBuckConfig.getKotlinCompiler().get().getCommandPrefix(null).get(0); + String command = kotlinBuckConfig.getPathToCompilerBinary().toString(); assertEquals(command, kotlinCompiler.toString()); } @Test - public void testFindsKotlinCompilerInHome() throws HumanReadableException, IOException { + public void testFindsKotlinCompilerInPathBin() throws HumanReadableException, IOException { // Get faux kotlinc binary location in project - Path kotlinCompiler = workspace.resolve("bin").resolve("kotlinc"); + Path kotlinHome = testDataDirectory.resolve("faux_kotlin_home/bin").normalize(); + Path kotlinCompiler = kotlinHome.resolve("kotlinc"); MoreFiles.makeExecutable(kotlinCompiler); BuckConfig buckConfig = FakeBuckConfig.builder() + .setSections(ImmutableMap.of("kotlin", ImmutableMap.of("external", "true"))) .setEnvironment( - ImmutableMap.of("KOTLIN_HOME", workspace.getPath(".").normalize().toString())) + ImmutableMap.of( + "PATH", kotlinHome.toString() + pathSeparator + System.getenv("PATH"))) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - String command = kotlinBuckConfig.getKotlinCompiler().get().getCommandPrefix(null).get(0); + String command = kotlinBuckConfig.getPathToCompilerBinary().toString(); assertEquals(command, kotlinCompiler.toString()); } @Test - public void testFindsKotlinCompilerInConfigWithAbsolutePath() + public void testFindsKotlinCompilerInHomeEnvironment() throws HumanReadableException, IOException { // Get faux kotlinc binary location in project - Path kotlinCompiler = workspace.resolve("bin").resolve("kotlinc"); + Path kotlinHome = testDataDirectory.resolve("faux_kotlin_home").normalize(); + Path kotlinCompiler = kotlinHome.resolve("bin").resolve("kotlinc"); MoreFiles.makeExecutable(kotlinCompiler); BuckConfig buckConfig = FakeBuckConfig.builder() - .setSections( - ImmutableMap.of("kotlin", ImmutableMap.of("compiler", kotlinCompiler.toString()))) + .setSections(ImmutableMap.of("kotlin", ImmutableMap.of("external", "true"))) + .setEnvironment( + ImmutableMap.of( + "KOTLIN_HOME", + testDataDirectory.resolve("faux_kotlin_home").toAbsolutePath().toString())) + .build(); + + KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); + String command = kotlinBuckConfig.getPathToCompilerBinary().toString(); + assertEquals(command, kotlinCompiler.toString()); + } + + @Test + public void testFindsKotlinCompilerInHomeEnvironment2() + throws HumanReadableException, IOException { + // Get faux kotlinc binary location in project + Path kotlinHome = testDataDirectory.resolve("faux_kotlin_home/libexec").normalize(); + Path kotlinCompiler = kotlinHome.resolve("bin").resolve("kotlinc"); + MoreFiles.makeExecutable(kotlinCompiler); + + BuckConfig buckConfig = + FakeBuckConfig.builder() + .setSections(ImmutableMap.of("kotlin", ImmutableMap.of("external", "true"))) + .setEnvironment( + ImmutableMap.of( + "KOTLIN_HOME", + testDataDirectory + .resolve("faux_kotlin_home/libexec/bin") + .toAbsolutePath() + .toString())) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - String command = kotlinBuckConfig.getKotlinCompiler().get().getCommandPrefix(null).get(0); + String command = kotlinBuckConfig.getPathToCompilerBinary().toString(); assertEquals(command, kotlinCompiler.toString()); } @@ -108,77 +147,186 @@ public void testFindsKotlinCompilerInConfigWithAbsolutePath() public void testFindsKotlinCompilerInConfigWithRelativePath() throws HumanReadableException, InterruptedException, IOException { // Get faux kotlinc binary location in project - Path kotlinCompiler = workspace.resolve("bin").resolve("kotlinc"); + Path kotlinHome = testDataDirectory.resolve("faux_kotlin_home").normalize(); + Path kotlinCompiler = kotlinHome.resolve("bin").resolve("kotlinc"); MoreFiles.makeExecutable(kotlinCompiler); - ProjectFilesystem filesystem = new ProjectFilesystem(workspace.resolve(".")); + ProjectFilesystem filesystem = new ProjectFilesystem(testDataDirectory.resolve(".")); BuckConfig buckConfig = FakeBuckConfig.builder() .setFilesystem(filesystem) - .setSections(ImmutableMap.of("kotlin", ImmutableMap.of("compiler", "bin/kotlinc"))) + .setSections( + ImmutableMap.of( + "kotlin", + ImmutableMap.of("kotlin_home", "./faux_kotlin_home", "external", "true"))) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - String command = kotlinBuckConfig.getKotlinCompiler().get().getCommandPrefix(null).get(0); + String command = kotlinBuckConfig.getPathToCompilerBinary().toString(); assertEquals(command, kotlinCompiler.toString()); } @Test - public void testFindsKotlinRuntimeLibraryInPath() throws IOException { + public void testFindsKotlinCompilerJarInConfigWithAbsolutePath() + throws HumanReadableException, InterruptedException, IOException { + + Path kotlinRuntime = + testDataDirectory + .resolve("faux_kotlin_home") + .resolve("libexec") + .resolve("lib") + .resolve("kotlin-compiler.jar"); + + BuckConfig buckConfig = + FakeBuckConfig.builder() + .setSections( + ImmutableMap.of( + "kotlin", + ImmutableMap.of( + "kotlin_home", + testDataDirectory.resolve("faux_kotlin_home").toAbsolutePath().toString(), + "external", + "false"))) + .build(); + + KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); + Path compilerJar = kotlinBuckConfig.getPathToCompilerJar(); + assertEquals(kotlinRuntime, compilerJar); + } + + @Test + public void testFindsKotlinCompilerJarInConfigWithAbsolutePath2() + throws HumanReadableException, InterruptedException, IOException { + + Path kotlinRuntime = + testDataDirectory + .resolve("faux_kotlin_home") + .resolve("libexec") + .resolve("lib") + .resolve("kotlin-compiler.jar"); + + BuckConfig buckConfig = + FakeBuckConfig.builder() + .setSections( + ImmutableMap.of( + "kotlin", + ImmutableMap.of( + "kotlin_home", + testDataDirectory + .resolve("faux_kotlin_home") + .resolve("libexec") + .toAbsolutePath() + .toString(), + "external", + "false"))) + .build(); + + KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); + Path compilerJar = kotlinBuckConfig.getPathToCompilerJar(); + assertEquals(kotlinRuntime, compilerJar); + } + + @Test + public void testFindsKotlinCompilerLibraryInPath() throws IOException { // Get faux kotlinc binary location in project - Path kotlinPath = workspace.resolve("bin"); - Path kotlinCompiler = kotlinPath.resolve("kotlinc"); + Path kotlinHome = testDataDirectory.resolve("faux_kotlin_home").normalize(); + Path kotlinCompiler = kotlinHome.resolve("libexec").resolve("bin").resolve("kotlinc"); MoreFiles.makeExecutable(kotlinCompiler); BuckConfig buckConfig = FakeBuckConfig.builder() + .setSections(ImmutableMap.of("kotlin", ImmutableMap.of("external", "true"))) .setEnvironment( ImmutableMap.of( - "PATH", kotlinPath.toString() + pathSeparator + System.getenv("PATH"))) + "PATH", + kotlinCompiler.getParent().toString() + pathSeparator + System.getenv("PATH"))) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - Path runtimeJar = kotlinBuckConfig.getPathToRuntimeJar().getRight(); + Path compilerJar = kotlinBuckConfig.getPathToCompilerJar(); Assert.assertThat( - runtimeJar.toString(), - Matchers.containsString(workspace.getPath(".").normalize().toString())); + compilerJar.toString(), Matchers.containsString(testDataDirectory.toString())); } @Test - public void testFindsKotlinRuntimeInConfigWithAbsolutePath() - throws HumanReadableException, IOException { + public void testFindsKotlinStdlibJarInConfigWithAbsolutePath() + throws HumanReadableException, InterruptedException, IOException { - Path kotlinRuntime = workspace.resolve("lib").resolve("kotlin-runtime.jar"); + Path kotlinRuntime = + testDataDirectory + .resolve("faux_kotlin_home") + .resolve("libexec") + .resolve("lib") + .resolve("kotlin-stdlib.jar"); BuckConfig buckConfig = FakeBuckConfig.builder() .setSections( - ImmutableMap.of("kotlin", ImmutableMap.of("runtime_jar", kotlinRuntime.toString()))) - .setEnvironment( - ImmutableMap.of("KOTLIN_HOME", workspace.getPath(".").normalize().toString())) + ImmutableMap.of( + "kotlin", + ImmutableMap.of( + "kotlin_home", + testDataDirectory.resolve("faux_kotlin_home").toAbsolutePath().toString(), + "external", + "false"))) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - Path runtimeJar = kotlinBuckConfig.getPathToRuntimeJar().getRight(); - assertEquals(runtimeJar.toString(), kotlinRuntime.toString()); + Path runtimeJar = kotlinBuckConfig.getPathToStdlibJar(); + assertEquals(kotlinRuntime, runtimeJar); } @Test - public void testFindsKotlinRuntimeInConfigWithRelativePath() + public void testFindsKotlinStdlibJarInConfigWithAbsolutePath2() throws HumanReadableException, InterruptedException, IOException { - ProjectFilesystem filesystem = new ProjectFilesystem(workspace.resolve(".")); + Path kotlinRuntime = + testDataDirectory + .resolve("faux_kotlin_home") + .resolve("libexec") + .resolve("lib") + .resolve("kotlin-stdlib.jar"); + BuckConfig buckConfig = FakeBuckConfig.builder() - .setFilesystem(filesystem) .setSections( - ImmutableMap.of("kotlin", ImmutableMap.of("runtime_jar", "lib/kotlin-runtime.jar"))) - .setEnvironment( - ImmutableMap.of("KOTLIN_HOME", workspace.getPath(".").normalize().toString())) + ImmutableMap.of( + "kotlin", + ImmutableMap.of( + "kotlin_home", + testDataDirectory + .resolve("faux_kotlin_home") + .resolve("libexec") + .resolve("lib") + .toAbsolutePath() + .toString(), + "external", + "false"))) + .build(); + + KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); + Path runtimeJar = kotlinBuckConfig.getPathToStdlibJar(); + assertEquals(kotlinRuntime, runtimeJar); + } + + @Test + public void testFindsKotlinStdlibJarInConfigWithRelativePath() + throws HumanReadableException, InterruptedException, IOException { + + BuckConfig buckConfig = + FakeBuckConfig.builder() + .setFilesystem(new ProjectFilesystem(testDataDirectory)) + .setSections( + ImmutableMap.of( + "kotlin", + ImmutableMap.of( + "kotlin_home", "faux_kotlin_home", + "external", "false"))) .build(); KotlinBuckConfig kotlinBuckConfig = new KotlinBuckConfig(buckConfig); - PathSourcePath runtimeJar = (PathSourcePath) kotlinBuckConfig.getPathToRuntimeJar().getLeft(); - assertEquals(runtimeJar.getRelativePath().toString(), "lib/kotlin-runtime.jar"); + Path runtimeJar = kotlinBuckConfig.getPathToStdlibJar(); + assertNotNull(runtimeJar); + assertTrue(runtimeJar.endsWith("faux_kotlin_home/libexec/lib/kotlin-stdlib.jar")); } } diff --git a/test/com/facebook/buck/jvm/kotlin/KotlinLibraryIntegrationTest.java b/test/com/facebook/buck/jvm/kotlin/KotlinLibraryIntegrationTest.java index 9df12b31599..9a9cb0f3ad7 100644 --- a/test/com/facebook/buck/jvm/kotlin/KotlinLibraryIntegrationTest.java +++ b/test/com/facebook/buck/jvm/kotlin/KotlinLibraryIntegrationTest.java @@ -39,6 +39,7 @@ public void setUp() throws IOException, InterruptedException { @Test public void shouldCompileKotlinClass() throws Exception { + KotlinTestAssumptions.assumeCompilerAvailable(); ProjectWorkspace.ProcessResult buildResult = workspace.runBuckCommand("build", "//com/example/good:example"); buildResult.assertSuccess("Build should have succeeded."); @@ -46,6 +47,7 @@ public void shouldCompileKotlinClass() throws Exception { @Test public void shouldCompileLibraryWithDependencyOnAnother() throws Exception { + KotlinTestAssumptions.assumeCompilerAvailable(); ProjectWorkspace.ProcessResult buildResult = workspace.runBuckCommand("build", "//com/example/child:child"); buildResult.assertSuccess("Build should have succeeded."); @@ -53,6 +55,7 @@ public void shouldCompileLibraryWithDependencyOnAnother() throws Exception { @Test public void shouldFailToCompileInvalidKotlinCode() throws Exception { + KotlinTestAssumptions.assumeCompilerAvailable(); ProjectWorkspace.ProcessResult buildResult = workspace.runBuckCommand("build", "//com/example/bad:fail"); buildResult.assertFailure(); diff --git a/test/com/facebook/buck/jvm/kotlin/KotlinTestAssumptions.java b/test/com/facebook/buck/jvm/kotlin/KotlinTestAssumptions.java index 2068bb1f3d9..4b4a4194d34 100644 --- a/test/com/facebook/buck/jvm/kotlin/KotlinTestAssumptions.java +++ b/test/com/facebook/buck/jvm/kotlin/KotlinTestAssumptions.java @@ -32,7 +32,7 @@ public static void assumeUnixLike() { public static void assumeCompilerAvailable() throws IOException { Throwable exception = null; try { - new KotlinBuckConfig(FakeBuckConfig.builder().build()).getKotlinCompiler(); + new KotlinBuckConfig(FakeBuckConfig.builder().build()).getPathToCompilerJar(); } catch (HumanReadableException e) { exception = e; } diff --git a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_compiler_test/lib/kotlin-compiler.jar b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_compiler_test/lib/kotlin-compiler.jar new file mode 100644 index 00000000000..1b3cfcec1a6 --- /dev/null +++ b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_compiler_test/lib/kotlin-compiler.jar @@ -0,0 +1 @@ +This is not really a jar. \ No newline at end of file diff --git a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/BUCK.fixture b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/BUCK.fixture index b910bba347e..d2d6b8eb1dd 100644 --- a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/BUCK.fixture +++ b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/BUCK.fixture @@ -17,9 +17,3 @@ prebuilt_jar( name = 'hamcrest-library', binary_jar = 'hamcrest-library-1.3.jar', ) - -prebuilt_jar( - name = 'kotlin-runtime', - binary_jar = 'kotlin-runtime-1.0.4.jar', - visibility = [ 'PUBLIC' ], -) diff --git a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/com/example/basic/BUCK.fixture b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/com/example/basic/BUCK.fixture index a4a49f2587f..6e59b589437 100644 --- a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/com/example/basic/BUCK.fixture +++ b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/com/example/basic/BUCK.fixture @@ -5,7 +5,6 @@ kotlin_test( ], deps = [ '//:junit', - '//:kotlin-runtime', ], ) @@ -16,6 +15,5 @@ kotlin_test( ], deps = [ '//:junit', - '//:kotlin-runtime', ], ) diff --git a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/kotlin-runtime-1.0.4.jar b/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/kotlin-runtime-1.0.4.jar deleted file mode 100644 index a7624530802..00000000000 Binary files a/test/com/facebook/buck/jvm/kotlin/testdata/kotlin_test_description/kotlin-runtime-1.0.4.jar and /dev/null differ