add builder tests for annotation processing
diff --git a/Makefile b/Makefile
index 261925c..6825372 100644
--- a/Makefile
+++ b/Makefile
@@ -17,3 +17,6 @@
proto.regen:
scripts/gen_proto_jars
+
+install.tools:
+ go get github.com/bazelbuild/buildtools/buildifier
diff --git a/scripts/reflow_skylark b/scripts/reflow_skylark
index 3489993..6e5c6f4 100755
--- a/scripts/reflow_skylark
+++ b/scripts/reflow_skylark
@@ -15,13 +15,13 @@
# Note: Written on a mac please make it compatible with linux if needed.
#!/usr/bin/env bash
-buildifier -mode=fix -v $(find kotlin -type f \
+buildifier -showlog -mode=fix -v $(find src -type f \
-iname "*.bzl" -or \
-iname "BUILD" -or \
-iname "BUILD.com_github_jetbrains_kotlin"
)
-buildifier -mode=fix -v \
+buildifier -showlog -mode=fix -v \
"third_party/BUILD" \
"WORKSPACE" \
"BUILD"
\ No newline at end of file
diff --git a/src/main/kotlin/BUILD b/src/main/kotlin/BUILD
index ede4bea..c4b9698 100644
--- a/src/main/kotlin/BUILD
+++ b/src/main/kotlin/BUILD
@@ -23,6 +23,7 @@
"@com_github_jetbrains_kotlin//:kotlin-annotation-processing",
"@com_github_jetbrains_kotlin//:kotlin-script-runtime",
],
+ visibility = ["//src/test/kotlin/io/bazel/kotlin/builder:__subpackages__"],
)
# The builder artifact.
@@ -54,11 +55,11 @@
"//src/test:__subpackages__",
],
exports = [
- "//src/main/kotlin/io/bazel/kotlin/builder:builder",
- "//third_party/jvm/com/google/truth",
- "//third_party/jvm/junit",
+ "//src/main/kotlin/io/bazel/kotlin/builder",
"//src/main/protobuf:deps",
"//src/main/protobuf:kotlin_model",
+ "//third_party/jvm/com/google/truth",
+ "//third_party/jvm/junit",
"@io_bazel_rules_kotlin_com_google_guava_guava//jar",
"@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java//jar",
],
diff --git a/src/main/kotlin/bootstrap.bzl b/src/main/kotlin/bootstrap.bzl
index 02e184b..5901cda 100644
--- a/src/main/kotlin/bootstrap.bzl
+++ b/src/main/kotlin/bootstrap.bzl
@@ -27,7 +27,7 @@
else:
return d
-def kt_bootstrap_library(name, srcs, deps = [], neverlink_deps = [], runtime_deps = []):
+def kt_bootstrap_library(name, srcs, visibility = [], deps = [], neverlink_deps = [], runtime_deps = []):
"""
Simple compilation of a kotlin library using a non-persistent worker. The target is a JavaInfo provider.
@@ -87,7 +87,7 @@
jars = [jar_label],
tags = ["no-ide"],
runtime_deps = deps + runtime_deps,
- visibility = ["//visibility:private"],
+ visibility = visibility,
)
# hsyed todo this part of the graph should not be wired up outside of development.
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/BUILD b/src/main/kotlin/io/bazel/kotlin/builder/BUILD
index 6957022..0983523 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/BUILD
+++ b/src/main/kotlin/io/bazel/kotlin/builder/BUILD
@@ -21,10 +21,10 @@
"utils/**/*.kt",
]),
deps = [
- "@com_github_jetbrains_kotlin//:kotlin-preloader",
"//src/main/protobuf:deps",
"//src/main/protobuf:kotlin_model",
"//src/main/protobuf:worker",
+ "@com_github_jetbrains_kotlin//:kotlin-preloader",
"@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java//jar",
"@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java_util//jar",
"@io_bazel_rules_kotlin_javax_inject_javax_inject//jar",
@@ -34,7 +34,7 @@
java_library(
name = "builder",
srcs = glob(["*.java"]),
- visibility = ["//src/main/kotlin:__subpackages__"],
+ visibility = ["//src:__subpackages__"],
exports = [":builder_kt"],
runtime_deps = [
"@com_github_jetbrains_kotlin//:kotlin-stdlib-jdk7",
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt
index bad7f60..10bf26b 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt
+++ b/src/main/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinJvmTaskExecutor.kt
@@ -38,28 +38,26 @@
fun execute(context: CompilationTaskContext, task: JvmCompilationTask) {
// TODO fix error handling
try {
- val preprocessedTask = task.preprocessingSteps(context)
+ val preprocessedTask = task.preProcessingSteps(context)
context.execute("compile classes") { preprocessedTask.compileAll(context) }
context.execute("create jar") { preprocessedTask.createOutputJar() }
context.execute("produce src jar") { preprocessedTask.produceSourceJar() }
- context.execute("generate jdeps") { preprocessedTask.generateJDeps() }
+ context.execute("generate jdeps") { jDepsGenerator.generateJDeps(preprocessedTask) }
} catch (ex: Throwable) {
throw RuntimeException(ex)
}
}
- private fun JvmCompilationTask.preprocessingSteps(context: CompilationTaskContext): JvmCompilationTask {
+ private fun JvmCompilationTask.preProcessingSteps(context: CompilationTaskContext): JvmCompilationTask {
ensureDirectories(
directories.temp,
directories.generatedSources,
directories.generatedClasses
)
val taskWithAdditionalSources = context.execute("expand sources") { expandWithSourceJarSources() }
- return context.execute("kapt") { taskWithAdditionalSources.runAnnotationProcessors(context) }
- }
-
- private fun JvmCompilationTask.generateJDeps() {
- jDepsGenerator.generateJDeps(this)
+ return context.execute({
+ "kapt (${info.plugins.annotationProcessorsList.joinToString(", ") { it.processorClass }})"
+ }) { taskWithAdditionalSources.runAnnotationProcessors(context) }
}
private fun JvmCompilationTask.produceSourceJar() {
@@ -129,7 +127,7 @@
this
} else {
runAnnotationProcessor(context, printOnSuccess = !context.isTracing).let { outputLines ->
- // if tracing is enabled the output should be formated in a special way, if we aren't tracing then any
+ // if tracing is enabled the output should be formatted in a special way, if we aren't tracing then any
// compiler output would make it's way to the console as is.
if (context.isTracing) {
context.printLines("kapt output", outputLines)
diff --git a/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt b/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt
index 2c626bd..9351233 100644
--- a/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt
+++ b/src/main/kotlin/io/bazel/kotlin/builder/utils/CompilationTaskContext.kt
@@ -15,7 +15,6 @@
*/
package io.bazel.kotlin.builder.utils
-
import com.google.protobuf.MessageOrBuilder
import com.google.protobuf.TextFormat
import io.bazel.kotlin.builder.toolchain.CompilationStatusException
@@ -27,8 +26,10 @@
import java.nio.file.Paths
class CompilationTaskContext(val info: CompilationTaskInfo, private val out: PrintStream) {
+ private val start = System.currentTimeMillis()
private val executionRoot: String = Paths.get("").toAbsolutePath().toString() + File.separator
- private val timings: MutableList<String>?
+ private var timings: MutableList<String>?
+ private var level = -1
@PublishedApi
internal val isTracing: Boolean
@@ -42,7 +43,11 @@
throwable.printStackTrace(out)
}
- fun print(msg: String) { out.println(msg) }
+ @Suppress("unused")
+ fun print(msg: String) {
+ out.println(msg)
+ }
+
/**
* Print a list of debugging lines.
*
@@ -113,7 +118,7 @@
printCompilerOutput(output)
}
throw CompilationStatusException("compile phase failed", result, output)
- } else if(printOnSuccess) {
+ } else if (printOnSuccess) {
printCompilerOutput(output)
}
return output
@@ -122,28 +127,46 @@
/**
* Runs a task and records the timings.
*/
- fun <T> execute(name: String, task: () -> T): T {
+ fun <T> execute(name: String, task: () -> T): T = execute({ name }, task)
+
+ /**
+ * Runs a task and records the timings.
+ */
+ @Suppress("MemberVisibilityCanBePrivate")
+ fun <T> execute(name: () -> String, task: () -> T): T {
return if (timings == null) {
task()
- } else {
- val start = System.currentTimeMillis()
- try {
- task()
- } finally {
- val stop = System.currentTimeMillis()
- timings += "$name: ${stop - start} ms"
+ } else pushTimedTask(name(), task)
+ }
+
+ private inline fun <T> pushTimedTask(name: String, task: () -> T): T {
+ level += 1
+ val previousTimings = timings
+ timings = mutableListOf()
+ return try {
+ System.currentTimeMillis().let { start ->
+ task().also {
+ val stop = System.currentTimeMillis()
+ previousTimings!! += "${" ".repeat(level)} * $name: ${stop - start} ms"
+ previousTimings.addAll(timings!!)
+ }
}
+ } finally {
+ level -= 1
+ timings = previousTimings
}
}
/**
* This method should be called at the end of builder invocation.
*
- * @param succesfull true if the task finished succesfully.
+ * @param successful true if the task finished successfully.
*/
- fun finalize(succesfull: Boolean) {
- if (succesfull) {
- timings?.also { printLines("Task timings", it, prefix = " * ") }
+ fun finalize(successful: Boolean) {
+ if (successful) {
+ timings?.also {
+ printLines("Task timings for ${info.label} (total: ${System.currentTimeMillis() - start} ms)", it)
+ }
}
}
}
diff --git a/src/test/data/jvm/basic/BUILD b/src/test/data/jvm/basic/BUILD
index 16a016c..ebfdc39 100644
--- a/src/test/data/jvm/basic/BUILD
+++ b/src/test/data/jvm/basic/BUILD
@@ -11,7 +11,8 @@
# 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(default_visibility=["//visibility:private"])
+package(default_visibility = ["//visibility:private"])
+
load("//kotlin:kotlin.bzl", "kt_jvm_binary", "kt_jvm_library", "kt_jvm_test")
kt_jvm_binary(
@@ -104,7 +105,7 @@
kt_jvm_library(
name = "test_friends_library",
srcs = ["test_friends/Service.kt"],
- visibility = ["//src/test/kotlin:__subpackages__"]
+ visibility = ["//src/test/kotlin:__subpackages__"],
)
filegroup(
diff --git a/src/test/kotlin/io/bazel/kotlin/BUILD b/src/test/kotlin/io/bazel/kotlin/BUILD
index d0b9697..03df859 100644
--- a/src/test/kotlin/io/bazel/kotlin/BUILD
+++ b/src/test/kotlin/io/bazel/kotlin/BUILD
@@ -11,7 +11,7 @@
# 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(default_visibility=["//visibility:private"])
+package(default_visibility = ["//visibility:private"])
load("//kotlin:kotlin.bzl", "kt_jvm_library")
load(":defs.bzl", "kt_rules_e2e_test", "kt_rules_test")
@@ -21,62 +21,62 @@
testonly = 1,
srcs = ["KotlinAssertionTestCase.kt"],
visibility = ["//visibility:public"],
+ exports = [
+ "//third_party/jvm/com/google/truth",
+ "//third_party/jvm/junit",
+ "@com_github_jetbrains_kotlin//:kotlin-test",
+ "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
+ ],
deps = [
"@com_github_jetbrains_kotlin//:kotlin-test",
"@io_bazel_rules_kotlin_com_google_guava_guava//jar",
],
- exports = [
- "//third_party/jvm/com/google/truth",
- "//third_party/jvm/junit:junit",
- "@com_github_jetbrains_kotlin//:kotlin-test",
- "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
- ]
)
kt_rules_e2e_test(
name = "KotlinJvmBasicAssertionTest",
srcs = ["KotlinJvmBasicAssertionTest.kt"],
- data = [ "//src/test/data/jvm/basic"]
+ data = ["//src/test/data/jvm/basic"],
)
kt_rules_e2e_test(
name = "KotlinNormalizationAssertionTest",
- srcs =["KotlinNormalizationAssertionTest.kt"],
+ srcs = ["KotlinNormalizationAssertionTest.kt"],
data = ["//src/test/data/jvm/basic"],
)
kt_rules_e2e_test(
name = "KotlinJvmKaptAssertionTest",
srcs = ["KotlinJvmKaptAssertionTest.kt"],
- data = ["//src/test/data/jvm/kapt"]
+ data = ["//src/test/data/jvm/kapt"],
)
kt_rules_e2e_test(
name = "KotlinJvmDaggerExampleTest",
- srcs =["KotlinJvmDaggerExampleTest.kt"],
+ srcs = ["KotlinJvmDaggerExampleTest.kt"],
data = ["//examples/dagger:coffee_app"],
)
kt_rules_e2e_test(
name = "KotlinJvmFriendsVisibilityTest",
srcs = ["KotlinJvmFriendsVisibilityTest.kt"],
- friends = ["//src/test/data/jvm/basic:test_friends_library"]
+ friends = ["//src/test/data/jvm/basic:test_friends_library"],
)
test_suite(
name = "assertion_tests",
tests = [
"KotlinJvmBasicAssertionTest",
- "KotlinJvmKaptAssertionTest",
"KotlinJvmDaggerExampleTest",
"KotlinJvmFriendsVisibilityTest",
- ]
+ "KotlinJvmKaptAssertionTest",
+ ],
)
test_suite(
name = "local_assertion_tests",
tests = [
+ ":KotlinNormalizationAssertionTest",
":assertion_tests",
- ":KotlinNormalizationAssertionTest"
- ]
+ ],
)
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/BUILD b/src/test/kotlin/io/bazel/kotlin/builder/BUILD
index 599ac4a..3c7a9f2 100644
--- a/src/test/kotlin/io/bazel/kotlin/builder/BUILD
+++ b/src/test/kotlin/io/bazel/kotlin/builder/BUILD
@@ -11,15 +11,38 @@
# 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(default_visibility=["//visibility:private"])
+package(default_visibility = ["//visibility:private"])
+
load("//src/test/kotlin/io/bazel/kotlin:defs.bzl", "kt_rules_test")
+_COMMON_DEPS = [
+ "//src/main/kotlin/io/bazel/kotlin/builder",
+ "//src/main/protobuf:kotlin_model",
+ "//third_party/jvm/com/google/truth",
+ "//third_party/jvm/junit",
+ "@io_bazel_rules_kotlin_com_google_guava_guava//jar",
+]
+
java_library(
name = "test_lib",
testonly = 1,
- srcs = [":KotlinBuilderTestCase.java"],
- exports = ["//src/main/kotlin:builder_lib_for_tests"],
- deps = ["//src/main/kotlin:builder_lib_for_tests"],
+ srcs = [
+ "KotlinBuilderJvmTestTask.java",
+ "KotlinBuilderResource.java",
+ ],
+ data = [
+ "//src/main/kotlin:compiler_lib",
+ "@com_github_jetbrains_kotlin//:home",
+ ],
+ exports = _COMMON_DEPS + [
+ "@io_bazel_rules_kotlin_com_google_protobuf_protobuf_java//jar",
+ "//src/main/protobuf:deps",
+ ],
+ runtime_deps = [
+ "@com_github_jetbrains_kotlin//:kotlin-reflect",
+ "@com_github_jetbrains_kotlin//:kotlin-stdlib",
+ ],
+ deps = _COMMON_DEPS,
)
kt_rules_test(
@@ -35,15 +58,17 @@
kt_rules_test(
name = "KotlinBuilderJvmTest",
srcs = ["tasks/jvm/KotlinBuilderJvmTest.java"],
+ data = [
+ "//third_party/jvm/com/google/auto/value:auto_value",
+ ],
)
test_suite(
name = "builder_tests",
tests = [
- ":SourceJarCreatorTest",
":JdepsParserTest",
- ":KotlinBuilderJvmTest"
+ ":KotlinBuilderJvmTest",
+ ":SourceJarCreatorTest",
],
- visibility = ["//visibility:public"]
+ visibility = ["//visibility:public"],
)
-
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderJvmTestTask.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderJvmTestTask.java
new file mode 100644
index 0000000..e3c1f97
--- /dev/null
+++ b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderJvmTestTask.java
@@ -0,0 +1,111 @@
+package io.bazel.kotlin.builder;
+
+import io.bazel.kotlin.builder.utils.CompilationTaskContext;
+import io.bazel.kotlin.model.*;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public final class KotlinBuilderJvmTestTask extends KotlinBuilderResource {
+
+ private static final JvmCompilationTask.Builder taskBuilder = JvmCompilationTask.newBuilder();
+
+ @Override
+ CompilationTaskInfo.Builder infoBuilder() {
+ return taskBuilder.getInfoBuilder();
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ super.before();
+ taskBuilder.clear();
+
+ taskBuilder
+ .getInfoBuilder()
+ .setLabel("//some/bogus:" + label())
+ .setModuleName("some_bogus_module")
+ .setPlatform(Platform.JVM)
+ .setRuleKind(RuleKind.LIBRARY)
+ .setToolchainInfo(
+ KotlinToolchainInfo.newBuilder()
+ .setCommon(
+ KotlinToolchainInfo.Common.newBuilder()
+ .setApiVersion("1.2")
+ .setCoroutines("enabled")
+ .setLanguageVersion("1.2"))
+ .setJvm(KotlinToolchainInfo.Jvm.newBuilder().setJvmTarget("1.8")));
+ taskBuilder
+ .getDirectoriesBuilder()
+ .setClasses(directory(DirectoryType.CLASSES).toAbsolutePath().toString())
+ .setGeneratedSources(directory(DirectoryType.SOURCE_GEN).toAbsolutePath().toString())
+ .setTemp(directory(DirectoryType.TEMP).toAbsolutePath().toString())
+ .setGeneratedClasses(
+ directory(DirectoryType.GENERATED_CLASSES).toAbsolutePath().toString());
+ taskBuilder
+ .getOutputsBuilder()
+ .setJar(instanceRoot().resolve("jar_file.jar").toAbsolutePath().toString())
+ .setJdeps(instanceRoot().resolve("jdeps_file.jdeps").toAbsolutePath().toString())
+ .setSrcjar(instanceRoot().resolve("jar_file-sources.jar").toAbsolutePath().toString());
+ }
+
+ public void addSource(String filename, String... lines) {
+ Path path = directory(DirectoryType.SOURCES).resolve(filename).toAbsolutePath();
+ try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
+ fos.write(String.join("\n", lines).getBytes(UTF_8));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ String pathAsString = path.toString();
+ if (pathAsString.endsWith(".kt")) {
+ taskBuilder.getInputsBuilder().addKotlinSources(pathAsString);
+ } else if (pathAsString.endsWith(".java")) {
+ taskBuilder.getInputsBuilder().addJavaSources(pathAsString);
+ } else {
+ throw new RuntimeException("unhandled file type: " + path.toString());
+ }
+ }
+
+ public void addAnnotationProcessors(AnnotationProcessor... annotationProcessors) {
+ taskBuilder
+ .getInfoBuilder()
+ .getPluginsBuilder()
+ .addAllAnnotationProcessors(Arrays.asList(annotationProcessors));
+ }
+
+ public void addDirectDependencies(Dep... dependencies) {
+ Dep.merge(dependencies)
+ .compileJars()
+ .forEach(
+ (dependency) -> {
+ assert dependency.toFile().exists();
+ String depString = dependency.toString();
+ taskBuilder.getInputsBuilder().addClasspath(depString);
+ });
+ }
+
+ public void runCompileTask(BiConsumer<CompilationTaskContext, JvmCompilationTask> operation) {
+ JvmCompilationTask task = taskBuilder.build();
+ super.runCompileTask(
+ new CompilationTaskContext(task.getInfo(), System.err),
+ task,
+ (ctx, t) -> {
+ operation.accept(ctx, t);
+ return null;
+ });
+ }
+
+ @SuppressWarnings("unused")
+ public <R> R runCompileTask(BiFunction<CompilationTaskContext, JvmCompilationTask, R> operation) {
+ JvmCompilationTask task = taskBuilder.build();
+ return super.runCompileTask(
+ new CompilationTaskContext(task.getInfo(), System.err), task, operation);
+ }
+}
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderResource.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderResource.java
new file mode 100644
index 0000000..02db955
--- /dev/null
+++ b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderResource.java
@@ -0,0 +1,241 @@
+package io.bazel.kotlin.builder;
+
+import io.bazel.kotlin.builder.utils.CompilationTaskContext;
+import io.bazel.kotlin.model.CompilationTaskInfo;
+import org.junit.rules.ExternalResource;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+public abstract class KotlinBuilderResource extends ExternalResource {
+ public enum DirectoryType {
+ INSTANCE_ROOT("test root", null),
+ EXTERNAL("bazel external directory", null),
+ /** The rest of the paths are instance relative. */
+ SOURCES("sources", Paths.get("sources")),
+ CLASSES("compiled classes", Paths.get("classes")),
+ GENERATED_CLASSES("generated classes", Paths.get("generated_classes")),
+ TEMP("temp directory", Paths.get("temp")),
+ SOURCE_GEN("generated sources directory", Paths.get("generated_sources"));
+
+ private static final EnumSet<DirectoryType> INSTANCE_TYPES =
+ EnumSet.of(SOURCES, CLASSES, SOURCE_GEN, GENERATED_CLASSES, TEMP);
+
+ final String name;
+ private final Path relativePath;
+
+ DirectoryType(String name, Path relativePath) {
+ this.name = name;
+ this.relativePath = relativePath;
+ }
+ }
+
+ public abstract static class Dep {
+ private Dep() {}
+
+ abstract Stream<Path> compileJars();
+
+ private static class Simple extends Dep {
+ private final List<Path> compileJars;
+
+ private Simple(List<Path> compileJars) {
+ this.compileJars = compileJars;
+ }
+
+ @Override
+ Stream<Path> compileJars() {
+ return compileJars.stream();
+ }
+ }
+
+ static Dep merge(Dep... dependencies) {
+ return new Simple(
+ Stream.of(dependencies).flatMap(Dep::compileJars).collect(Collectors.toList()));
+ }
+
+ public static List<String> classpathOf(Dep... dependencies) {
+ return merge(dependencies).compileJars().map(Path::toString).collect(Collectors.toList());
+ }
+
+ public static Dep simpleOf(String compileJar) {
+ return new Simple(Collections.singletonList(toPlatformPath(compileJar).toAbsolutePath()));
+ }
+
+ @SuppressWarnings("unused")
+ static Dep simpleOf(Path compileJar) {
+ return new Simple(Collections.singletonList(compileJar));
+ }
+ }
+
+ private static final Path
+ BAZEL_TEST_DIR = Paths.get(Objects.requireNonNull(System.getenv("TEST_TMPDIR"))),
+ EXTERNAL_PATH = Paths.get("external");
+ @SuppressWarnings("unused")
+ public static Dep
+ KOTLIN_ANNOTATIONS =
+ Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/annotations-13.0.jar"),
+ KOTLIN_STDLIB = Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/kotlin-stdlib.jar"),
+ KOTLIN_STDLIB_JDK7 =
+ Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/kotlin-stdlib-jdk7.jar"),
+ KOTLIN_STDLIB_JDK8 =
+ Dep.simpleOf("external/com_github_jetbrains_kotlin/lib/kotlin-stdlib-jdk8.jar");
+
+ private static final AtomicInteger counter = new AtomicInteger(0);
+ private Path instanceRoot = null;
+ private String label = null;
+
+ KotlinBuilderResource() {}
+
+ abstract CompilationTaskInfo.Builder infoBuilder();
+
+ String label() {
+ return Objects.requireNonNull(label);
+ }
+
+ Path instanceRoot() {
+ return Objects.requireNonNull(instanceRoot);
+ }
+
+ @Override
+ protected void before() throws Throwable {
+ label = "a_test_" + counter.incrementAndGet();
+ setTimeout(DEFAULT_TIMEOUT);
+ try {
+ this.instanceRoot = Files.createDirectory(BAZEL_TEST_DIR.resolve(Paths.get(label)));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ for (DirectoryType instanceType : DirectoryType.INSTANCE_TYPES) {
+ try {
+ Files.createDirectory(instanceRoot.resolve(instanceType.relativePath));
+ } catch (IOException e) {
+ throw new RuntimeException("could not create instance directory: " + instanceType.name, e);
+ }
+ }
+ }
+
+ Path directory(DirectoryType type) {
+ switch (type) {
+ case INSTANCE_ROOT:
+ return instanceRoot;
+ case EXTERNAL:
+ return KotlinBuilderResource.EXTERNAL_PATH;
+ case SOURCES:
+ case CLASSES:
+ case GENERATED_CLASSES:
+ case TEMP:
+ case SOURCE_GEN:
+ return instanceRoot.resolve(type.relativePath);
+ default:
+ throw new IllegalStateException(type.toString());
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public final void setDebugTags(String... tags) {
+ infoBuilder().addAllDebug(Arrays.asList(tags));
+ }
+
+ private int DEFAULT_TIMEOUT = 10;
+ private int timeoutSeconds = DEFAULT_TIMEOUT;
+
+ /**
+ * sets the timeout for the builder tasks.
+ *
+ * @param timeoutSeconds a timeout in seconds. For debugging purposes it can be set to <= 0 which
+ * means wait indefinitely.
+ */
+ @SuppressWarnings("WeakerAccess")
+ public final void setTimeout(int timeoutSeconds) {
+ this.timeoutSeconds = timeoutSeconds;
+ }
+
+ <T, R> R runCompileTask(
+ CompilationTaskContext context, T task, BiFunction<CompilationTaskContext, T, R> operation) {
+ String curDir = System.getProperty("user.dir");
+ System.setProperty("user.dir", instanceRoot().toAbsolutePath().toString());
+ try {
+ CompletableFuture<R> future =
+ CompletableFuture.supplyAsync(() -> operation.apply(context, task));
+ return timeoutSeconds > 0 ? future.get(timeoutSeconds, TimeUnit.SECONDS) : future.get();
+
+ } catch (TimeoutException e) {
+ throw new AssertionError("did not complete in: " + timeoutSeconds);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ System.setProperty("user.dir", curDir);
+ }
+ }
+
+ public final void assertFilesExist(DirectoryType dir, String... paths) {
+ assertFileExistence(resolved(dir, paths), true);
+ }
+
+ public void assertFilesExist(String... paths) {
+ assertFileExistence(Stream.of(paths).map(Paths::get), true);
+ }
+
+ @SuppressWarnings("unused")
+ public final void assertFilesDoNotExist(DirectoryType dir, String... filePath) {
+ assertFileExistence(resolved(dir, filePath), false);
+ }
+
+ private static void assertFileExistence(Stream<Path> pathStream, boolean shouldexist) {
+ pathStream.forEach(
+ path -> {
+ if (shouldexist)
+ assertWithMessage("file did not exist: " + path).that(path.toFile().exists()).isTrue();
+ else assertWithMessage("file existed: " + path).that(path.toFile().exists()).isFalse();
+ });
+ }
+
+ private Stream<Path> resolved(DirectoryType dir, String... filePath) {
+ Path directory = directory(dir);
+ return Stream.of(filePath).map(f -> directory.resolve(toPlatformPath(f)));
+ }
+
+ /**
+ * Normalize a path string.
+ *
+ * @param path a path using '/' as the seperator.
+ * @return a path string suitable for the target platform.
+ */
+ private static Path toPlatformPath(String path) {
+ String[] parts = path.split("/");
+ return parts.length == 1
+ ? Paths.get(parts[0])
+ : Paths.get(parts[0], Arrays.copyOfRange(parts, 1, parts.length));
+ }
+
+ @SuppressWarnings("unused")
+ private Stream<Path> directoryContents(DirectoryType type) {
+ try {
+ return Files.walk(directory(type)).map(p -> directory(type).relativize(p));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ public final void logDirectoryContents(DirectoryType type) {
+ System.out.println(
+ directoryContents(type)
+ .map(Path::toString)
+ .collect(Collectors.joining("\n", "directory " + type.name + " contents:\n", "")));
+ }
+}
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderTestCase.java b/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderTestCase.java
deleted file mode 100644
index 3a3b7de..0000000
--- a/src/test/kotlin/io/bazel/kotlin/builder/KotlinBuilderTestCase.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package io.bazel.kotlin.builder;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
-import io.bazel.kotlin.builder.toolchain.KotlinToolchain;
-import io.bazel.kotlin.builder.utils.CompilationTaskContext;
-import io.bazel.kotlin.model.JvmCompilationTask;
-import io.bazel.kotlin.model.KotlinToolchainInfo;
-import io.bazel.kotlin.model.Platform;
-import io.bazel.kotlin.model.RuleKind;
-import org.junit.Before;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static com.google.common.base.Charsets.UTF_8;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-public abstract class KotlinBuilderTestCase {
- private static final Path BAZEL_TEST_DIR =
- Paths.get(Preconditions.checkNotNull(System.getenv("TEST_TMPDIR")));
- private static final AtomicInteger counter = new AtomicInteger(0);
-
- private final JvmCompilationTask.Builder builder = JvmCompilationTask.newBuilder();
- private final KotlinBuilderComponent component =
- DaggerKotlinBuilderComponent.builder().toolchain(KotlinToolchain.createToolchain()).build();
-
- private String label = null;
- private Path inputSourceDir = null;
-
- @Before
- public void setupNext() {
- resetTestContext("a_test_" + counter.incrementAndGet());
- }
-
- protected JvmCompilationTask.Outputs outputs() {
- return builder.getOutputs();
- }
-
- protected JvmCompilationTask.Directories directories() {
- return builder.getDirectories();
- }
-
- protected String label() {
- return Preconditions.checkNotNull(label);
- }
-
- private Path classDir() {
- return Paths.get(directories().getClasses());
- }
-
- protected CompilationTaskContext context() {
- return new CompilationTaskContext(builder.getInfo(), System.err);
- }
-
- protected JvmCompilationTask builderCommand() {
- return builder.build();
- }
-
- protected KotlinBuilderComponent component() {
- return component;
- }
-
- protected void addSource(String filename, String... lines) {
- Path file =
- Preconditions.checkNotNull(inputSourceDir, "initialize test context").resolve(filename);
- try (BufferedWriter writer = com.google.common.io.Files.newWriter(file.toFile(), UTF_8)) {
- writer.write(Joiner.on("\n").join(lines));
- String f = file.toString();
- if (f.endsWith(".kt")) {
- builder.getInputsBuilder().addKotlinSources(f);
- } else if (f.endsWith(".java")) {
- builder.getInputsBuilder().addJavaSources(f);
- } else {
- throw new RuntimeException("unhandled file type: " + f);
- }
-
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- protected void resetTestContext(String label) {
- this.label = label;
- Path prefixPath = Paths.get(label);
-
- createTestOuputDirectory(prefixPath);
- inputSourceDir = Paths.get(createTestOuputDirectory(prefixPath.resolve("input_sources")));
-
- builder.clear();
- builder
- .getInfoBuilder()
- .setLabel("//some/bogus:" + label)
- .setModuleName("some_bogus_module")
- .setPlatform(Platform.JVM)
- .setRuleKind(RuleKind.LIBRARY)
- .setToolchainInfo(
- KotlinToolchainInfo.newBuilder()
- .setCommon(
- KotlinToolchainInfo.Common.newBuilder()
- .setApiVersion("1.2")
- .setCoroutines("enabled")
- .setLanguageVersion("1.2"))
- .setJvm(KotlinToolchainInfo.Jvm.newBuilder().setJvmTarget("1.8")));
- builder
- .getDirectoriesBuilder()
- .setClasses(prefixPath.resolve("classes").toAbsolutePath().toString())
- .setGeneratedSources(prefixPath.resolve("sources").toAbsolutePath().toString())
- .setTemp(prefixPath.resolve("temp").toAbsolutePath().toString())
- .setGeneratedClasses(prefixPath.resolve("generated_classes").toAbsolutePath().toString());
- builder
- .getOutputsBuilder()
- .setJar(prefixPath.resolve("jar_file.jar").toAbsolutePath().toString())
- .setJdeps(prefixPath.resolve("jdeps_file.jdeps").toAbsolutePath().toString())
- .setSrcjar(prefixPath.resolve("jar_file-sources.jar").toAbsolutePath().toString());
- }
-
- private static String createTestOuputDirectory(Path path) {
- try {
- return Files.createDirectory(BAZEL_TEST_DIR.resolve(path)).toAbsolutePath().toString();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- private static String createTestOutputFile(Path path) {
- try {
- return Files.createFile(BAZEL_TEST_DIR.resolve(path)).toAbsolutePath().toString();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- protected enum DirectoryType {
- ROOT,
- CLASSES,
- GENERATED_CLASSES,
- TEMP,
- SOURCE_GEN;
-
- public static Path select(DirectoryType type, JvmCompilationTask command) {
- Path ret;
- switch (type) {
- case CLASSES:
- ret = Paths.get(command.getDirectories().getClasses());
- break;
- case GENERATED_CLASSES:
- ret = Paths.get(command.getDirectories().getGeneratedClasses());
- break;
- case TEMP:
- ret = Paths.get(command.getDirectories().getTemp());
- break;
- case SOURCE_GEN:
- ret = Paths.get(command.getDirectories().getGeneratedSources());
- break;
- default:
- throw new RuntimeException("unhandled type: " + type);
- }
- return ret;
- }
- }
-
- protected void assertFileExists(DirectoryType dir, String filePath) {
- Path file = DirectoryType.select(dir, builderCommand()).resolve(filePath);
- assertFileExists(file.toString());
- }
-
- void assertFileDoesNotExist(String filePath) {
- assertWithMessage("file exisst: " + filePath).that(new File(filePath).exists()).isFalse();
- }
-
- protected void assertFileExists(String filePath) {
- assertWithMessage("file did not exist: " + filePath).that(new File(filePath).exists()).isTrue();
- }
-}
diff --git a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java
index e1e4373..386bf8c 100644
--- a/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java
+++ b/src/test/kotlin/io/bazel/kotlin/builder/tasks/jvm/KotlinBuilderJvmTest.java
@@ -1,71 +1,177 @@
package io.bazel.kotlin.builder.tasks.jvm;
-import com.google.common.truth.Truth;
-import com.google.devtools.build.lib.view.proto.Deps;
-import io.bazel.kotlin.builder.KotlinBuilderTestCase;
+import io.bazel.kotlin.builder.DaggerKotlinBuilderComponent;
+import io.bazel.kotlin.builder.KotlinBuilderComponent;
+import io.bazel.kotlin.builder.KotlinBuilderJvmTestTask;
+import io.bazel.kotlin.builder.KotlinBuilderResource.Dep;
+import io.bazel.kotlin.builder.KotlinBuilderResource.DirectoryType;
+import io.bazel.kotlin.builder.toolchain.KotlinToolchain;
import io.bazel.kotlin.builder.utils.CompilationTaskContext;
+import io.bazel.kotlin.model.AnnotationProcessor;
import io.bazel.kotlin.model.JvmCompilationTask;
+import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
+import static io.bazel.kotlin.builder.KotlinBuilderResource.KOTLIN_ANNOTATIONS;
+import static io.bazel.kotlin.builder.KotlinBuilderResource.KOTLIN_STDLIB;
-public class KotlinBuilderJvmTest extends KotlinBuilderTestCase {
+public class KotlinBuilderJvmTest {
+ private static Dep AUTO_VALUE =
+ Dep.simpleOf(
+ "external/io_bazel_rules_kotlin_com_google_auto_value_auto_value"
+ + "/jar/io_bazel_rules_kotlin_com_google_auto_value_auto_value.jar");
+
+ private static final AnnotationProcessor AUTO_VALUE_ANNOTATION_PROCESSOR =
+ AnnotationProcessor.newBuilder()
+ .setLabel("autovalue")
+ .setProcessorClass("com.google.auto.value.processor.AutoValueProcessor")
+ .addAllClasspath(Dep.classpathOf(AUTO_VALUE, KOTLIN_ANNOTATIONS))
+ .build();
+
+ private static final KotlinBuilderComponent component =
+ DaggerKotlinBuilderComponent.builder().toolchain(KotlinToolchain.createToolchain()).build();
+
+ @Rule public KotlinBuilderJvmTestTask ctx = new KotlinBuilderJvmTestTask();
+
@Test
- public void testSimpleCompile() {
- addSource("AClass.kt", "package something;" + "class AClass{}");
- runCompileTask();
- assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
+ public void testSimpleMixedModeCompile() {
+ ctx.addSource("AClass.kt", "package something;" + "class AClass{}");
+ ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}");
+ ctx.runCompileTask(this::jvmCompilationTask);
+ ctx.assertFilesExist(
+ DirectoryType.CLASSES, "something/AClass.class", "something/AnotherClass.class");
}
@Test
- public void testMixedModeCompile() {
- addSource("AClass.kt", "package something;" + "class AClass{}");
- addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}");
- runCompileTask();
- assertFileExists(DirectoryType.CLASSES, "something/AClass.class");
- assertFileExists(DirectoryType.CLASSES, "something/AnotherClass.class");
- assertFileExists(outputs().getJar());
+ public void testMixedBiReferences() {
+ ctx.addSource(
+ "AClass.java",
+ "package a;",
+ "",
+ "import b.BClass;",
+ "",
+ "public class AClass {",
+ " static BClass b = new BClass();",
+ "}");
+ ctx.addSource(
+ "BClass.kt",
+ "package b",
+ "",
+ "import a.AClass",
+ "",
+ "class BClass() {",
+ " val a = AClass()",
+ "}");
+ ctx.runCompileTask(this::jvmCompilationTask);
+ ctx.assertFilesExist(DirectoryType.CLASSES, "a/AClass.class", "b/BClass.class");
}
- private void runCompileTask() {
- JvmCompilationTask command = builderCommand();
- for (DirectoryType directoryType : DirectoryType.values()) {
- try {
- if (directoryType != DirectoryType.ROOT) {
- Files.createDirectories(DirectoryType.select(directoryType, command));
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- int timeoutSeconds = 10;
- try {
+ @Test
+ public void testKaptKt() {
+ ctx.addSource(
+ "TestKtValue.kt",
+ "package autovalue\n"
+ + "\n"
+ + "import com.google.auto.value.AutoValue\n"
+ + "\n"
+ + "@AutoValue\n"
+ + "abstract class TestKtValue {\n"
+ + " abstract fun name(): String\n"
+ + " fun builder(): Builder = AutoValue_TestKtValue.Builder()\n"
+ + "\n"
+ + " @AutoValue.Builder\n"
+ + " abstract class Builder {\n"
+ + " abstract fun setName(name: String): Builder\n"
+ + " abstract fun build(): TestKtValue\n"
+ + " }\n"
+ + "}");
+ ctx.addAnnotationProcessors(AUTO_VALUE_ANNOTATION_PROCESSOR);
+ ctx.addDirectDependencies(AUTO_VALUE, KOTLIN_STDLIB);
+ ctx.runCompileTask(this::jvmCompilationTask);
+ ctx.assertFilesExist(
+ DirectoryType.CLASSES,
+ "autovalue/TestKtValue.class",
+ "autovalue/AutoValue_TestKtValue.class");
+ ctx.assertFilesExist(DirectoryType.SOURCE_GEN, "autovalue/AutoValue_TestKtValue.java");
+ }
- CompletableFuture.runAsync(
- () ->
- component()
- .jvmTaskExecutor()
- .execute(new CompilationTaskContext(command.getInfo(), System.err), command))
- .get(timeoutSeconds, TimeUnit.SECONDS);
- } catch (TimeoutException e) {
- throw new AssertionError("did not complete in: " + timeoutSeconds);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- assertFileExists(outputs().getJar());
- assertFileExists(outputs().getJdeps());
- try (FileInputStream fs = new FileInputStream(Paths.get(outputs().getJdeps()).toFile())) {
- Deps.Dependencies dependencies = Deps.Dependencies.parseFrom(fs);
- Truth.assertThat(dependencies.getRuleLabel()).endsWith(label());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ @Test
+ public void testMixedKaptBiReferences() {
+ ctx.addSource(
+ "TestKtValue.kt",
+ "package autovalue.a\n"
+ + "\n"
+ + "import com.google.auto.value.AutoValue\n"
+ + "import autovalue.b.TestAutoValue\n"
+ + "\n"
+ + "@AutoValue\n"
+ + "abstract class TestKtValue {\n"
+ + " abstract fun name(): String\n"
+ + " fun builder(): Builder = AutoValue_TestKtValue.Builder()\n"
+ + "\n"
+ + " @AutoValue.Builder\n"
+ + " abstract class Builder {\n"
+ + " abstract fun setName(name: String): Builder\n"
+ + " abstract fun build(): TestKtValue\n"
+ + " }\n"
+ + "}");
+ ctx.addSource(
+ "TestAutoValue.java",
+ "package autovalue.b;\n"
+ + "\n"
+ + "import com.google.auto.value.AutoValue;\n"
+ + "import autovalue.a.TestKtValue;\n"
+ + "\n"
+ + "@AutoValue\n"
+ + "public abstract class TestAutoValue {\n"
+ + " abstract String name();\n"
+ + "\n"
+ + "\n"
+ + " static Builder builder() {\n"
+ + " return new AutoValue_TestAutoValue.Builder();\n"
+ + " }\n"
+ + "\n"
+ + " @AutoValue.Builder\n"
+ + " abstract static class Builder {\n"
+ + " abstract Builder setName(String name);\n"
+ + " abstract TestAutoValue build();\n"
+ + " }\n"
+ + "\n"
+ + "}");
+
+ ctx.addAnnotationProcessors(AUTO_VALUE_ANNOTATION_PROCESSOR);
+ ctx.addDirectDependencies(AUTO_VALUE, KOTLIN_STDLIB);
+ ctx.runCompileTask(this::jvmCompilationTask);
+ ctx.assertFilesExist(
+ DirectoryType.SOURCE_GEN,
+ "autovalue/a/AutoValue_TestKtValue.java",
+ "autovalue/b/AutoValue_TestAutoValue.java");
+ ctx.assertFilesExist(
+ DirectoryType.CLASSES,
+ "autovalue/a/AutoValue_TestKtValue.class",
+ "autovalue/b/AutoValue_TestAutoValue.class");
+ }
+
+ @Test
+ @Ignore("The Kotlin compiler expects a single kotlin file at least.")
+ public void testCompileSingleJavaFile() {
+ ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{}");
+ ctx.runCompileTask(this::jvmCompilationTask);
+ }
+
+ @Test
+ @Ignore(
+ "This test needs to test the output of error handling by the compiler, when the kotlin compiler compiles java "
+ + "this test needs to validate it.")
+ public void testWithJavaError() {
+ ctx.addSource("AClass.kt", "package something;" + "class AClass{}");
+ ctx.addSource("AnotherClass.java", "package something;", "", "class AnotherClass{");
+ ctx.runCompileTask(this::jvmCompilationTask);
+ }
+
+ private void jvmCompilationTask(CompilationTaskContext taskContext, JvmCompilationTask task) {
+ component.jvmTaskExecutor().execute(taskContext, task);
+ ctx.assertFilesExist(task.getOutputs().getJar(), task.getOutputs().getJdeps());
}
}
diff --git a/src/test/kotlin/io/bazel/kotlin/defs.bzl b/src/test/kotlin/io/bazel/kotlin/defs.bzl
index 3272c8e..0415cc6 100644
--- a/src/test/kotlin/io/bazel/kotlin/defs.bzl
+++ b/src/test/kotlin/io/bazel/kotlin/defs.bzl
@@ -36,4 +36,4 @@
kwargs.setdefault("size", "small")
kwargs["deps"] = kwargs.setdefault("deps", []) + ["//src/test/kotlin/io/bazel/kotlin:assertion_test_case"]
kwargs.setdefault("test_class", _get_class_name(kwargs))
- kt_jvm_test(name = name, **kwargs)
\ No newline at end of file
+ kt_jvm_test(name = name, **kwargs)