diff --git a/MODULE.bazel b/MODULE.bazel index c66cee4..03fef2d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,11 +19,11 @@ module( compatibility_level = 1, ) -bazel_dep(name = "aspect_bazel_lib", version = "2.5.3") +bazel_dep(name = "aspect_bazel_lib", version = "2.9.4") bazel_dep(name = "bazel_skylib", version = "1.7.1") bazel_dep(name = "platforms", version = "0.0.10") -bazel_dep(name = "rules_java", version = "8.3.2") -bazel_dep(name = "rules_jvm_external", version = "6.2") +bazel_dep(name = "rules_java", version = "8.6.1") +bazel_dep(name = "rules_jvm_external", version = "6.6") pkl = use_extension("//pkl:extensions.bzl", "pkl") use_repo( @@ -35,10 +35,11 @@ use_repo( ) register_toolchains( - "@rules_pkl//pkl:pkl_toolchain_macos_aarch64", - "@rules_pkl//pkl:pkl_toolchain_macos_amd64", + "@rules_pkl//pkl:pkl_toolchain_codegen_java", "@rules_pkl//pkl:pkl_toolchain_linux_aarch64", "@rules_pkl//pkl:pkl_toolchain_linux_amd64", + "@rules_pkl//pkl:pkl_toolchain_macos_aarch64", + "@rules_pkl//pkl:pkl_toolchain_macos_amd64", ) maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") @@ -48,6 +49,7 @@ maven.install( "org.pkl-lang:pkl-tools:0.27.0", "org.jetbrains.kotlin:kotlin-stdlib:1.7.10", "com.google.code.gson:gson:2.10.1", + "org.junit.vintage:junit-vintage-engine:5.7.0", ], lock_file = "//pkl/private:pkl_deps_install.json", repositories = [ @@ -63,7 +65,7 @@ use_repo( bazel_dep(name = "bazel_skylib_gazelle_plugin", version = "1.4.1", dev_dependency = True) bazel_dep(name = "buildifier_prebuilt", version = "6.1.0", dev_dependency = True) bazel_dep(name = "gazelle", version = "0.35.0", dev_dependency = True, repo_name = "bazel_gazelle") -bazel_dep(name = "stardoc", version = "0.7.0", dev_dependency = True) +bazel_dep(name = "stardoc", version = "0.7.2", dev_dependency = True) bazel_dep(name = "rules_python", version = "0.34.0", dev_dependency = True) python = use_extension( diff --git a/docs/rules_pkl_docs.md b/docs/rules_pkl_docs.md index 12b4cf0..bf8072b 100644 --- a/docs/rules_pkl_docs.md +++ b/docs/rules_pkl_docs.md @@ -2,6 +2,27 @@ Public API re-exports + + +## pkl_codegen_java_toolchain + +
+load("@rules_pkl//pkl:defs.bzl", "pkl_codegen_java_toolchain") + +pkl_codegen_java_toolchain(name, cli) ++ + + +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| cli | - | Label | optional | `"@rules_pkl//pkl:pkl_codegen_java_cli"` | + + ## pkl_doc_toolchain @@ -152,6 +173,55 @@ pkl_toolchain(name, cli | - | Label | optional | `None` | + + +## pkl_config_java_library + +
+load("@rules_pkl//pkl:defs.bzl", "pkl_config_java_library") + +pkl_config_java_library(name, files, module_path, generate_getters, deps, tags, kwargs) ++ +Create a compiled JAR of Java source files generated from Pkl source files. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | A unique name for this target. | none | +| files | The Pkl files that are used to generate the Java source files. | none | +| module_path | List of Java module targets. Must export provide the JavaInfo provider. | `[]` | +| generate_getters | Generate private final fields and public getter methods instead of public final fields. Defaults to True. | `None` | +| deps | Other targets to include in the Pkl module path when building this Java library. Must be pkl_* targets. | `[]` | +| tags | Bazel tags to add to this target. | `[]` | +| kwargs | Further keyword arguments. E.g. visibility | none | + + + + +## pkl_config_src + +
+load("@rules_pkl//pkl:defs.bzl", "pkl_config_src") + +pkl_config_src(name, files, module_path, kwargs) ++ +Create a JAR containing the generated Java source files from Pkl files. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | A unique name for this target. | none | +| files | The Pkl source files used to generate the Java source files. | none | +| module_path | List of Java module targets. Must export provide the JavaInfo provider. | `None` | +| kwargs | Further keyword arguments. E.g. visibility | none | + + ## pkl_doc diff --git a/pkl/BUILD.bazel b/pkl/BUILD.bazel index cb2ed43..efdf8d2 100644 --- a/pkl/BUILD.bazel +++ b/pkl/BUILD.bazel @@ -15,7 +15,7 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load("@rules_java//java:defs.bzl", "java_binary") load("@rules_jvm_external//:defs.bzl", "artifact") load("//pkl:repositories.bzl", "DEFAULT_PKL_VERSION") -load(":defs.bzl", "pkl_doc_toolchain", "pkl_toolchain") +load(":defs.bzl", "pkl_codegen_java_toolchain", "pkl_doc_toolchain", "pkl_toolchain") filegroup( name = "all_files", @@ -120,6 +120,21 @@ toolchain( toolchain_type = ":toolchain_type", ) +toolchain_type( + name = "codegen_toolchain_type", +) + +pkl_codegen_java_toolchain( + name = "pkl_codegen_java", + visibility = ["//visibility:public"], +) + +toolchain( + name = "pkl_toolchain_codegen_java", + toolchain = ":pkl_codegen_java", + toolchain_type = ":codegen_toolchain_type", +) + toolchain_type( name = "doc_toolchain_type", ) @@ -143,6 +158,21 @@ bzl_library( "//pkl/private:docs", "@bazel_tools//tools/build_defs/repo:http.bzl", "@bazel_tools//tools/build_defs/repo:utils.bzl", + "@rules_java//java/private:proto_support", + ], +) + +java_binary( + name = "pkl_codegen_java_cli", + main_class = "org.pkl.codegen.java.Main", + visibility = [ + "//visibility:public", + ], + runtime_deps = [ + artifact( + "org.pkl-lang:pkl-tools", + repository_name = "rules_pkl_deps", + ), ], ) diff --git a/pkl/defs.bzl b/pkl/defs.bzl index d30b8e5..4e3a057 100644 --- a/pkl/defs.bzl +++ b/pkl/defs.bzl @@ -20,6 +20,11 @@ load( _pkl_eval = "pkl_eval", _pkl_test = "pkl_test", ) +load( + "//pkl/private:pkl_codegen.bzl", + _pkl_config_java_library = "pkl_config_java_library", + _pkl_config_src = "pkl_config_src", +) load( "//pkl/private:pkl_doc.bzl", _pkl_doc = "pkl_doc", @@ -38,10 +43,14 @@ load( ) load( "//pkl/private:toolchain.bzl", + _pkl_codegen_java_toolchain = "pkl_codegen_java_toolchain", _pkl_doc_toolchain = "pkl_doc_toolchain", _pkl_toolchain = "pkl_toolchain", ) +pkl_codegen_java_toolchain = _pkl_codegen_java_toolchain +pkl_config_java_library = _pkl_config_java_library +pkl_config_src = _pkl_config_src pkl_doc = _pkl_doc pkl_doc_toolchain = _pkl_doc_toolchain pkl_library = _pkl_library diff --git a/pkl/private/constants.bzl b/pkl/private/constants.bzl index 5af146a..6b941de 100644 --- a/pkl/private/constants.bzl +++ b/pkl/private/constants.bzl @@ -17,9 +17,12 @@ Constants. PKL_DEPS = { "0.26.1": [ + "com.google.code.gson:gson:2.10.1", + "org.junit.vintage:junit-vintage-engine:5.7.0", + "org.pkl-lang:pkl-codegen-java:0.26.1", + "org.pkl-lang:pkl-config-java:0.26.1", "org.pkl-lang:pkl-doc:0.26.1", "org.pkl-lang:pkl-tools:0.26.1", - "com.google.code.gson:gson:2.10.1", ], "0.26.3": [ "org.pkl-lang:pkl-doc:0.26.3", diff --git a/pkl/private/pkl_codegen.bzl b/pkl/private/pkl_codegen.bzl new file mode 100644 index 0000000..82647c5 --- /dev/null +++ b/pkl/private/pkl_codegen.bzl @@ -0,0 +1,147 @@ +""" +Implementation for 'pkl_config_src' and 'pkl_config_java_library' macros. +""" + +load("@rules_jvm_external//:defs.bzl", "artifact") + +def _to_short_path(f, _expander): + return f.tree_relative_path + "=" + f.path + +def _zipit(ctx, outfile, files): + zip_args = ctx.actions.args() + zip_args.add_all("cC", [outfile.path]) + zip_args.add_all(files, map_each = _to_short_path) + ctx.actions.run( + inputs = files, + outputs = [outfile], + executable = ctx.executable._zip, + arguments = [zip_args], + progress_message = "Writing via zip: %s" % outfile.basename, + ) + +def _pkl_codegen_impl(ctx): + modules = depset(transitive = [depset(dep[JavaInfo].runtime_output_jars) for dep in ctx.attr.module_path]).to_list() + java_codegen_toolchain = ctx.toolchains["//pkl:codegen_toolchain_type"] + + # Generate Java from PKL + outdir = ctx.actions.declare_directory(ctx.attr.name, sibling = None) + gen_args = ctx.actions.args() + + gen_args.add("--no-cache") + if len(modules): + gen_args.add_all(["--module-path", ctx.configuration.host_path_separator.join([module.path for module in modules])]) + if ctx.attr.generate_getters: + gen_args.add_all(["--generate-getters"]) + gen_args.add_all("-o", [outdir.path]) + gen_args.add_all(ctx.files.files) + + ctx.actions.run( + inputs = ctx.files.files + modules, + outputs = [outdir], + executable = java_codegen_toolchain.codegen_cli, + arguments = [gen_args], + tools = [ + java_codegen_toolchain.cli_files_to_run, + ], + progress_message = "Generating Java sources from Pkl %s" % (ctx.label), + ) + + # Create JAR + outjar = ctx.outputs.out + _zipit( + ctx = ctx, + outfile = ctx.outputs.out, + files = [outdir], + ) + + # Return JAR + return OutputGroupInfo(out = [outjar]) + +_pkl_codegen = rule( + _pkl_codegen_impl, + attrs = { + "files": attr.label_list( + mandatory = True, + allow_files = [".pkl"], + doc = "The Pkl source files.", + ), + "generate_getters": attr.bool( + doc = "Whether to generate getters in the AppConfig. Defaults to True", + default = True, + ), + "module_path": attr.label_list( + providers = [ + [JavaInfo], + ], + doc = "List of Java module targets. Must export provide the JavaInfo provider.", + ), + "out": attr.output( + doc = "The output JAR generated by this action.", + ), + "_zip": attr.label( + allow_single_file = True, + cfg = "exec", + default = "@bazel_tools//tools/zip:zipper", + executable = True, + ), + }, + toolchains = [ + "//pkl:codegen_toolchain_type", + ], +) + +def pkl_config_src(name, files, module_path = None, **kwargs): + """Create a JAR containing the generated Java source files from Pkl files. + + Args: + name: A unique name for this target. + files: The Pkl source files used to generate the Java source files. + module_path: List of Java module targets. Must export provide the JavaInfo provider. + **kwargs: Further keyword arguments. E.g. visibility + """ + _pkl_codegen( + name = name, + files = files, + module_path = module_path, + out = name + "_codegen.srcjar", + **kwargs + ) + +def pkl_config_java_library(name, files, module_path = [], generate_getters = None, deps = [], tags = [], **kwargs): + """Create a compiled JAR of Java source files generated from Pkl source files. + + Args: + name: A unique name for this target. + files: The Pkl files that are used to generate the Java source files. + module_path: List of Java module targets. Must export provide the JavaInfo provider. + generate_getters: Generate private final fields and public getter methods instead of public final fields. Defaults to True. + deps: Other targets to include in the Pkl module path when building this Java library. Must be pkl_* targets. + tags: Bazel tags to add to this target. + **kwargs: Further keyword arguments. E.g. visibility + """ + name_generated_code = name + "_pkl" + + pkl_config_src( + name = name_generated_code, + files = files, + generate_getters = generate_getters, + module_path = module_path, + tags = tags, + ) + + pkl_deps = [artifact("org.pkl-lang:pkl-tools", repository_name = "rules_pkl_deps")] + + # Ensure that there are no duplicate entries in the deps + all_deps = depset( + pkl_deps + module_path, + transitive = [depset([dep]) for dep in deps], + ) + + native.java_library( + name = name, + srcs = [name_generated_code], + deps = all_deps.to_list(), + resources = files, + tags = tags + [] if "no-lint" in tags else ["no-lint"], + **kwargs + ) diff --git a/pkl/private/pkl_deps_install.json b/pkl/private/pkl_deps_install.json index 9190654..84c12f4 100755 --- a/pkl/private/pkl_deps_install.json +++ b/pkl/private/pkl_deps_install.json @@ -1,7 +1,7 @@ { "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", - "__INPUT_ARTIFACTS_HASH": -77776129, - "__RESOLVED_ARTIFACTS_HASH": 443997981, + "__INPUT_ARTIFACTS_HASH": 1648045667, + "__RESOLVED_ARTIFACTS_HASH": 1195298318, "artifacts": { "com.google.code.gson:gson": { "shasums": { @@ -9,6 +9,24 @@ }, "version": "2.10.1" }, + "junit:junit": { + "shasums": { + "jar": "4b8532f63bdc0e0661507f947eb324a954d1dbac631ad19c8aa9a00feed1d863" + }, + "version": "4.13" + }, + "org.apiguardian:apiguardian-api": { + "shasums": { + "jar": "a9aae9ff8ae3e17a2a18f79175e82b16267c246fbbd3ca9dfbbb290b08dcfdd4" + }, + "version": "1.1.0" + }, + "org.hamcrest:hamcrest-core": { + "shasums": { + "jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9" + }, + "version": "1.3" + }, "org.jetbrains.kotlin:kotlin-stdlib": { "shasums": { "jar": "e771fe74250a943e8f6346713201ff1d8cb95c3a5d1a91a22b65a9e04f6a8901" @@ -27,6 +45,30 @@ }, "version": "13.0" }, + "org.junit.platform:junit-platform-commons": { + "shasums": { + "jar": "5330ee87cc7586e6e25175a34e9251624ff12ff525269d3415d0b4ca519b6fea" + }, + "version": "1.7.0" + }, + "org.junit.platform:junit-platform-engine": { + "shasums": { + "jar": "75f21a20dc594afdc875736725b408cec6d0344874d29f34b2dd3075500236f2" + }, + "version": "1.7.0" + }, + "org.junit.vintage:junit-vintage-engine": { + "shasums": { + "jar": "75644937ecec00284d0a1a228e99f2010c92b15d63f8d443ef0cd01bf554fcfd" + }, + "version": "5.7.0" + }, + "org.opentest4j:opentest4j": { + "shasums": { + "jar": "58812de60898d976fb81ef3b62da05c6604c18fd4a249f5044282479fc286af2" + }, + "version": "1.2.0" + }, "org.pkl-lang:pkl-tools": { "shasums": { "jar": "27efd30d137875d3892f0e72480096c20f3b6530e5097c81e9fca143563a5323" @@ -35,9 +77,25 @@ } }, "dependencies": { + "junit:junit": [ + "org.hamcrest:hamcrest-core" + ], "org.jetbrains.kotlin:kotlin-stdlib": [ "org.jetbrains.kotlin:kotlin-stdlib-common", "org.jetbrains:annotations" + ], + "org.junit.platform:junit-platform-commons": [ + "org.apiguardian:apiguardian-api" + ], + "org.junit.platform:junit-platform-engine": [ + "org.apiguardian:apiguardian-api", + "org.junit.platform:junit-platform-commons", + "org.opentest4j:opentest4j" + ], + "org.junit.vintage:junit-vintage-engine": [ + "junit:junit", + "org.apiguardian:apiguardian-api", + "org.junit.platform:junit-platform-engine" ] }, "packages": { @@ -52,6 +110,48 @@ "com.google.gson.reflect", "com.google.gson.stream" ], + "junit:junit": [ + "junit.extensions", + "junit.framework", + "junit.runner", + "junit.textui", + "org.junit", + "org.junit.experimental", + "org.junit.experimental.categories", + "org.junit.experimental.max", + "org.junit.experimental.results", + "org.junit.experimental.runners", + "org.junit.experimental.theories", + "org.junit.experimental.theories.internal", + "org.junit.experimental.theories.suppliers", + "org.junit.function", + "org.junit.internal", + "org.junit.internal.builders", + "org.junit.internal.management", + "org.junit.internal.matchers", + "org.junit.internal.requests", + "org.junit.internal.runners", + "org.junit.internal.runners.model", + "org.junit.internal.runners.rules", + "org.junit.internal.runners.statements", + "org.junit.matchers", + "org.junit.rules", + "org.junit.runner", + "org.junit.runner.manipulation", + "org.junit.runner.notification", + "org.junit.runners", + "org.junit.runners.model", + "org.junit.runners.parameterized", + "org.junit.validator" + ], + "org.apiguardian:apiguardian-api": [ + "org.apiguardian.api" + ], + "org.hamcrest:hamcrest-core": [ + "org.hamcrest", + "org.hamcrest.core", + "org.hamcrest.internal" + ], "org.jetbrains.kotlin:kotlin-stdlib": [ "kotlin", "kotlin.annotation", @@ -88,6 +188,34 @@ "org.intellij.lang.annotations", "org.jetbrains.annotations" ], + "org.junit.platform:junit-platform-commons": [ + "org.junit.platform.commons", + "org.junit.platform.commons.annotation", + "org.junit.platform.commons.function", + "org.junit.platform.commons.logging", + "org.junit.platform.commons.support", + "org.junit.platform.commons.util" + ], + "org.junit.platform:junit-platform-engine": [ + "org.junit.platform.engine", + "org.junit.platform.engine.discovery", + "org.junit.platform.engine.reporting", + "org.junit.platform.engine.support.config", + "org.junit.platform.engine.support.descriptor", + "org.junit.platform.engine.support.discovery", + "org.junit.platform.engine.support.filter", + "org.junit.platform.engine.support.hierarchical" + ], + "org.junit.vintage:junit-vintage-engine": [ + "org.junit.vintage.engine", + "org.junit.vintage.engine.descriptor", + "org.junit.vintage.engine.discovery", + "org.junit.vintage.engine.execution", + "org.junit.vintage.engine.support" + ], + "org.opentest4j:opentest4j": [ + "org.opentest4j" + ], "org.pkl-lang:pkl-tools": [ "org.pkl.cli", "org.pkl.cli.commands", @@ -430,13 +558,25 @@ "repositories": { "https://repo1.maven.org/maven2/": [ "com.google.code.gson:gson", + "junit:junit", + "org.apiguardian:apiguardian-api", + "org.hamcrest:hamcrest-core", "org.jetbrains.kotlin:kotlin-stdlib", "org.jetbrains.kotlin:kotlin-stdlib-common", "org.jetbrains:annotations", + "org.junit.platform:junit-platform-commons", + "org.junit.platform:junit-platform-engine", + "org.junit.vintage:junit-vintage-engine", + "org.opentest4j:opentest4j", "org.pkl-lang:pkl-tools" ] }, "services": { + "org.junit.vintage:junit-vintage-engine": { + "org.junit.platform.engine.TestEngine": [ + "org.junit.vintage.engine.VintageTestEngine" + ] + }, "org.pkl-lang:pkl-tools": { "java.nio.file.spi.FileTypeDetector": [ "org.pkl.core.runtime.VmFileDetector" diff --git a/pkl/private/toolchain.bzl b/pkl/private/toolchain.bzl index 046f640..60c9681 100644 --- a/pkl/private/toolchain.bzl +++ b/pkl/private/toolchain.bzl @@ -56,6 +56,24 @@ pkl_toolchain = rule( }, ) +def _pkl_codegen_java_toolchain_impl(ctx): + toolchain_info = platform_common.ToolchainInfo( + codegen_cli = ctx.executable.cli, + cli_files_to_run = ctx.attr.cli[DefaultInfo].files_to_run, + ) + return [toolchain_info] + +pkl_codegen_java_toolchain = rule( + _pkl_codegen_java_toolchain_impl, + attrs = { + "cli": attr.label( + default = "//pkl:pkl_codegen_java_cli", + executable = True, + cfg = "exec", + ), + }, +) + def _pkl_doc_toolchain_impl(ctx): toolchain_info = platform_common.ToolchainInfo( pkl_doc_cli = ctx.executable.cli, diff --git a/tests/pkl_codegen_java/BUILD.bazel b/tests/pkl_codegen_java/BUILD.bazel new file mode 100644 index 0000000..06d02a0 --- /dev/null +++ b/tests/pkl_codegen_java/BUILD.bazel @@ -0,0 +1,47 @@ +load("@rules_jvm_external//:defs.bzl", "artifact") +load("//pkl:defs.bzl", "pkl_config_java_library", "pkl_config_src") + +pkl_config_src( + name = "base", + files = [ + "srcs/base.pkl", + ], +) + +java_library( + name = "gen_lib", + srcs = [":base"], + deps = [artifact( + "org.pkl-lang:pkl-tools", + repository_name = "rules_pkl_deps", + )], +) + +java_test( + name = "litter_test", + srcs = ["TestPklCodegen.java"], + resource_strip_prefix = "tests/pkl_codegen_java", + resources = [ + "srcs/base.pkl", + "srcs/litter.pkl", + ], + test_class = "TestPklCodegen", + deps = [ + artifact( + "junit:junit", + repository_name = "rules_pkl_deps", + ), + artifact( + "org.pkl-lang:pkl-tools", + repository_name = "rules_pkl_deps", + ), + ":gen_lib", + ], +) + +pkl_config_java_library( + name = "pkl_config_java_lib", + files = [ + "srcs/base.pkl", + ], +) diff --git a/tests/pkl_codegen_java/TestPklCodegen.java b/tests/pkl_codegen_java/TestPklCodegen.java new file mode 100644 index 0000000..e5f7f20 --- /dev/null +++ b/tests/pkl_codegen_java/TestPklCodegen.java @@ -0,0 +1,39 @@ +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +import org.pkl.config.java.ConfigEvaluator; +import org.pkl.config.java.Config; +import org.pkl.config.java.JavaType; +import org.pkl.core.ModuleSource; + +import java.util.List; + +import org.Dogs; + + +public class TestPklCodegen { + @Test + public void verifyReadingPklFiles() { + ConfigEvaluator evaluator = ConfigEvaluator.preconfigured(); + Config config = evaluator.evaluate(ModuleSource.modulePath("srcs/litter.pkl")); + + org.Dogs.Litter actual = config.get("puppies").as(JavaType.of(Dogs.Litter.class)); + org.Dogs.Litter expected = new org.Dogs.Litter(List.of(new Dogs.Dog("Bruno"), new Dogs.Dog("Bruce"))); + + assertEquals(expected, actual); + } + + @Test + public void verifyGeneratedClasses() { + org.Dogs.Litter litter = new org.Dogs.Litter(List.of( + new Dogs.Dog("A"), + new Dogs.Dog("B") + )); + + org.Dogs.Litter expected = new org.Dogs.Litter(List.of(new Dogs.Dog("A"), new Dogs.Dog("B"))); + + assertEquals(expected, litter); + } + +} diff --git a/tests/pkl_codegen_java/srcs/base.pkl b/tests/pkl_codegen_java/srcs/base.pkl new file mode 100644 index 0000000..74dafcd --- /dev/null +++ b/tests/pkl_codegen_java/srcs/base.pkl @@ -0,0 +1,11 @@ +module org.dogs + +class Dog { + name: String +} + +class Litter { + dogs: Listing