From a6e9445caa23d0e50e388dd013b933610183472d Mon Sep 17 00:00:00 2001 From: Bor Kae Hwang Date: Wed, 23 Oct 2019 13:48:00 -0600 Subject: [PATCH] Customizable phases --- WORKSPACE | 23 +++++---- scala/private/phases/api.bzl | 37 ++++++++++++++ scala/private/phases/phases.bzl | 2 + scala/private/rules/scala_binary.bzl | 24 ++++++--- scala/private/rules/scala_junit_test.bzl | 24 ++++++--- scala/private/rules/scala_library.bzl | 62 +++++++++++++++-------- scala/private/rules/scala_repl.bzl | 24 ++++++--- scala/private/rules/scala_test.bzl | 26 ++++++---- scala/providers.bzl | 7 +++ scala/scala.bzl | 15 ++++++ test/phase/BUILD | 22 ++++++++ test/phase/HelloBinary.scala | 7 +++ test/phase/HelloLibrary.scala | 5 ++ test/phase/customizability_test.bzl | 35 +++++++++++++ test/phase/phase_customizability_test.bzl | 7 +++ test/shell/test_helper.sh | 19 +++++++ test/shell/test_phase.sh | 20 ++++++++ test_rules_scala.sh | 1 + 18 files changed, 295 insertions(+), 65 deletions(-) create mode 100644 test/phase/BUILD create mode 100644 test/phase/HelloBinary.scala create mode 100644 test/phase/HelloLibrary.scala create mode 100644 test/phase/customizability_test.bzl create mode 100644 test/phase/phase_customizability_test.bzl create mode 100755 test/shell/test_phase.sh diff --git a/WORKSPACE b/WORKSPACE index e74e13b1e2..35b0a9c603 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,6 +2,18 @@ workspace(name = "io_bazel_rules_scala") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +http_archive( + name = "com_github_bazelbuild_buildtools", + sha256 = "cdaac537b56375f658179ee2f27813cac19542443f4722b6730d84e4125355e6", + strip_prefix = "buildtools-f27d1753c8b3210d9e87cdc9c45bc2739ae2c2db", + url = "https://github.com/bazelbuild/buildtools/archive/f27d1753c8b3210d9e87cdc9c45bc2739ae2c2db.zip", +) + +load("@com_github_bazelbuild_buildtools//buildifier:deps.bzl", "buildifier_dependencies") + +buildifier_dependencies() + load("//scala:scala.bzl", "scala_repositories") scala_repositories() @@ -151,13 +163,6 @@ http_archive( url = "https://github.com/bazelbuild/rules_go/releases/download/0.18.7/rules_go-0.18.7.tar.gz", ) -http_archive( - name = "com_github_bazelbuild_buildtools", - sha256 = "cdaac537b56375f658179ee2f27813cac19542443f4722b6730d84e4125355e6", - strip_prefix = "buildtools-f27d1753c8b3210d9e87cdc9c45bc2739ae2c2db", - url = "https://github.com/bazelbuild/buildtools/archive/f27d1753c8b3210d9e87cdc9c45bc2739ae2c2db.zip", -) - load( "@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", @@ -168,10 +173,6 @@ go_rules_dependencies() go_register_toolchains() -load("@com_github_bazelbuild_buildtools//buildifier:deps.bzl", "buildifier_dependencies") - -buildifier_dependencies() - http_archive( name = "bazel_toolchains", sha256 = "5962fe677a43226c409316fcb321d668fc4b7fa97cb1f9ef45e7dc2676097b26", diff --git a/scala/private/phases/api.bzl b/scala/private/phases/api.bzl index afbcee020a..76e41c73a0 100644 --- a/scala/private/phases/api.bzl +++ b/scala/private/phases/api.bzl @@ -1,4 +1,33 @@ +load( + "@io_bazel_rules_scala//scala:providers.bzl", + _ScalaRulePhase = "ScalaRulePhase", +) + +def _adjust_phases(phases, adjustments): + if len(adjustments) == 0: + return phases + phases = phases[:] + for (relation, peer_name, name, function) in adjustments: + for idx, (needle, _) in enumerate(phases): + if needle == peer_name: + if relation in ["-", "before"]: + phases.insert(idx, (name, function)) + elif relation in ["+", "after"]: + phases.insert(idx + 1, (name, function)) + elif relation in ["=", "replace"]: + phases[idx] = (name, function) + return phases + def run_phases(ctx, phases): + phase_providers = [ + p[_ScalaRulePhase] + for p in ctx.attr._phase_providers + if _ScalaRulePhase in p + ] + + if phase_providers != []: + phases = _adjust_phases(phases, [p for pp in phase_providers for p in pp.phases]) + global_provider = {} current_provider = struct(**global_provider) for (name, function) in phases: @@ -8,3 +37,11 @@ def run_phases(ctx, phases): current_provider = struct(**global_provider) return current_provider + +def extras_phases(extras): + return { + "_phase_providers": attr.label_list( + default = [pp for extra in extras for pp in extra["phase_providers"]], + providers = [_ScalaRulePhase], + ), + } diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl index bbe975425d..e18387ef63 100644 --- a/scala/private/phases/phases.bzl +++ b/scala/private/phases/phases.bzl @@ -1,5 +1,6 @@ load( "@io_bazel_rules_scala//scala/private:phases/api.bzl", + _extras_phases = "extras_phases", _run_phases = "run_phases", ) load( @@ -64,6 +65,7 @@ load("@io_bazel_rules_scala//scala/private:phases/phase_coverage_runfiles.bzl", # API run_phases = _run_phases +extras_phases = _extras_phases # init phase_common_init = _phase_common_init diff --git a/scala/private/rules/scala_binary.bzl b/scala/private/rules/scala_binary.bzl index c0b8cb26d0..7e13a20758 100644 --- a/scala/private/rules/scala_binary.bzl +++ b/scala/private/rules/scala_binary.bzl @@ -1,5 +1,6 @@ """Builds Scala binaries""" +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") load( "@io_bazel_rules_scala//scala/private:common_attributes.bzl", "common_attrs", @@ -10,6 +11,7 @@ load( load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", "phase_binary_coda", "phase_binary_compile", "phase_common_collect_jars", @@ -54,11 +56,17 @@ _scala_binary_attrs.update(common_attrs) _scala_binary_attrs.update(resolve_deps) -scala_binary = rule( - attrs = _scala_binary_attrs, - executable = True, - fragments = ["java"], - outputs = common_outputs, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_binary_impl, -) +def make_scala_binary(*extras): + return rule( + attrs = _dicts.add( + _scala_binary_attrs, + extras_phases(extras), + ), + executable = True, + fragments = ["java"], + outputs = common_outputs, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_binary_impl, + ) + +scala_binary = make_scala_binary() diff --git a/scala/private/rules/scala_junit_test.bzl b/scala/private/rules/scala_junit_test.bzl index 67217507f1..03d2d7313e 100644 --- a/scala/private/rules/scala_junit_test.bzl +++ b/scala/private/rules/scala_junit_test.bzl @@ -1,5 +1,6 @@ """Rules for writing tests with JUnit""" +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") load( "@io_bazel_rules_scala//scala/private:common_attributes.bzl", "common_attrs", @@ -9,6 +10,7 @@ load( load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", "phase_binary_coda", "phase_common_init", "phase_common_java_wrapper", @@ -106,11 +108,17 @@ _scala_junit_test_attrs.update({ "tests_from": attr.label_list(providers = [[JavaInfo]]), }) -scala_junit_test = rule( - attrs = _scala_junit_test_attrs, - fragments = ["java"], - outputs = common_outputs, - test = True, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_junit_test_impl, -) +def make_scala_junit_test(*extras): + return rule( + attrs = _dicts.add( + _scala_junit_test_attrs, + extras_phases(extras), + ), + fragments = ["java"], + outputs = common_outputs, + test = True, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_junit_test_impl, + ) + +scala_junit_test = make_scala_junit_test() diff --git a/scala/private/rules/scala_library.bzl b/scala/private/rules/scala_library.bzl index 2ac1914c47..0aefff85c8 100644 --- a/scala/private/rules/scala_library.bzl +++ b/scala/private/rules/scala_library.bzl @@ -1,3 +1,4 @@ +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") load( "@io_bazel_rules_scala//scala/private:common.bzl", "sanitize_string_for_usage", @@ -16,6 +17,7 @@ load( ) load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", "phase_common_collect_jars", "phase_library_coda", "phase_library_compile", @@ -70,13 +72,19 @@ _scala_library_attrs.update(_library_attrs) _scala_library_attrs.update(resolve_deps) -scala_library = rule( - attrs = _scala_library_attrs, - fragments = ["java"], - outputs = common_outputs, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_library_impl, -) +def make_scala_library(*extras): + return rule( + attrs = _dicts.add( + _scala_library_attrs, + extras_phases(extras), + ), + fragments = ["java"], + outputs = common_outputs, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_library_impl, + ) + +scala_library = make_scala_library() # Scala library suite generates a series of scala libraries # then it depends on them with a meta one which exports all the sub targets @@ -135,13 +143,19 @@ _scala_library_for_plugin_bootstrapping_attrs.update( common_attrs_for_plugin_bootstrapping, ) -scala_library_for_plugin_bootstrapping = rule( - attrs = _scala_library_for_plugin_bootstrapping_attrs, - fragments = ["java"], - outputs = common_outputs, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_library_for_plugin_bootstrapping_impl, -) +def make_scala_library_for_plugin_bootstrapping(*extras): + return rule( + attrs = _dicts.add( + _scala_library_for_plugin_bootstrapping_attrs, + extras_phases(extras), + ), + fragments = ["java"], + outputs = common_outputs, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_library_for_plugin_bootstrapping_impl, + ) + +scala_library_for_plugin_bootstrapping = make_scala_library_for_plugin_bootstrapping() ## # scala_macro_library @@ -184,10 +198,16 @@ _scala_macro_library_attrs["unused_dependency_checker_mode"] = attr.string( mandatory = False, ) -scala_macro_library = rule( - attrs = _scala_macro_library_attrs, - fragments = ["java"], - outputs = common_outputs, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_macro_library_impl, -) +def make_scala_macro_library(*extras): + return rule( + attrs = _dicts.add( + _scala_macro_library_attrs, + extras_phases(extras), + ), + fragments = ["java"], + outputs = common_outputs, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_macro_library_impl, + ) + +scala_macro_library = make_scala_macro_library() diff --git a/scala/private/rules/scala_repl.bzl b/scala/private/rules/scala_repl.bzl index 209be55a7f..0a3d1ee183 100644 --- a/scala/private/rules/scala_repl.bzl +++ b/scala/private/rules/scala_repl.bzl @@ -1,5 +1,6 @@ """Rule for launching a Scala REPL with dependencies""" +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") load( "@io_bazel_rules_scala//scala/private:common_attributes.bzl", "common_attrs", @@ -10,6 +11,7 @@ load( load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", "phase_binary_coda", "phase_common_init", "phase_common_runfiles", @@ -53,11 +55,17 @@ _scala_repl_attrs.update(common_attrs) _scala_repl_attrs.update(resolve_deps) -scala_repl = rule( - attrs = _scala_repl_attrs, - executable = True, - fragments = ["java"], - outputs = common_outputs, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_repl_impl, -) +def make_scala_repl(*extras): + return rule( + attrs = _dicts.add( + _scala_repl_attrs, + extras_phases(extras), + ), + executable = True, + fragments = ["java"], + outputs = common_outputs, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_repl_impl, + ) + +scala_repl = make_scala_repl() diff --git a/scala/private/rules/scala_test.bzl b/scala/private/rules/scala_test.bzl index ed47dddb1c..35b609f962 100644 --- a/scala/private/rules/scala_test.bzl +++ b/scala/private/rules/scala_test.bzl @@ -1,5 +1,6 @@ """Rules for writing tests with ScalaTest""" +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") load( "@io_bazel_rules_scala//scala/private:common_attributes.bzl", "common_attrs", @@ -10,6 +11,7 @@ load("@io_bazel_rules_scala//scala/private:common.bzl", "sanitize_string_for_usa load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", "phase_common_init", "phase_common_java_wrapper", "phase_common_scala_provider", @@ -91,15 +93,21 @@ _scala_test_attrs.update(common_attrs) _scala_test_attrs.update(_test_resolve_deps) -scala_test = rule( - attrs = _scala_test_attrs, - executable = True, - fragments = ["java"], - outputs = common_outputs, - test = True, - toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], - implementation = _scala_test_impl, -) +def make_scala_test(*extras): + return rule( + attrs = _dicts.add( + _scala_test_attrs, + extras_phases(extras), + ), + executable = True, + fragments = ["java"], + outputs = common_outputs, + test = True, + toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"], + implementation = _scala_test_impl, + ) + +scala_test = make_scala_test() # This auto-generates a test suite based on the passed set of targets # we will add a root test_suite with the name of the passed name diff --git a/scala/providers.bzl b/scala/providers.bzl index 5a92d45258..5a23ebfe34 100644 --- a/scala/providers.bzl +++ b/scala/providers.bzl @@ -57,3 +57,10 @@ declare_scalac_provider = rule( "default_macro_classpath": attr.label_list(allow_files = True), }, ) + +ScalaRulePhase = provider( + doc = "A Scala compiler plugin", + fields = { + "phases": "the phases to add", + }, +) diff --git a/scala/scala.bzl b/scala/scala.bzl index fb4a9eeaf7..f63e1521b0 100644 --- a/scala/scala.bzl +++ b/scala/scala.bzl @@ -8,6 +8,7 @@ load( ) load( "@io_bazel_rules_scala//scala/private:rules/scala_binary.bzl", + _make_scala_binary = "make_scala_binary", _scala_binary = "scala_binary", ) load( @@ -16,10 +17,14 @@ load( ) load( "@io_bazel_rules_scala//scala/private:rules/scala_junit_test.bzl", + _make_scala_junit_test = "make_scala_junit_test", _scala_junit_test = "scala_junit_test", ) load( "@io_bazel_rules_scala//scala/private:rules/scala_library.bzl", + _make_scala_library = "make_scala_library", + _make_scala_library_for_plugin_bootstrapping = "make_scala_library_for_plugin_bootstrapping", + _make_scala_macro_library = "make_scala_macro_library", _scala_library = "scala_library", _scala_library_for_plugin_bootstrapping = "scala_library_for_plugin_bootstrapping", _scala_library_suite = "scala_library_suite", @@ -27,10 +32,12 @@ load( ) load( "@io_bazel_rules_scala//scala/private:rules/scala_repl.bzl", + _make_scala_repl = "make_scala_repl", _scala_repl = "scala_repl", ) load( "@io_bazel_rules_scala//scala/private:rules/scala_test.bzl", + _make_scala_test = "make_scala_test", _scala_test = "scala_test", _scala_test_suite = "scala_test_suite", ) @@ -60,3 +67,11 @@ scala_repl = _scala_repl scala_repositories = _scala_repositories scala_test = _scala_test scala_test_suite = _scala_test_suite + +make_scala_binary = _make_scala_binary +make_scala_library = _make_scala_library +make_scala_library_for_plugin_bootstrapping = _make_scala_library_for_plugin_bootstrapping +make_scala_macro_library = _make_scala_macro_library +make_scala_repl = _make_scala_repl +make_scala_junit_test = _make_scala_junit_test +make_scala_test = _make_scala_test diff --git a/test/phase/BUILD b/test/phase/BUILD new file mode 100644 index 0000000000..f842eef4c8 --- /dev/null +++ b/test/phase/BUILD @@ -0,0 +1,22 @@ +load( + "//test/phase:customizability_test.bzl", + "add_phase_customizability_test_singleton", + "customizability_test_scala_binary", + "customizability_test_scala_library", +) + +add_phase_customizability_test_singleton( + name = "phase_customizability_test", + visibility = ["//visibility:public"], +) + +customizability_test_scala_binary( + name = "HelloBinary", + srcs = ["HelloBinary.scala"], + main_class = "scalarules.test.phase.HelloBinary", +) + +customizability_test_scala_library( + name = "HelloLibrary", + srcs = ["HelloLibrary.scala"], +) diff --git a/test/phase/HelloBinary.scala b/test/phase/HelloBinary.scala new file mode 100644 index 0000000000..3bc83a23e2 --- /dev/null +++ b/test/phase/HelloBinary.scala @@ -0,0 +1,7 @@ +package scalarules.test.phase + +object HelloBinary { + def main(args: Array[String]) { + println("You can customize binary phases!") + } +} diff --git a/test/phase/HelloLibrary.scala b/test/phase/HelloLibrary.scala new file mode 100644 index 0000000000..8b3855fe5c --- /dev/null +++ b/test/phase/HelloLibrary.scala @@ -0,0 +1,5 @@ +package scalarules.test.phase + +object HelloLibrary { + println("You can customize library phases!") +} diff --git a/test/phase/customizability_test.bzl b/test/phase/customizability_test.bzl new file mode 100644 index 0000000000..afd1b35830 --- /dev/null +++ b/test/phase/customizability_test.bzl @@ -0,0 +1,35 @@ +load( + "//scala:providers.bzl", + _ScalaRulePhase = "ScalaRulePhase", +) +load( + "//test/phase:phase_customizability_test.bzl", + _phase_customizability_test = "phase_customizability_test", +) +load( + "//scala:scala.bzl", + _make_scala_binary = "make_scala_binary", + _make_scala_library = "make_scala_library", +) + +ext_add_phase_customizability_test = { + "phase_providers": [ + "//test/phase:phase_customizability_test", + ], +} + +def _add_phase_customizability_test_singleton_implementation(ctx): + return [ + _ScalaRulePhase( + phases = [ + ("-", "coda", "customizability_test", _phase_customizability_test), + ], + ), + ] + +add_phase_customizability_test_singleton = rule( + implementation = _add_phase_customizability_test_singleton_implementation, +) + +customizability_test_scala_binary = _make_scala_binary(ext_add_phase_customizability_test) +customizability_test_scala_library = _make_scala_library(ext_add_phase_customizability_test) diff --git a/test/phase/phase_customizability_test.bzl b/test/phase/phase_customizability_test.bzl new file mode 100644 index 0000000000..ee95c10622 --- /dev/null +++ b/test/phase/phase_customizability_test.bzl @@ -0,0 +1,7 @@ +# +# PHASE: customizability test +# +# A dummy test phase to make sure rules are customizable +# +def phase_customizability_test(ctx, p): + print("customizable phase") diff --git a/test/shell/test_helper.sh b/test/shell/test_helper.sh index 7e5f1b9891..99e42eb1b2 100755 --- a/test/shell/test_helper.sh +++ b/test/shell/test_helper.sh @@ -60,6 +60,25 @@ action_should_fail_with_message() { fi } +action_should_contain_message() { + set +e + MSG=$1 + TEST_ARG=${@:2} + RES=$(bazel $TEST_ARG 2>&1) + RESPONSE_CODE=$? + echo $RES | grep -- "$MSG" + GREP_RES=$? + if [ $RESPONSE_CODE -ne 0 ]; then + echo -e "${RED} \"bazel $TEST_ARG\" should pass but failed. $NC" + exit 1 + elif [ $GREP_RES -ne 0 ]; then + echo -e "${RED} \"bazel $TEST_ARG\" should pass with message \"$MSG\" but did not. $NC" + exit 1 + else + exit 0 + fi +} + test_expect_failure_or_warning_on_missing_direct_deps_with_expected_message() { set +e diff --git a/test/shell/test_phase.sh b/test/shell/test_phase.sh new file mode 100755 index 0000000000..016fdf060a --- /dev/null +++ b/test/shell/test_phase.sh @@ -0,0 +1,20 @@ +# shellcheck source=./test_runner.sh +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +. "${dir}"/test_runner.sh +. "${dir}"/test_helper.sh +runner=$(get_test_runner "${1:-local}") + +test_binary_with_extra_phase() { + action_should_contain_message \ + "customizable phase" \ + build //test/phase:HelloBinary +} + +test_library_with_extra_phase() { + action_should_contain_message \ + "customizable phase" \ + build //test/phase:HelloLibrary +} + +$runner test_binary_with_extra_phase +$runner test_library_with_extra_phase diff --git a/test_rules_scala.sh b/test_rules_scala.sh index 04b1ced740..e8456fabfe 100755 --- a/test_rules_scala.sh +++ b/test_rules_scala.sh @@ -29,6 +29,7 @@ $runner bazel test //test/... --extra_toolchains="//test_expect_failure/plus_one . "${test_dir}"/test_javac_jvm_flags.sh . "${test_dir}"/test_junit.sh . "${test_dir}"/test_misc.sh +. "${test_dir}"/test_phase.sh . "${test_dir}"/test_scala_binary.sh . "${test_dir}"/test_scalac_jvm_flags.sh . "${test_dir}"/test_scala_classpath.sh