diff --git a/README.md b/README.md index 0dcdd22d8..f50f81cc1 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,16 @@ Unused dependency checking can either be enabled globally for all targets using in these cases you can enable unused dependency checking globally through a toolchain and override individual misbehaving targets using the attribute. +## Advanced configurable rules +To make the ruleset more flexible and configurable, we introduce a phase architecture. By using a phase architecture, where rule implementations are defined as a list of phases that are executed sequentially, functionality can easily be added (or modified) by adding (or swapping) phases. + +Phases provide 3 major benefits: + - Consumers are able to configure the rules to their specific use cases by defining new phases within their workspace without impacting other consumers. + - Contributors are able to implement new functionalities by creating additional default phases. + - Phases give us more clear idea what steps are shared across rules. + +See [Customizable Phase](docs/customizable_phase.md) for more info. + ## Building from source Test & Build: ``` diff --git a/WORKSPACE b/WORKSPACE index 692dcfd4e..44227568f 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -3,6 +3,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") load("@bazel_tools//tools/build_defs/repo:jvm.bzl", "jvm_maven_import_external") + +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() @@ -162,13 +174,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", @@ -179,10 +184,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/docs/customizable_phase.md b/docs/customizable_phase.md new file mode 100644 index 000000000..5d53ac359 --- /dev/null +++ b/docs/customizable_phase.md @@ -0,0 +1,149 @@ +# Customizable Phase + +## Contents +* [Overview](#overview) +* [Who needs customizable phase](#who-needs-customizable-phase) +* [As a consumer](#as-a-consumer) +* [As a contributor](#as-a-contributor) + * [Phase naming convention](#phase-naming-convention) + +## Overview +Phases increase configurability. Rule implementations are defined as a list of phases. Each phase defines a specific step, which helps breaking up implementation into smaller and more readable groups. Some phases are independent from others, which means the order doesn't matter. However, some phases depend on outputs of previous phases, in this case, we should make sure it meets all the prerequisites before executing phases. + +The biggest benefit of phases is that it is customizable. If default phase A is not doing what you expect, you may switch it with your self-defined phase A. One use case is to write your own compilation phase with your favorite Scala compiler. You may also extend the default phase list for more functionalities. One use case is to check the Scala format. + +## Who needs customizable phase +Customizable phase is an advanced feature for people who want the rules to do more. If you are an experienced Bazel rules developer, we make this powerful API public for you to do custom work without impacting other consumers. If you have no experience on writing Bazel rules, we are happy to help but be aware it may be frustrating at first. + +If you don't need to customize your rules and just need the default setup to work correctly, then just load the following file for default rules: +``` +load("@io_bazel_rules_scala//scala:scala.bzl") +``` +Otherwise read on: + +## As a consumer +You need to load the following 2 files: +``` +load("@io_bazel_rules_scala//scala:advanced_usage/providers.bzl", "ScalaRulePhase") +load("@io_bazel_rules_scala//scala:advanced_usage/scala.bzl", "make_scala_binary") +``` +`ScalaRulePhase` is a phase provider to pass in custom phases. Rules with `make_` prefix, like `make_scala_binary`, are customizable rules. `make_`s take a dictionary as input. It currently supports appending `attrs` and `outputs` to default rules, as well as modifying the phase list. + +For example: +``` +ext_add_custom_phase = { + "attrs": { + "custom_content": attr.string( + default = "This is custom content", + ), + }, + "outputs": { + "custom_output": "%{name}.custom-output", + }, + "phase_providers": [ + "//custom/phase:phase_custom_write_extra_file", + ], +} + +custom_scala_binary = make_scala_binary(ext_add_custom_phase) +``` +`make_`s append `attrs` and `outputs` to the default rule definitions. All items in `attrs` can be accessed by `ctx.attr`, and all items in `outputs` can be accessed by `ctx.outputs`. `phase_providers` takes a list of targets which define how you want to modify phase list. +``` +def _add_custom_phase_singleton_implementation(ctx): + return [ + ScalaRulePhase( + custom_phases = [ + ("last", "", "custom_write_extra_file", phase_custom_write_extra_file), + ], + ), + ] + +add_custom_phase_singleton = rule( + implementation = _add_custom_phase_singleton_implementation, +) +``` +`add_custom_phase_singleton` is a rule solely to pass in custom phases using `ScalaRulePhase`. The `custom_phases` field in `ScalaRulePhase` takes a list of tuples. Each tuple has 4 elements: +``` +(relation, peer_name, phase_name, phase_function) +``` + - relation: the position to add a new phase + - peer_name: the existing phase to compare the position with + - phase_name: the name of the new phase, also used to access phase information + - phase_function: the function of the new phase + +There are 5 possible relations: + - `^` or `first` + - `$` or `last` + - `-` or `before` + - `+` or `after` + - `=` or `replace` + +The symbols and words are interchangable. If `first` or `last` is used, it puts your custom phase at the beginning or the end of the phase list, `peer_name` is not needed. + +Then you have to call the rule in a `BUILD` +``` +add_custom_phase_singleton( + name = "phase_custom_write_extra_file", + visibility = ["//visibility:public"], +) +``` + +You may now see `phase_providers` in `ext_add_custom_phase` is pointing to this target. + +The last step is to write the function of the phase. For example: +``` +def phase_custom_write_extra_file(ctx, p): + ctx.actions.write( + output = ctx.outputs.custom_output, + content = ctx.attr.custom_content, + ) +``` +Every phase has 2 arguments, `ctx` and `p`. `ctx` gives you access to the fields defined in rules. `p` is the global provider, which contains information from initial state as well as all the previous phases. You may access the information from previous phases by `p..`. For example, if the previous phase, said `phase_jar` with phase name `jar`, returns a struct +``` +def phase_jar(ctx, p): + # Some works to get the jars + return struct( + class_jar = class_jar, + ijar = ijar, + ) +``` +You are able to access information like `p.jar.class_jar` in `phase_custom_write_extra_file`. You can provide the information for later phases in the same way, then they can access it by `p.custom_write_extra_file.`. + +You should be able to define the files above entirely in your own workspace without making change to the [bazelbuild/rules_scala](https://github.com/bazelbuild/rules_scala). If you believe your custom phase will be valuable to the community, please refer to [As a contributor](#as-a-contributor). Pull requests are welcome. + +## As a contributor +Besides the basics in [As a consumer](#as-a-consumer), the followings help you understand how phases are setup if you plan to contribute to [bazelbuild/rules_scala](https://github.com/bazelbuild/rules_scala). + +These are the relevant files + - `scala/private/phases/api.bzl`: the API of executing and modifying the phase list + - `scala/private/phases/phases.bzl`: re-expose phases for convenience + - `scala/private/phases/phase_.bzl`: all the phase definitions + +Currently phase architecture is used by 7 rules: + - scala_library + - scala_macro_library + - scala_library_for_plugin_bootstrapping + - scala_binary + - scala_test + - scala_junit_test + - scala_repl + +In each of the rule implementation, it calls `run_phases` and returns the information from `phase_final`, which groups the final returns of the rule. To prevent consumers from accidently removing `phase_final` from the list, we make it a non-customizable phase. + +To make a new phase, you have to define a new `phase_.bzl` in `scala/private/phases/`. Function definition should have 2 arguments, `ctx` and `p`. You may expose the information for later phases by returning a `struct`. In some phases, there are multiple phase functions since different rules may take slightly different input arguemnts. You may want to re-expose the phase definition in `scala/private/phases/phases.bzl`, so it's more convenient to access in rule files. + +In the rule implementations, put your new phase in `builtin_customizable_phases` list. The phases are executed sequentially, the order matters if the new phase depends on previous phases. + +If you are making new return fields of the rule, remember to modify `phase_final`. + +### Phase naming convention +Files in `scala/private/phases/` + - `phase_.bzl`: phase definition file + +Function names in `phase_.bzl` + - `phase__`: function with custom inputs of specific rule + - `phase_common_`: function without custom inputs + - `_phase_default_`: private function that takes `_args` for custom inputs + - `_phase_`: private function with the actual logic + +See `phase_compile.bzl` for example. diff --git a/scala/advanced_usage/providers.bzl b/scala/advanced_usage/providers.bzl new file mode 100644 index 000000000..da329eccb --- /dev/null +++ b/scala/advanced_usage/providers.bzl @@ -0,0 +1,11 @@ +""" +A phase provider for customizable rules +It is used only when you intend to add functionalities to existing default rules +""" + +ScalaRulePhase = provider( + doc = "A custom phase plugin", + fields = { + "custom_phases": "The phases to add. It takes an array of (relation, peer_name, phase_name, phase_function). Please refer to docs/customizable_phase.md for more details.", + }, +) diff --git a/scala/advanced_usage/scala.bzl b/scala/advanced_usage/scala.bzl new file mode 100644 index 000000000..552532880 --- /dev/null +++ b/scala/advanced_usage/scala.bzl @@ -0,0 +1,35 @@ +""" +Re-expose the customizable rules +It is used only when you intend to add functionalities to existing default rules +""" + +load( + "@io_bazel_rules_scala//scala/private:rules/scala_binary.bzl", + _make_scala_binary = "make_scala_binary", +) +load( + "@io_bazel_rules_scala//scala/private:rules/scala_junit_test.bzl", + _make_scala_junit_test = "make_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", +) +load( + "@io_bazel_rules_scala//scala/private:rules/scala_repl.bzl", + _make_scala_repl = "make_scala_repl", +) +load( + "@io_bazel_rules_scala//scala/private:rules/scala_test.bzl", + _make_scala_test = "make_scala_test", +) + +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/scala/private/phases/api.bzl b/scala/private/phases/api.bzl new file mode 100644 index 000000000..cc8a340af --- /dev/null +++ b/scala/private/phases/api.bzl @@ -0,0 +1,86 @@ +""" +The phase API for rules implementation +""" + +load( + "@io_bazel_rules_scala//scala:advanced_usage/providers.bzl", + _ScalaRulePhase = "ScalaRulePhase", +) + +# A method to modify the built-in phase list +# - Insert new phases to the first/last position +# - Insert new phases before/after existing phases +# - Replace existing phases +def _adjust_phases(phases, adjustments): + # Return when no adjustment needed + if len(adjustments) == 0: + return phases + phases = phases[:] + + # relation: the position to add a new phase + # peer_name: the existing phase to compare the position with + # phase_name: the name of the new phase, also used to access phase information + # phase_function: the function of the new phase + for (relation, peer_name, phase_name, phase_function) in adjustments: + for idx, (needle, _) in enumerate(phases): + if relation in ["^", "first"]: + phases.insert(0, (phase_name, phase_function)) + elif relation in ["$", "last"]: + phases.append((phase_name, phase_function)) + elif needle == peer_name: + if relation in ["-", "before"]: + phases.insert(idx, (phase_name, phase_function)) + elif relation in ["+", "after"]: + phases.insert(idx + 1, (phase_name, phase_function)) + elif relation in ["=", "replace"]: + phases[idx] = (phase_name, phase_function) + return phases + +# Execute phases +def run_phases(ctx, builtin_customizable_phases, fixed_phase): + # Loading custom phases + # Phases must be passed in by provider + phase_providers = [ + phase_provider[_ScalaRulePhase] + for phase_provider in ctx.attr._phase_providers + if _ScalaRulePhase in phase_provider + ] + + # Modify the built-in phase list + adjusted_phases = _adjust_phases( + builtin_customizable_phases, + [ + phase + for phase_provider in phase_providers + for phase in phase_provider.custom_phases + ], + ) + + # A placeholder for data shared with later phases + global_provider = {} + current_provider = struct(**global_provider) + for (name, function) in adjusted_phases + [fixed_phase]: + # Run a phase + new_provider = function(ctx, current_provider) + + # If a phase returns data, append it to global_provider + # for later phases to access + if new_provider != None: + global_provider[name] = new_provider + current_provider = struct(**global_provider) + + # The final return of rules implementation + return current_provider + +# A method to pass in phase provider +def extras_phases(extras): + return { + "_phase_providers": attr.label_list( + default = [ + phase_provider + for extra in extras + for phase_provider in extra["phase_providers"] + ], + providers = [_ScalaRulePhase], + ), + } diff --git a/scala/private/phases/phase_collect_exports_jars.bzl b/scala/private/phases/phase_collect_exports_jars.bzl new file mode 100644 index 000000000..35a83dbe3 --- /dev/null +++ b/scala/private/phases/phase_collect_exports_jars.bzl @@ -0,0 +1,15 @@ +# +# PHASE: collect exports jars +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:common.bzl", + "collect_jars", +) + +def phase_collect_exports_jars(ctx, p): + # Add information from exports (is key that AFTER all build actions/runfiles analysis) + # Since after, will not show up in deploy_jar or old jars runfiles + # Notice that compile_jars is intentionally transitive for exports + return collect_jars(ctx.attr.exports) diff --git a/scala/private/phases/phase_collect_jars.bzl b/scala/private/phases/phase_collect_jars.bzl new file mode 100644 index 000000000..19c22feee --- /dev/null +++ b/scala/private/phases/phase_collect_jars.bzl @@ -0,0 +1,74 @@ +# +# PHASE: collect jars +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "collect_jars_from_common_ctx", +) + +def phase_scalatest_collect_jars(ctx, p): + args = struct( + base_classpath = p.scalac_provider.default_classpath + [ctx.attr._scalatest], + extra_runtime_deps = [ + ctx.attr._scalatest_reporter, + ctx.attr._scalatest_runner, + ], + ) + return _phase_default_collect_jars(ctx, p, args) + +def phase_repl_collect_jars(ctx, p): + args = struct( + base_classpath = p.scalac_provider.default_repl_classpath, + ) + return _phase_default_collect_jars(ctx, p, args) + +def phase_macro_library_collect_jars(ctx, p): + args = struct( + base_classpath = p.scalac_provider.default_macro_classpath, + ) + return _phase_default_collect_jars(ctx, p, args) + +def phase_junit_test_collect_jars(ctx, p): + args = struct( + extra_deps = [ + ctx.attr._junit, + ctx.attr._hamcrest, + ctx.attr.suite_label, + ctx.attr._bazel_test_runner, + ], + ) + return _phase_default_collect_jars(ctx, p, args) + +def phase_library_for_plugin_bootstrapping_collect_jars(ctx, p): + args = struct( + unused_dependency_checker_mode = "off", + ) + return _phase_default_collect_jars(ctx, p, args) + +def phase_common_collect_jars(ctx, p): + return _phase_default_collect_jars(ctx, p) + +def _phase_default_collect_jars(ctx, p, _args = struct()): + return _phase_collect_jars( + ctx, + _args.base_classpath if hasattr(_args, "base_classpath") else p.scalac_provider.default_classpath, + _args.extra_deps if hasattr(_args, "extra_deps") else [], + _args.extra_runtime_deps if hasattr(_args, "extra_runtime_deps") else [], + _args.unused_dependency_checker_mode if hasattr(_args, "unused_dependency_checker_mode") else p.unused_deps_checker, + ) + +def _phase_collect_jars( + ctx, + base_classpath, + extra_deps, + extra_runtime_deps, + unused_dependency_checker_mode): + return collect_jars_from_common_ctx( + ctx, + base_classpath, + extra_deps, + extra_runtime_deps, + unused_dependency_checker_mode == "off", + ) diff --git a/scala/private/phases/phase_collect_srcjars.bzl b/scala/private/phases/phase_collect_srcjars.bzl new file mode 100644 index 000000000..020ce99cb --- /dev/null +++ b/scala/private/phases/phase_collect_srcjars.bzl @@ -0,0 +1,14 @@ +# +# PHASE: collect srcjars +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:common.bzl", + "collect_srcjars", +) + +def phase_collect_srcjars(ctx, p): + # This will be used to pick up srcjars from non-scala library + # targets (like thrift code generation) + return collect_srcjars(ctx.attr.deps) diff --git a/scala/private/phases/phase_compile.bzl b/scala/private/phases/phase_compile.bzl new file mode 100644 index 000000000..b29ca266c --- /dev/null +++ b/scala/private/phases/phase_compile.bzl @@ -0,0 +1,152 @@ +# +# PHASE: compile +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "compile_or_empty", + "pack_source_jars", +) + +def phase_binary_compile(ctx, p): + args = struct( + buildijar = False, + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_classpath + + ctx.attr.unused_dependency_checker_ignored_targets + ], + ) + return _phase_default_compile(ctx, p, args) + +def phase_library_compile(ctx, p): + args = struct( + srcjars = p.collect_srcjars, + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_classpath + ctx.attr.exports + + ctx.attr.unused_dependency_checker_ignored_targets + ], + ) + return _phase_default_compile(ctx, p, args) + +def phase_library_for_plugin_bootstrapping_compile(ctx, p): + args = struct( + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_classpath + ctx.attr.exports + ], + unused_dependency_checker_mode = "off", + ) + return _phase_default_compile(ctx, p, args) + +def phase_macro_library_compile(ctx, p): + args = struct( + buildijar = False, + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_macro_classpath + ctx.attr.exports + + ctx.attr.unused_dependency_checker_ignored_targets + ], + ) + return _phase_default_compile(ctx, p, args) + +def phase_junit_test_compile(ctx, p): + args = struct( + buildijar = False, + implicit_junit_deps_needed_for_java_compilation = [ + ctx.attr._junit, + ctx.attr._hamcrest, + ], + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_classpath + + ctx.attr.unused_dependency_checker_ignored_targets + ] + [ + ctx.attr._junit.label, + ctx.attr._hamcrest.label, + ctx.attr.suite_label.label, + ctx.attr._bazel_test_runner.label, + ], + ) + return _phase_default_compile(ctx, p, args) + +def phase_repl_compile(ctx, p): + args = struct( + buildijar = False, + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_repl_classpath + + ctx.attr.unused_dependency_checker_ignored_targets + ], + ) + return _phase_default_compile(ctx, p, args) + +def phase_scalatest_compile(ctx, p): + args = struct( + buildijar = False, + unused_dependency_checker_ignored_targets = [ + target.label + for target in p.scalac_provider.default_classpath + + ctx.attr.unused_dependency_checker_ignored_targets + ], + ) + return _phase_default_compile(ctx, p, args) + +def phase_common_compile(ctx, p): + return _phase_default_compile(ctx, p) + +def _phase_default_compile(ctx, p, _args = struct()): + return _phase_compile( + ctx, + p, + _args.srcjars if hasattr(_args, "srcjars") else depset(), + _args.buildijar if hasattr(_args, "buildijar") else True, + _args.implicit_junit_deps_needed_for_java_compilation if hasattr(_args, "implicit_junit_deps_needed_for_java_compilation") else [], + _args.unused_dependency_checker_ignored_targets if hasattr(_args, "unused_dependency_checker_ignored_targets") else [], + _args.unused_dependency_checker_mode if hasattr(_args, "unused_dependency_checker_mode") else p.unused_deps_checker, + ) + +def _phase_compile( + ctx, + p, + srcjars, + buildijar, + # TODO: generalize this hack + implicit_junit_deps_needed_for_java_compilation, + unused_dependency_checker_ignored_targets, + unused_dependency_checker_mode): + manifest = ctx.outputs.manifest + jars = p.collect_jars.compile_jars + rjars = p.collect_jars.transitive_runtime_jars + transitive_compile_jars = p.collect_jars.transitive_compile_jars + jars2labels = p.collect_jars.jars2labels.jars_to_labels + deps_providers = p.collect_jars.deps_providers + + out = compile_or_empty( + ctx, + manifest, + jars, + srcjars, + buildijar, + transitive_compile_jars, + jars2labels, + implicit_junit_deps_needed_for_java_compilation, + unused_dependency_checker_mode, + unused_dependency_checker_ignored_targets, + deps_providers, + ) + + # TODO: simplify the return values and use provider + return struct( + class_jar = out.class_jar, + coverage = out.coverage, + full_jars = out.full_jars, + ijar = out.ijar, + ijars = out.ijars, + rjars = depset(out.full_jars, transitive = [rjars]), + java_jar = out.java_jar, + source_jars = pack_source_jars(ctx) + out.source_jars, + merged_provider = out.merged_provider, + ) diff --git a/scala/private/phases/phase_coverage_runfiles.bzl b/scala/private/phases/phase_coverage_runfiles.bzl new file mode 100644 index 000000000..00e50cf44 --- /dev/null +++ b/scala/private/phases/phase_coverage_runfiles.bzl @@ -0,0 +1,28 @@ +# +# PHASE: coverage runfiles +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:coverage_replacements_provider.bzl", + _coverage_replacements_provider = "coverage_replacements_provider", +) + +def phase_coverage_runfiles(ctx, p): + coverage_runfiles = [] + rjars = p.compile.rjars + if ctx.configuration.coverage_enabled and _coverage_replacements_provider.is_enabled(ctx): + coverage_replacements = _coverage_replacements_provider.from_ctx( + ctx, + base = p.compile.coverage.replacements, + ).replacements + + rjars = depset([ + coverage_replacements[jar] if jar in coverage_replacements else jar + for jar in rjars.to_list() + ]) + coverage_runfiles = ctx.files._jacocorunner + ctx.files._lcov_merger + coverage_replacements.values() + return struct( + coverage_runfiles = coverage_runfiles, + rjars = rjars, + ) diff --git a/scala/private/phases/phase_declare_executable.bzl b/scala/private/phases/phase_declare_executable.bzl new file mode 100644 index 000000000..53d0cdd50 --- /dev/null +++ b/scala/private/phases/phase_declare_executable.bzl @@ -0,0 +1,12 @@ +# +# PHASE: declare executable +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "declare_executable", +) + +def phase_declare_executable(ctx, p): + return declare_executable(ctx) diff --git a/scala/private/phases/phase_final.bzl b/scala/private/phases/phase_final.bzl new file mode 100644 index 000000000..e0c2e16c4 --- /dev/null +++ b/scala/private/phases/phase_final.bzl @@ -0,0 +1,38 @@ +# +# PHASE: final +# +# DOCUMENT THIS +# +def phase_binary_final(ctx, p): + return struct( + executable = p.declare_executable, + coverage = p.compile.coverage, + files = depset([p.declare_executable, ctx.outputs.jar]), + instrumented_files = p.compile.coverage.instrumented_files, + providers = [p.compile.merged_provider, p.collect_jars.jars2labels] + p.compile.coverage.providers, + runfiles = p.runfiles.runfiles, + scala = p.scala_provider, + transitive_rjars = p.compile.rjars, #calling rules need this for the classpath in the launcher + ) + +def phase_library_final(ctx, p): + return struct( + files = depset([ctx.outputs.jar] + p.compile.full_jars), # Here is the default output + instrumented_files = p.compile.coverage.instrumented_files, + jars_to_labels = p.collect_jars.jars2labels, + providers = [p.compile.merged_provider, p.collect_jars.jars2labels] + p.compile.coverage.providers, + runfiles = p.runfiles.runfiles, + scala = p.scala_provider, + ) + +def phase_scalatest_final(ctx, p): + coverage_runfiles = p.coverage_runfiles.coverage_runfiles + coverage_runfiles.extend(p.write_executable) + return struct( + executable = p.declare_executable, + files = depset([p.declare_executable, ctx.outputs.jar]), + instrumented_files = p.compile.coverage.instrumented_files, + providers = [p.compile.merged_provider, p.collect_jars.jars2labels] + p.compile.coverage.providers, + runfiles = ctx.runfiles(coverage_runfiles, transitive_files = p.runfiles.runfiles.files), + scala = p.scala_provider, + ) diff --git a/scala/private/phases/phase_java_wrapper.bzl b/scala/private/phases/phase_java_wrapper.bzl new file mode 100644 index 000000000..eda478a1f --- /dev/null +++ b/scala/private/phases/phase_java_wrapper.bzl @@ -0,0 +1,49 @@ +# +# PHASE: java wrapper +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "write_java_wrapper", +) + +def phase_repl_java_wrapper(ctx, p): + args = struct( + args = " ".join(ctx.attr.scalacopts), + wrapper_preamble = """ +# save stty like in bin/scala +saved_stty=$(stty -g 2>/dev/null) +if [[ ! $? ]]; then + saved_stty="" +fi +function finish() { + if [[ "$saved_stty" != "" ]]; then + stty $saved_stty + saved_stty="" + fi +} +trap finish EXIT +""", + ) + return _phase_default_java_wrapper(ctx, p, args) + +def phase_common_java_wrapper(ctx, p): + return _phase_default_java_wrapper(ctx, p) + +def _phase_default_java_wrapper(ctx, p, _args = struct()): + return _phase_java_wrapper( + ctx, + _args.args if hasattr(_args, "args") else "", + _args.wrapper_preamble if hasattr(_args, "wrapper_preamble") else "", + ) + +def _phase_java_wrapper( + ctx, + args, + wrapper_preamble): + return write_java_wrapper( + ctx, + args, + wrapper_preamble, + ) diff --git a/scala/private/phases/phase_jvm_flags.bzl b/scala/private/phases/phase_jvm_flags.bzl new file mode 100644 index 000000000..9f17a95dd --- /dev/null +++ b/scala/private/phases/phase_jvm_flags.bzl @@ -0,0 +1,52 @@ +# +# PHASE: jvm flags +# +# DOCUMENT THIS +# +def phase_jvm_flags(ctx, p): + if ctx.attr.tests_from: + archives = _get_test_archive_jars(ctx, ctx.attr.tests_from) + else: + archives = [archive.class_jar for archive in p.scala_provider.outputs.jars] + + serialized_archives = _serialize_archives_short_path(archives) + test_suite = _gen_test_suite_flags_based_on_prefixes_and_suffixes( + ctx, + serialized_archives, + ) + return [ + "-ea", + test_suite.archiveFlag, + test_suite.prefixesFlag, + test_suite.suffixesFlag, + test_suite.printFlag, + test_suite.testSuiteFlag, + ] + +def _gen_test_suite_flags_based_on_prefixes_and_suffixes(ctx, archives): + return struct( + archiveFlag = "-Dbazel.discover.classes.archives.file.paths=%s" % + archives, + prefixesFlag = "-Dbazel.discover.classes.prefixes=%s" % ",".join( + ctx.attr.prefixes, + ), + printFlag = "-Dbazel.discover.classes.print.discovered=%s" % + ctx.attr.print_discovered_classes, + suffixesFlag = "-Dbazel.discover.classes.suffixes=%s" % ",".join( + ctx.attr.suffixes, + ), + testSuiteFlag = "-Dbazel.test_suite=%s" % ctx.attr.suite_class, + ) + +def _serialize_archives_short_path(archives): + archives_short_path = "" + for archive in archives: + archives_short_path += archive.short_path + "," + return archives_short_path[:-1] #remove redundant comma + +def _get_test_archive_jars(ctx, test_archives): + flattened_list = [] + for archive in test_archives: + class_jars = [java_output.class_jar for java_output in archive[JavaInfo].outputs.jars] + flattened_list.extend(class_jars) + return flattened_list diff --git a/scala/private/phases/phase_merge_jars.bzl b/scala/private/phases/phase_merge_jars.bzl new file mode 100644 index 000000000..bec2194f5 --- /dev/null +++ b/scala/private/phases/phase_merge_jars.bzl @@ -0,0 +1,19 @@ +# +# PHASE: merge jars +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "merge_jars", +) + +def phase_merge_jars(ctx, p): + merge_jars( + actions = ctx.actions, + deploy_jar = ctx.outputs.deploy_jar, + singlejar_executable = ctx.executable._singlejar, + jars_list = p.compile.rjars.to_list(), + main_class = getattr(ctx.attr, "main_class", ""), + progress_message = "Merging Scala jar: %s" % ctx.label, + ) diff --git a/scala/private/phases/phase_runfiles.bzl b/scala/private/phases/phase_runfiles.bzl new file mode 100644 index 000000000..db5e6e8a2 --- /dev/null +++ b/scala/private/phases/phase_runfiles.bzl @@ -0,0 +1,68 @@ +# +# PHASE: runfiles +# +# DOCUMENT THIS +# +def phase_library_runfiles(ctx, p): + args = struct( + # Using transitive_files since transitive_rjars a depset and avoiding linearization + transitive_files = p.compile.rjars, + ) + return _phase_default_runfiles(ctx, p, args) + +def phase_scalatest_runfiles(ctx, p): + args = "\n".join([ + "-R", + ctx.outputs.jar.short_path, + _scala_test_flags(ctx), + "-C", + "io.bazel.rules.scala.JUnitXmlReporter", + ]) + args_file = ctx.actions.declare_file("%s.args" % ctx.label.name) + ctx.actions.write(args_file, args) + runfiles_ext = [args_file] + + args = struct( + transitive_files = depset( + [p.declare_executable, p.java_wrapper] + ctx.files._java_runtime + runfiles_ext, + transitive = [p.compile.rjars], + ), + args_file = args_file, + ) + return _phase_default_runfiles(ctx, p, args) + +def phase_common_runfiles(ctx, p): + return _phase_default_runfiles(ctx, p) + +def _phase_default_runfiles(ctx, p, _args = struct()): + return _phase_runfiles( + ctx, + _args.transitive_files if hasattr(_args, "transitive_files") else depset( + [p.declare_executable, p.java_wrapper] + ctx.files._java_runtime, + transitive = [p.compile.rjars], + ), + _args.args_file if hasattr(_args, "args_file") else None, + ) + +def _phase_runfiles( + ctx, + transitive_files, + args_file): + return struct( + runfiles = ctx.runfiles( + transitive_files = transitive_files, + collect_data = True, + ), + args_file = args_file, + ) + +def _scala_test_flags(ctx): + # output report test duration + flags = "-oD" + if ctx.attr.full_stacktraces: + flags += "F" + else: + flags += "S" + if not ctx.attr.colors: + flags += "W" + return flags diff --git a/scala/private/phases/phase_scala_provider.bzl b/scala/private/phases/phase_scala_provider.bzl new file mode 100644 index 000000000..b50ec6a5d --- /dev/null +++ b/scala/private/phases/phase_scala_provider.bzl @@ -0,0 +1,51 @@ +# +# PHASE: scala provider +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala:providers.bzl", + "create_scala_provider", +) + +def phase_library_scala_provider(ctx, p): + args = struct( + rjars = depset( + transitive = [p.compile.rjars, p.collect_exports_jars.transitive_runtime_jars], + ), + compile_jars = depset( + p.compile.ijars, + transitive = [p.collect_exports_jars.compile_jars], + ), + ijar = p.compile.ijar, + ) + return _phase_default_scala_provider(ctx, p, args) + +def phase_common_scala_provider(ctx, p): + return _phase_default_scala_provider(ctx, p) + +def _phase_default_scala_provider(ctx, p, _args = struct()): + return _phase_scala_provider( + ctx, + p, + _args.rjars if hasattr(_args, "rjars") else p.compile.rjars, + _args.compile_jars if hasattr(_args, "compile_jars") else depset(p.compile.ijars), + _args.ijar if hasattr(_args, "ijar") else p.compile.class_jar, # we aren't using ijar here + ) + +def _phase_scala_provider( + ctx, + p, + rjars, + compile_jars, + ijar): + return create_scala_provider( + class_jar = p.compile.class_jar, + compile_jars = compile_jars, + deploy_jar = ctx.outputs.deploy_jar, + full_jars = p.compile.full_jars, + ijar = ijar, + source_jars = p.compile.source_jars, + statsfile = ctx.outputs.statsfile, + transitive_runtime_jars = rjars, + ) diff --git a/scala/private/phases/phase_scalac_provider.bzl b/scala/private/phases/phase_scalac_provider.bzl new file mode 100644 index 000000000..86e453618 --- /dev/null +++ b/scala/private/phases/phase_scalac_provider.bzl @@ -0,0 +1,12 @@ +# +# PHASE: scalac provider +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "get_scalac_provider", +) + +def phase_scalac_provider(ctx, p): + return get_scalac_provider(ctx) diff --git a/scala/private/phases/phase_unused_deps_checker.bzl b/scala/private/phases/phase_unused_deps_checker.bzl new file mode 100644 index 000000000..4757118cf --- /dev/null +++ b/scala/private/phases/phase_unused_deps_checker.bzl @@ -0,0 +1,12 @@ +# +# PHASE: unused deps checker +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "get_unused_dependency_checker_mode", +) + +def phase_unused_deps_checker(ctx, p): + return get_unused_dependency_checker_mode(ctx) diff --git a/scala/private/phases/phase_write_executable.bzl b/scala/private/phases/phase_write_executable.bzl new file mode 100644 index 000000000..a678c1ea2 --- /dev/null +++ b/scala/private/phases/phase_write_executable.bzl @@ -0,0 +1,75 @@ +# +# PHASE: write executable +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:rule_impls.bzl", + "expand_location", + "first_non_empty", + "write_executable", +) + +def phase_scalatest_write_executable(ctx, p): + # jvm_flags passed in on the target override scala_test_jvm_flags passed in on the + # toolchain + final_jvm_flags = first_non_empty( + ctx.attr.jvm_flags, + ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].scala_test_jvm_flags, + ) + args = struct( + rjars = p.coverage_runfiles.rjars, + jvm_flags = [ + "-DRULES_SCALA_MAIN_WS_NAME=%s" % ctx.workspace_name, + "-DRULES_SCALA_ARGS_FILE=%s" % p.runfiles.args_file.short_path, + ] + expand_location(ctx, final_jvm_flags), + use_jacoco = ctx.configuration.coverage_enabled, + ) + return _phase_deafult_write_executable(ctx, p, args) + +def phase_repl_write_executable(ctx, p): + args = struct( + jvm_flags = ["-Dscala.usejavacp=true"] + ctx.attr.jvm_flags, + main_class = "scala.tools.nsc.MainGenericRunner", + ) + return _phase_deafult_write_executable(ctx, p, args) + +def phase_junit_test_write_executable(ctx, p): + args = struct( + jvm_flags = p.jvm_flags + ctx.attr.jvm_flags, + main_class = "com.google.testing.junit.runner.BazelTestRunner", + ) + return _phase_deafult_write_executable(ctx, p, args) + +def phase_common_write_executable(ctx, p): + return _phase_deafult_write_executable(ctx, p) + +def _phase_deafult_write_executable(ctx, p, _args = struct()): + return _phase_write_executable( + ctx, + p, + _args.rjars if hasattr(_args, "rjars") else p.compile.rjars, + _args.jvm_flags if hasattr(_args, "jvm_flags") else ctx.attr.jvm_flags, + _args.use_jacoco if hasattr(_args, "use_jacoco") else False, + _args.main_class if hasattr(_args, "main_class") else ctx.attr.main_class, + ) + +def _phase_write_executable( + ctx, + p, + rjars, + jvm_flags, + use_jacoco, + main_class): + executable = p.declare_executable + wrapper = p.java_wrapper + + return write_executable( + ctx, + executable, + rjars, + main_class, + jvm_flags, + wrapper, + use_jacoco, + ) diff --git a/scala/private/phases/phase_write_manifest.bzl b/scala/private/phases/phase_write_manifest.bzl new file mode 100644 index 000000000..13a9df179 --- /dev/null +++ b/scala/private/phases/phase_write_manifest.bzl @@ -0,0 +1,12 @@ +# +# PHASE: write manifest +# +# DOCUMENT THIS +# +load( + "@io_bazel_rules_scala//scala/private:common.bzl", + "write_manifest", +) + +def phase_write_manifest(ctx, p): + write_manifest(ctx) diff --git a/scala/private/phases/phases.bzl b/scala/private/phases/phases.bzl new file mode 100644 index 000000000..c1b4e4b9a --- /dev/null +++ b/scala/private/phases/phases.bzl @@ -0,0 +1,140 @@ +""" +Re-expose all the phase APIs and built-in phases +""" + +load( + "@io_bazel_rules_scala//scala/private:phases/api.bzl", + _extras_phases = "extras_phases", + _run_phases = "run_phases", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_write_executable.bzl", + _phase_common_write_executable = "phase_common_write_executable", + _phase_junit_test_write_executable = "phase_junit_test_write_executable", + _phase_repl_write_executable = "phase_repl_write_executable", + _phase_scalatest_write_executable = "phase_scalatest_write_executable", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_java_wrapper.bzl", + _phase_common_java_wrapper = "phase_common_java_wrapper", + _phase_repl_java_wrapper = "phase_repl_java_wrapper", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_collect_jars.bzl", + _phase_common_collect_jars = "phase_common_collect_jars", + _phase_junit_test_collect_jars = "phase_junit_test_collect_jars", + _phase_library_for_plugin_bootstrapping_collect_jars = "phase_library_for_plugin_bootstrapping_collect_jars", + _phase_macro_library_collect_jars = "phase_macro_library_collect_jars", + _phase_repl_collect_jars = "phase_repl_collect_jars", + _phase_scalatest_collect_jars = "phase_scalatest_collect_jars", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_compile.bzl", + _phase_binary_compile = "phase_binary_compile", + _phase_common_compile = "phase_common_compile", + _phase_junit_test_compile = "phase_junit_test_compile", + _phase_library_compile = "phase_library_compile", + _phase_library_for_plugin_bootstrapping_compile = "phase_library_for_plugin_bootstrapping_compile", + _phase_macro_library_compile = "phase_macro_library_compile", + _phase_repl_compile = "phase_repl_compile", + _phase_scalatest_compile = "phase_scalatest_compile", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_scala_provider.bzl", + _phase_common_scala_provider = "phase_common_scala_provider", + _phase_library_scala_provider = "phase_library_scala_provider", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_runfiles.bzl", + _phase_common_runfiles = "phase_common_runfiles", + _phase_library_runfiles = "phase_library_runfiles", + _phase_scalatest_runfiles = "phase_scalatest_runfiles", +) +load( + "@io_bazel_rules_scala//scala/private:phases/phase_final.bzl", + _phase_binary_final = "phase_binary_final", + _phase_library_final = "phase_library_final", + _phase_scalatest_final = "phase_scalatest_final", +) +load("@io_bazel_rules_scala//scala/private:phases/phase_scalac_provider.bzl", _phase_scalac_provider = "phase_scalac_provider") +load("@io_bazel_rules_scala//scala/private:phases/phase_write_manifest.bzl", _phase_write_manifest = "phase_write_manifest") +load("@io_bazel_rules_scala//scala/private:phases/phase_collect_srcjars.bzl", _phase_collect_srcjars = "phase_collect_srcjars") +load("@io_bazel_rules_scala//scala/private:phases/phase_collect_exports_jars.bzl", _phase_collect_exports_jars = "phase_collect_exports_jars") +load("@io_bazel_rules_scala//scala/private:phases/phase_unused_deps_checker.bzl", _phase_unused_deps_checker = "phase_unused_deps_checker") +load("@io_bazel_rules_scala//scala/private:phases/phase_declare_executable.bzl", _phase_declare_executable = "phase_declare_executable") +load("@io_bazel_rules_scala//scala/private:phases/phase_merge_jars.bzl", _phase_merge_jars = "phase_merge_jars") +load("@io_bazel_rules_scala//scala/private:phases/phase_jvm_flags.bzl", _phase_jvm_flags = "phase_jvm_flags") +load("@io_bazel_rules_scala//scala/private:phases/phase_coverage_runfiles.bzl", _phase_coverage_runfiles = "phase_coverage_runfiles") + +# API +run_phases = _run_phases +extras_phases = _extras_phases + +# scalac_provider +phase_scalac_provider = _phase_scalac_provider + +# collect_srcjars +phase_collect_srcjars = _phase_collect_srcjars + +# collect_exports_jars +phase_collect_exports_jars = _phase_collect_exports_jars + +# write_manifest +phase_write_manifest = _phase_write_manifest + +# unused_deps_checker +phase_unused_deps_checker = _phase_unused_deps_checker + +# declare_executable +phase_declare_executable = _phase_declare_executable + +# merge_jars +phase_merge_jars = _phase_merge_jars + +# jvm_flags +phase_jvm_flags = _phase_jvm_flags + +# coverage_runfiles +phase_coverage_runfiles = _phase_coverage_runfiles + +# write_executable +phase_scalatest_write_executable = _phase_scalatest_write_executable +phase_repl_write_executable = _phase_repl_write_executable +phase_junit_test_write_executable = _phase_junit_test_write_executable +phase_common_write_executable = _phase_common_write_executable + +# java_wrapper +phase_repl_java_wrapper = _phase_repl_java_wrapper +phase_common_java_wrapper = _phase_common_java_wrapper + +# collect_jars +phase_scalatest_collect_jars = _phase_scalatest_collect_jars +phase_repl_collect_jars = _phase_repl_collect_jars +phase_macro_library_collect_jars = _phase_macro_library_collect_jars +phase_junit_test_collect_jars = _phase_junit_test_collect_jars +phase_library_for_plugin_bootstrapping_collect_jars = _phase_library_for_plugin_bootstrapping_collect_jars +phase_common_collect_jars = _phase_common_collect_jars + +# compile +phase_binary_compile = _phase_binary_compile +phase_library_compile = _phase_library_compile +phase_library_for_plugin_bootstrapping_compile = _phase_library_for_plugin_bootstrapping_compile +phase_macro_library_compile = _phase_macro_library_compile +phase_junit_test_compile = _phase_junit_test_compile +phase_repl_compile = _phase_repl_compile +phase_scalatest_compile = _phase_scalatest_compile +phase_common_compile = _phase_common_compile + +# scala_provider +phase_library_scala_provider = _phase_library_scala_provider +phase_common_scala_provider = _phase_common_scala_provider + +# runfiles +phase_library_runfiles = _phase_library_runfiles +phase_scalatest_runfiles = _phase_scalatest_runfiles +phase_common_runfiles = _phase_common_runfiles + +# final +phase_binary_final = _phase_binary_final +phase_library_final = _phase_library_final +phase_scalatest_final = _phase_scalatest_final diff --git a/scala/private/rule_impls.bzl b/scala/private/rule_impls.bzl index f57f905e3..925179430 100644 --- a/scala/private/rule_impls.bzl +++ b/scala/private/rule_impls.bzl @@ -811,79 +811,6 @@ def get_unused_dependency_checker_mode(ctx): else: return ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].unused_dependency_checker_mode -# Common code shared by all scala binary implementations. -def scala_binary_common( - ctx, - executable, - cjars, - rjars, - transitive_compile_time_jars, - jars2labels, - java_wrapper, - unused_dependency_checker_mode, - unused_dependency_checker_ignored_targets, - deps_providers, - implicit_junit_deps_needed_for_java_compilation = [], - runfiles_ext = []): - write_manifest(ctx) - outputs = compile_or_empty( - ctx, - ctx.outputs.manifest, - cjars, - depset(), - False, - transitive_compile_time_jars, - jars2labels.jars_to_labels, - implicit_junit_deps_needed_for_java_compilation, - unused_dependency_checker_ignored_targets = - unused_dependency_checker_ignored_targets, - unused_dependency_checker_mode = unused_dependency_checker_mode, - deps_providers = deps_providers, - ) # no need to build an ijar for an executable - rjars = depset(outputs.full_jars, transitive = [rjars]) - - merge_jars( - actions = ctx.actions, - deploy_jar = ctx.outputs.deploy_jar, - singlejar_executable = ctx.executable._singlejar, - jars_list = rjars.to_list(), - main_class = getattr(ctx.attr, "main_class", ""), - progress_message = "Merging Scala binary jar: %s" % ctx.label, - ) - - runfiles = ctx.runfiles( - transitive_files = depset( - [executable, java_wrapper] + ctx.files._java_runtime + runfiles_ext, - transitive = [rjars], - ), - collect_data = True, - ) - - source_jars = pack_source_jars(ctx) + outputs.source_jars - - scalaattr = create_scala_provider( - class_jar = outputs.class_jar, - compile_jars = depset(outputs.ijars), - deploy_jar = ctx.outputs.deploy_jar, - full_jars = outputs.full_jars, - ijar = outputs.class_jar, # we aren't using ijar here - source_jars = source_jars, - statsfile = ctx.outputs.statsfile, - transitive_runtime_jars = rjars, - ) - - return struct( - executable = executable, - coverage = outputs.coverage, - files = depset([executable, ctx.outputs.jar]), - instrumented_files = outputs.coverage.instrumented_files, - providers = [outputs.merged_provider, jars2labels] + outputs.coverage.providers, - runfiles = runfiles, - scala = scalaattr, - transitive_rjars = - rjars, #calling rules need this for the classpath in the launcher - ) - def _pack_source_jar(ctx): # collect .scala sources and pack a source jar for Scala scala_sources = [ diff --git a/scala/private/rules/scala_binary.bzl b/scala/private/rules/scala_binary.bzl index df4dcbea5..1c62193f8 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", @@ -9,58 +10,44 @@ load( ) load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( - "@io_bazel_rules_scala//scala/private:rule_impls.bzl", - "collect_jars_from_common_ctx", - "declare_executable", - "get_scalac_provider", - "get_unused_dependency_checker_mode", - "scala_binary_common", - "write_executable", - "write_java_wrapper", + "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", + "phase_binary_compile", + "phase_binary_final", + "phase_common_collect_jars", + "phase_common_java_wrapper", + "phase_common_runfiles", + "phase_common_scala_provider", + "phase_common_write_executable", + "phase_declare_executable", + "phase_merge_jars", + "phase_scalac_provider", + "phase_unused_deps_checker", + "phase_write_manifest", + "run_phases", ) def _scala_binary_impl(ctx): - scalac_provider = get_scalac_provider(ctx) - unused_dependency_checker_mode = get_unused_dependency_checker_mode(ctx) - unused_dependency_checker_is_off = unused_dependency_checker_mode == "off" - - jars = collect_jars_from_common_ctx( - ctx, - scalac_provider.default_classpath, - unused_dependency_checker_is_off = unused_dependency_checker_is_off, - ) - (cjars, transitive_rjars) = (jars.compile_jars, jars.transitive_runtime_jars) - - wrapper = write_java_wrapper(ctx, "", "") - - executable = declare_executable(ctx) - - out = scala_binary_common( + return run_phases( ctx, - executable, - cjars, - transitive_rjars, - jars.transitive_compile_jars, - jars.jars2labels, - wrapper, - unused_dependency_checker_ignored_targets = [ - target.label - for target in scalac_provider.default_classpath + - ctx.attr.unused_dependency_checker_ignored_targets + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + ("collect_jars", phase_common_collect_jars), + ("java_wrapper", phase_common_java_wrapper), + ("declare_executable", phase_declare_executable), + # no need to build an ijar for an executable + ("compile", phase_binary_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_common_runfiles), + ("scala_provider", phase_common_scala_provider), + ("write_executable", phase_common_write_executable), ], - unused_dependency_checker_mode = unused_dependency_checker_mode, - deps_providers = jars.deps_providers, - ) - write_executable( - ctx = ctx, - executable = executable, - jvm_flags = ctx.attr.jvm_flags, - main_class = ctx.attr.main_class, - rjars = out.transitive_rjars, - use_jacoco = False, - wrapper = wrapper, - ) - return out + # fixed phase + ("final", phase_binary_final), + ).final _scala_binary_attrs = { "main_class": attr.string(mandatory = True), @@ -76,11 +63,21 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + executable = True, + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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 a36b8082c..6f3bd255a 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", @@ -8,129 +9,50 @@ load( ) load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( - "@io_bazel_rules_scala//scala/private:rule_impls.bzl", - "collect_jars_from_common_ctx", - "declare_executable", - "get_scalac_provider", - "get_unused_dependency_checker_mode", - "scala_binary_common", - "write_executable", - "write_java_wrapper", + "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", + "phase_binary_final", + "phase_common_java_wrapper", + "phase_common_runfiles", + "phase_common_scala_provider", + "phase_declare_executable", + "phase_junit_test_collect_jars", + "phase_junit_test_compile", + "phase_junit_test_write_executable", + "phase_jvm_flags", + "phase_merge_jars", + "phase_scalac_provider", + "phase_unused_deps_checker", + "phase_write_manifest", + "run_phases", ) -def _gen_test_suite_flags_based_on_prefixes_and_suffixes(ctx, archives): - return struct( - archiveFlag = "-Dbazel.discover.classes.archives.file.paths=%s" % - archives, - prefixesFlag = "-Dbazel.discover.classes.prefixes=%s" % ",".join( - ctx.attr.prefixes, - ), - printFlag = "-Dbazel.discover.classes.print.discovered=%s" % - ctx.attr.print_discovered_classes, - suffixesFlag = "-Dbazel.discover.classes.suffixes=%s" % ",".join( - ctx.attr.suffixes, - ), - testSuiteFlag = "-Dbazel.test_suite=%s" % ctx.attr.suite_class, - ) - -def _serialize_archives_short_path(archives): - archives_short_path = "" - for archive in archives: - archives_short_path += archive.short_path + "," - return archives_short_path[:-1] #remove redundant comma - -def _get_test_archive_jars(ctx, test_archives): - flattened_list = [] - for archive in test_archives: - class_jars = [java_output.class_jar for java_output in archive[JavaInfo].outputs.jars] - flattened_list.extend(class_jars) - return flattened_list - def _scala_junit_test_impl(ctx): if (not (ctx.attr.prefixes) and not (ctx.attr.suffixes)): fail( "Setting at least one of the attributes ('prefixes','suffixes') is required", ) - scalac_provider = get_scalac_provider(ctx) - - unused_dependency_checker_mode = get_unused_dependency_checker_mode(ctx) - unused_dependency_checker_ignored_targets = [ - target.label - for target in scalac_provider.default_classpath + - ctx.attr.unused_dependency_checker_ignored_targets - ] + [ - ctx.attr._junit.label, - ctx.attr._hamcrest.label, - ctx.attr.suite_label.label, - ctx.attr._bazel_test_runner.label, - ] - unused_dependency_checker_is_off = unused_dependency_checker_mode == "off" - - jars = collect_jars_from_common_ctx( + return run_phases( ctx, - scalac_provider.default_classpath, - extra_deps = [ - ctx.attr._junit, - ctx.attr._hamcrest, - ctx.attr.suite_label, - ctx.attr._bazel_test_runner, + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + ("collect_jars", phase_junit_test_collect_jars), + ("java_wrapper", phase_common_java_wrapper), + ("declare_executable", phase_declare_executable), + # no need to build an ijar for an executable + ("compile", phase_junit_test_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_common_runfiles), + ("scala_provider", phase_common_scala_provider), + ("jvm_flags", phase_jvm_flags), + ("write_executable", phase_junit_test_write_executable), ], - unused_dependency_checker_is_off = unused_dependency_checker_is_off, - ) - (cjars, transitive_rjars) = (jars.compile_jars, jars.transitive_runtime_jars) - implicit_junit_deps_needed_for_java_compilation = [ - ctx.attr._junit, - ctx.attr._hamcrest, - ] - - executable = declare_executable(ctx) - - wrapper = write_java_wrapper(ctx, "", "") - out = scala_binary_common( - ctx, - executable, - cjars, - transitive_rjars, - jars.transitive_compile_jars, - jars.jars2labels, - wrapper, - implicit_junit_deps_needed_for_java_compilation = - implicit_junit_deps_needed_for_java_compilation, - unused_dependency_checker_ignored_targets = - unused_dependency_checker_ignored_targets, - unused_dependency_checker_mode = unused_dependency_checker_mode, - deps_providers = jars.deps_providers, - ) - - if ctx.attr.tests_from: - archives = _get_test_archive_jars(ctx, ctx.attr.tests_from) - else: - archives = [archive.class_jar for archive in out.scala.outputs.jars] - - serialized_archives = _serialize_archives_short_path(archives) - test_suite = _gen_test_suite_flags_based_on_prefixes_and_suffixes( - ctx, - serialized_archives, - ) - launcherJvmFlags = [ - "-ea", - test_suite.archiveFlag, - test_suite.prefixesFlag, - test_suite.suffixesFlag, - test_suite.printFlag, - test_suite.testSuiteFlag, - ] - write_executable( - ctx = ctx, - executable = executable, - jvm_flags = launcherJvmFlags + ctx.attr.jvm_flags, - main_class = "com.google.testing.junit.runner.BazelTestRunner", - rjars = out.transitive_rjars, - use_jacoco = False, - wrapper = wrapper, - ) - - return out + # fixed phase + ("final", phase_binary_final), + ).final _scala_junit_test_attrs = { "prefixes": attr.string_list(default = []), @@ -193,11 +115,21 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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 5978cb333..86273f37c 100644 --- a/scala/private/rules/scala_library.bzl +++ b/scala/private/rules/scala_library.bzl @@ -1,10 +1,7 @@ -load("@io_bazel_rules_scala//scala:providers.bzl", "create_scala_provider") +load("@bazel_skylib//lib:dicts.bzl", _dicts = "dicts") load( "@io_bazel_rules_scala//scala/private:common.bzl", - "collect_jars", - "collect_srcjars", "sanitize_string_for_usage", - "write_manifest", ) load( "@io_bazel_rules_scala//scala/private:common_attributes.bzl", @@ -19,13 +16,24 @@ load( _coverage_replacements_provider = "coverage_replacements_provider", ) load( - "@io_bazel_rules_scala//scala/private:rule_impls.bzl", - "collect_jars_from_common_ctx", - "compile_or_empty", - "get_scalac_provider", - "get_unused_dependency_checker_mode", - "merge_jars", - "pack_source_jars", + "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", + "phase_collect_exports_jars", + "phase_collect_srcjars", + "phase_common_collect_jars", + "phase_library_compile", + "phase_library_final", + "phase_library_for_plugin_bootstrapping_collect_jars", + "phase_library_for_plugin_bootstrapping_compile", + "phase_library_runfiles", + "phase_library_scala_provider", + "phase_macro_library_collect_jars", + "phase_macro_library_compile", + "phase_merge_jars", + "phase_scalac_provider", + "phase_unused_deps_checker", + "phase_write_manifest", + "run_phases", ) ## @@ -40,110 +48,30 @@ _library_attrs = { ), } -def _lib( - ctx, - base_classpath, - non_macro_lib, - unused_dependency_checker_mode, - unused_dependency_checker_ignored_targets): - # Build up information from dependency-like attributes - - # This will be used to pick up srcjars from non-scala library - # targets (like thrift code generation) - srcjars = collect_srcjars(ctx.attr.deps) - - unused_dependency_checker_is_off = unused_dependency_checker_mode == "off" - jars = collect_jars_from_common_ctx( - ctx, - base_classpath, - unused_dependency_checker_is_off = unused_dependency_checker_is_off, - ) - - (cjars, transitive_rjars) = (jars.compile_jars, jars.transitive_runtime_jars) - - write_manifest(ctx) - outputs = compile_or_empty( - ctx, - ctx.outputs.manifest, - cjars, - srcjars, - non_macro_lib, - jars.transitive_compile_jars, - jars.jars2labels.jars_to_labels, - [], - unused_dependency_checker_ignored_targets = [ - target.label - for target in base_classpath + ctx.attr.exports + - unused_dependency_checker_ignored_targets - ], - unused_dependency_checker_mode = unused_dependency_checker_mode, - deps_providers = jars.deps_providers, - ) - - transitive_rjars = depset(outputs.full_jars, transitive = [transitive_rjars]) - - merge_jars( - actions = ctx.actions, - deploy_jar = ctx.outputs.deploy_jar, - singlejar_executable = ctx.executable._singlejar, - jars_list = transitive_rjars.to_list(), - main_class = getattr(ctx.attr, "main_class", ""), - progress_message = "Merging Scala library jar: %s" % ctx.label, - ) - - # Using transitive_files since transitive_rjars a depset and avoiding linearization - runfiles = ctx.runfiles( - transitive_files = transitive_rjars, - collect_data = True, - ) - - # Add information from exports (is key that AFTER all build actions/runfiles analysis) - # Since after, will not show up in deploy_jar or old jars runfiles - # Notice that compile_jars is intentionally transitive for exports - exports_jars = collect_jars(ctx.attr.exports) - transitive_rjars = depset( - transitive = [transitive_rjars, exports_jars.transitive_runtime_jars], - ) - - source_jars = pack_source_jars(ctx) + outputs.source_jars - - scalaattr = create_scala_provider( - class_jar = outputs.class_jar, - compile_jars = depset( - outputs.ijars, - transitive = [exports_jars.compile_jars], - ), - deploy_jar = ctx.outputs.deploy_jar, - full_jars = outputs.full_jars, - ijar = outputs.ijar, - source_jars = source_jars, - statsfile = ctx.outputs.statsfile, - transitive_runtime_jars = transitive_rjars, - ) - - return struct( - files = depset([ctx.outputs.jar] + outputs.full_jars), # Here is the default output - instrumented_files = outputs.coverage.instrumented_files, - jars_to_labels = jars.jars2labels, - providers = [outputs.merged_provider, jars.jars2labels] + outputs.coverage.providers, - runfiles = runfiles, - scala = scalaattr, - ) - ## # scala_library ## def _scala_library_impl(ctx): - scalac_provider = get_scalac_provider(ctx) - unused_dependency_checker_mode = get_unused_dependency_checker_mode(ctx) - return _lib( + # Build up information from dependency-like attributes + return run_phases( ctx, - scalac_provider.default_classpath, - True, - unused_dependency_checker_mode, - ctx.attr.unused_dependency_checker_ignored_targets, - ) + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("collect_srcjars", phase_collect_srcjars), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + ("collect_jars", phase_common_collect_jars), + ("compile", phase_library_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_library_runfiles), + ("collect_exports_jars", phase_collect_exports_jars), + ("scala_provider", phase_library_scala_provider), + ], + # fixed phase + ("final", phase_library_final), + ).final _scala_library_attrs = {} @@ -155,13 +83,23 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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 @@ -195,14 +133,23 @@ def scala_library_suite( ## def _scala_library_for_plugin_bootstrapping_impl(ctx): - scalac_provider = get_scalac_provider(ctx) - return _lib( + return run_phases( ctx, - scalac_provider.default_classpath, - True, - unused_dependency_checker_ignored_targets = [], - unused_dependency_checker_mode = "off", - ) + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("collect_srcjars", phase_collect_srcjars), + ("write_manifest", phase_write_manifest), + ("collect_jars", phase_library_for_plugin_bootstrapping_collect_jars), + ("compile", phase_library_for_plugin_bootstrapping_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_library_runfiles), + ("collect_exports_jars", phase_collect_exports_jars), + ("scala_provider", phase_library_scala_provider), + ], + # fixed phase + ("final", phase_library_final), + ).final # the scala compiler plugin used for dependency analysis is compiled using `scala_library`. # in order to avoid cyclic dependencies `scala_library_for_plugin_bootstrapping` was created for this purpose, @@ -219,28 +166,47 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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 ## def _scala_macro_library_impl(ctx): - scalac_provider = get_scalac_provider(ctx) - unused_dependency_checker_mode = get_unused_dependency_checker_mode(ctx) - return _lib( + return run_phases( ctx, - scalac_provider.default_macro_classpath, - False, # don't build the ijar for macros - unused_dependency_checker_mode, - ctx.attr.unused_dependency_checker_ignored_targets, - ) + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("collect_srcjars", phase_collect_srcjars), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + ("collect_jars", phase_macro_library_collect_jars), + ("compile", phase_macro_library_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_library_runfiles), + ("collect_exports_jars", phase_collect_exports_jars), + ("scala_provider", phase_library_scala_provider), + ], + # fixed phase + ("final", phase_library_final), + ).final _scala_macro_library_attrs = { "main_class": attr.string(), @@ -267,10 +233,20 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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 78630ff3b..8f3fd9625 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", @@ -9,80 +10,45 @@ load( ) load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( - "@io_bazel_rules_scala//scala/private:rule_impls.bzl", - "collect_jars_from_common_ctx", - "declare_executable", - "get_scalac_provider", - "get_unused_dependency_checker_mode", - "scala_binary_common", - "write_executable", - "write_java_wrapper", + "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", + "phase_binary_final", + "phase_common_runfiles", + "phase_common_scala_provider", + "phase_declare_executable", + "phase_merge_jars", + "phase_repl_collect_jars", + "phase_repl_compile", + "phase_repl_java_wrapper", + "phase_repl_write_executable", + "phase_scalac_provider", + "phase_unused_deps_checker", + "phase_write_manifest", + "run_phases", ) def _scala_repl_impl(ctx): - scalac_provider = get_scalac_provider(ctx) - - unused_dependency_checker_mode = get_unused_dependency_checker_mode(ctx) - unused_dependency_checker_is_off = unused_dependency_checker_mode == "off" - - # need scala-compiler for MainGenericRunner below - jars = collect_jars_from_common_ctx( + return run_phases( ctx, - scalac_provider.default_repl_classpath, - unused_dependency_checker_is_off = unused_dependency_checker_is_off, - ) - (cjars, transitive_rjars) = (jars.compile_jars, jars.transitive_runtime_jars) - - args = " ".join(ctx.attr.scalacopts) - - executable = declare_executable(ctx) - - wrapper = write_java_wrapper( - ctx, - args, - wrapper_preamble = """ -# save stty like in bin/scala -saved_stty=$(stty -g 2>/dev/null) -if [[ ! $? ]]; then - saved_stty="" -fi -function finish() { - if [[ "$saved_stty" != "" ]]; then - stty $saved_stty - saved_stty="" - fi -} -trap finish EXIT -""", - ) - - out = scala_binary_common( - ctx, - executable, - cjars, - transitive_rjars, - jars.transitive_compile_jars, - jars.jars2labels, - wrapper, - unused_dependency_checker_ignored_targets = [ - target.label - for target in scalac_provider.default_repl_classpath + - ctx.attr.unused_dependency_checker_ignored_targets + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + # need scala-compiler for MainGenericRunner below + ("collect_jars", phase_repl_collect_jars), + ("java_wrapper", phase_repl_java_wrapper), + ("declare_executable", phase_declare_executable), + # no need to build an ijar for an executable + ("compile", phase_repl_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_common_runfiles), + ("scala_provider", phase_common_scala_provider), + ("write_executable", phase_repl_write_executable), ], - unused_dependency_checker_mode = unused_dependency_checker_mode, - deps_providers = jars.deps_providers, - ) - write_executable( - ctx = ctx, - executable = executable, - jvm_flags = ["-Dscala.usejavacp=true"] + ctx.attr.jvm_flags, - main_class = "scala.tools.nsc.MainGenericRunner", - rjars = out.transitive_rjars, - use_jacoco = False, - wrapper = wrapper, - ) - - return out + # fixed phase + ("final", phase_binary_final), + ).final _scala_repl_attrs = { "jvm_flags": attr.string_list(), @@ -96,11 +62,21 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + executable = True, + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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 7d86b2e75..900bd2faa 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", @@ -9,138 +10,46 @@ load( load("@io_bazel_rules_scala//scala/private:common.bzl", "sanitize_string_for_usage") load("@io_bazel_rules_scala//scala/private:common_outputs.bzl", "common_outputs") load( - "@io_bazel_rules_scala//scala/private:coverage_replacements_provider.bzl", - _coverage_replacements_provider = "coverage_replacements_provider", -) -load( - "@io_bazel_rules_scala//scala/private:rule_impls.bzl", - "collect_jars_from_common_ctx", - "declare_executable", - "expand_location", - "first_non_empty", - "get_scalac_provider", - "get_unused_dependency_checker_mode", - "scala_binary_common", - "write_executable", - "write_java_wrapper", + "@io_bazel_rules_scala//scala/private:phases/phases.bzl", + "extras_phases", + "phase_common_java_wrapper", + "phase_common_scala_provider", + "phase_coverage_runfiles", + "phase_declare_executable", + "phase_merge_jars", + "phase_scalac_provider", + "phase_scalatest_collect_jars", + "phase_scalatest_compile", + "phase_scalatest_final", + "phase_scalatest_runfiles", + "phase_scalatest_write_executable", + "phase_unused_deps_checker", + "phase_write_manifest", + "run_phases", ) -def _scala_test_flags(ctx): - # output report test duration - flags = "-oD" - if ctx.attr.full_stacktraces: - flags += "F" - else: - flags += "S" - if not ctx.attr.colors: - flags += "W" - return flags - def _scala_test_impl(ctx): - scalac_provider = get_scalac_provider(ctx) - - unused_dependency_checker_mode = get_unused_dependency_checker_mode(ctx) - unused_dependency_checker_ignored_targets = [ - target.label - for target in scalac_provider.default_classpath + - ctx.attr.unused_dependency_checker_ignored_targets - ] - unused_dependency_checker_is_off = unused_dependency_checker_mode == "off" - - scalatest_base_classpath = scalac_provider.default_classpath + [ctx.attr._scalatest] - jars = collect_jars_from_common_ctx( + return run_phases( ctx, - scalatest_base_classpath, - extra_runtime_deps = [ - ctx.attr._scalatest_reporter, - ctx.attr._scalatest_runner, + # customizable phases + [ + ("scalac_provider", phase_scalac_provider), + ("write_manifest", phase_write_manifest), + ("unused_deps_checker", phase_unused_deps_checker), + ("collect_jars", phase_scalatest_collect_jars), + ("java_wrapper", phase_common_java_wrapper), + ("declare_executable", phase_declare_executable), + # no need to build an ijar for an executable + ("compile", phase_scalatest_compile), + ("merge_jars", phase_merge_jars), + ("runfiles", phase_scalatest_runfiles), + ("scala_provider", phase_common_scala_provider), + ("coverage_runfiles", phase_coverage_runfiles), + ("write_executable", phase_scalatest_write_executable), ], - unused_dependency_checker_is_off = unused_dependency_checker_is_off, - ) - ( - cjars, - transitive_rjars, - transitive_compile_jars, - jars_to_labels, - ) = ( - jars.compile_jars, - jars.transitive_runtime_jars, - jars.transitive_compile_jars, - jars.jars2labels, - ) - - args = "\n".join([ - "-R", - ctx.outputs.jar.short_path, - _scala_test_flags(ctx), - "-C", - "io.bazel.rules.scala.JUnitXmlReporter", - ]) - - argsFile = ctx.actions.declare_file("%s.args" % ctx.label.name) - ctx.actions.write(argsFile, args) - - executable = declare_executable(ctx) - - wrapper = write_java_wrapper(ctx, "", "") - out = scala_binary_common( - ctx, - executable, - cjars, - transitive_rjars, - transitive_compile_jars, - jars_to_labels, - wrapper, - unused_dependency_checker_ignored_targets = - unused_dependency_checker_ignored_targets, - unused_dependency_checker_mode = unused_dependency_checker_mode, - runfiles_ext = [argsFile], - deps_providers = jars.deps_providers, - ) - - rjars = out.transitive_rjars - - coverage_runfiles = [] - if ctx.configuration.coverage_enabled and _coverage_replacements_provider.is_enabled(ctx): - coverage_replacements = _coverage_replacements_provider.from_ctx( - ctx, - base = out.coverage.replacements, - ).replacements - - rjars = depset([ - coverage_replacements[jar] if jar in coverage_replacements else jar - for jar in rjars.to_list() - ]) - coverage_runfiles = ctx.files._jacocorunner + ctx.files._lcov_merger + coverage_replacements.values() - - # jvm_flags passed in on the target override scala_test_jvm_flags passed in on the - # toolchain - final_jvm_flags = first_non_empty( - ctx.attr.jvm_flags, - ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].scala_test_jvm_flags, - ) - - coverage_runfiles.extend(write_executable( - ctx = ctx, - executable = executable, - jvm_flags = [ - "-DRULES_SCALA_MAIN_WS_NAME=%s" % ctx.workspace_name, - "-DRULES_SCALA_ARGS_FILE=%s" % argsFile.short_path, - ] + expand_location(ctx, final_jvm_flags), - main_class = ctx.attr.main_class, - rjars = rjars, - use_jacoco = ctx.configuration.coverage_enabled, - wrapper = wrapper, - )) - - return struct( - executable = executable, - files = out.files, - instrumented_files = out.instrumented_files, - providers = out.providers, - runfiles = ctx.runfiles(coverage_runfiles, transitive_files = out.runfiles.files), - scala = out.scala, - ) + # fixed phase + ("final", phase_scalatest_final), + ).final _scala_test_attrs = { "main_class": attr.string( @@ -191,15 +100,25 @@ _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), + *[extra["attrs"] for extra in extras if "attrs" in extra] + ), + executable = True, + fragments = ["java"], + outputs = _dicts.add( + common_outputs, + *[extra["outputs"] for extra in extras if "outputs" in extra] + ), + 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/test/phase/add_to_all_rules/BUILD b/test/phase/add_to_all_rules/BUILD new file mode 100644 index 000000000..fa0b847be --- /dev/null +++ b/test/phase/add_to_all_rules/BUILD @@ -0,0 +1,59 @@ +load( + "//test/phase/add_to_all_rules:phase_add_to_all_rules_test.bzl", + "add_to_all_rules_scala_binary", + "add_to_all_rules_scala_junit_test", + "add_to_all_rules_scala_library", + "add_to_all_rules_scala_library_for_plugin_bootstrapping", + "add_to_all_rules_scala_macro_library", + "add_to_all_rules_scala_repl", + "add_to_all_rules_scala_test", + "add_to_all_rules_singleton", +) + +add_to_all_rules_singleton( + name = "phase_add_to_all_rules", + visibility = ["//visibility:public"], +) + +add_to_all_rules_scala_binary( + name = "PhaseBinary", + srcs = ["PhaseBinary.scala"], + main_class = "scalarules.test.phase.add_to_all_rules.PhaseBinary", +) + +add_to_all_rules_scala_library( + name = "PhaseLibrary", + srcs = ["PhaseLibrary.scala"], + custom_content = "This is custom content in library", +) + +add_to_all_rules_scala_library_for_plugin_bootstrapping( + name = "PhaseLibraryForPluginBootstrapping", + srcs = ["PhaseLibrary.scala"], + custom_content = "This is custom content in library_for_plugin_bootstrapping", +) + +add_to_all_rules_scala_macro_library( + name = "PhaseMacroLibrary", + srcs = ["PhaseLibrary.scala"], + custom_content = "This is custom content in macro_library", +) + +add_to_all_rules_scala_test( + name = "PhaseTest", + srcs = ["PhaseTest.scala"], + custom_content = "This is custom content in test", +) + +add_to_all_rules_scala_junit_test( + name = "PhaseJunitTest", + srcs = ["PhaseJunitTest.scala"], + custom_content = "This is custom content in junit_test", + suffixes = ["Test"], +) + +add_to_all_rules_scala_repl( + name = "PhaseRepl", + srcs = ["PhaseLibrary.scala"], + custom_content = "This is custom content in repl", +) diff --git a/test/phase/add_to_all_rules/PhaseBinary.scala b/test/phase/add_to_all_rules/PhaseBinary.scala new file mode 100644 index 000000000..a1f7bf5da --- /dev/null +++ b/test/phase/add_to_all_rules/PhaseBinary.scala @@ -0,0 +1,7 @@ +package scalarules.test.phase.add_to_all_rules + +object PhaseBinary { + def main(args: Array[String]) { + val message = "You can customize binary phases!" + } +} diff --git a/test/phase/add_to_all_rules/PhaseJunitTest.scala b/test/phase/add_to_all_rules/PhaseJunitTest.scala new file mode 100644 index 000000000..bc20bbc44 --- /dev/null +++ b/test/phase/add_to_all_rules/PhaseJunitTest.scala @@ -0,0 +1,10 @@ +package scalarules.test.phase.add_to_all_rules + +import org.junit.Test + +class PhaseJunitTest { + @Test + def someTest: Unit = { + val message = "You can customize junit test phases!" + } +} diff --git a/test/phase/add_to_all_rules/PhaseLibrary.scala b/test/phase/add_to_all_rules/PhaseLibrary.scala new file mode 100644 index 000000000..a1b8b1219 --- /dev/null +++ b/test/phase/add_to_all_rules/PhaseLibrary.scala @@ -0,0 +1,5 @@ +package scalarules.test.phase.add_to_all_rules + +object PhaseLibrary { + val message = "You can customize library phases!" +} diff --git a/test/phase/add_to_all_rules/PhaseTest.scala b/test/phase/add_to_all_rules/PhaseTest.scala new file mode 100644 index 000000000..ca0803a11 --- /dev/null +++ b/test/phase/add_to_all_rules/PhaseTest.scala @@ -0,0 +1,10 @@ +package scalarules.test.phase.add_to_all_rules + +import org.scalatest._ + +class PhaseTest extends FlatSpec { + val message = "You can customize test phases!" + "HelloTest" should "be able to customize test phases!" in { + assert(message.equals("You can customize test phases!")) + } +} diff --git a/test/phase/add_to_all_rules/phase_add_to_all_rules.bzl b/test/phase/add_to_all_rules/phase_add_to_all_rules.bzl new file mode 100644 index 000000000..aa89d08ec --- /dev/null +++ b/test/phase/add_to_all_rules/phase_add_to_all_rules.bzl @@ -0,0 +1,10 @@ +# +# PHASE: add to all rules +# +# A dummy test phase to make sure phase is working for all rules +# +def phase_add_to_all_rules(ctx, p): + ctx.actions.write( + output = ctx.outputs.custom_output, + content = ctx.attr.custom_content, + ) diff --git a/test/phase/add_to_all_rules/phase_add_to_all_rules_test.bzl b/test/phase/add_to_all_rules/phase_add_to_all_rules_test.bzl new file mode 100644 index 000000000..2d9b4c5fc --- /dev/null +++ b/test/phase/add_to_all_rules/phase_add_to_all_rules_test.bzl @@ -0,0 +1,60 @@ +""" +This test makes sure custom phases can be inserted to the desired position through phase API +""" + +load( + "//scala:advanced_usage/providers.bzl", + _ScalaRulePhase = "ScalaRulePhase", +) +load( + "//scala:advanced_usage/scala.bzl", + _make_scala_binary = "make_scala_binary", + _make_scala_junit_test = "make_scala_junit_test", + _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_test = "make_scala_test", +) +load( + "//test/phase/add_to_all_rules:phase_add_to_all_rules.bzl", + _phase_add_to_all_rules = "phase_add_to_all_rules", +) + +# Inputs for the customizable rules +ext_add_to_all_rules = { + "attrs": { + "custom_content": attr.string( + default = "This is custom content", + ), + }, + "outputs": { + "custom_output": "%{name}.custom-output", + }, + "phase_providers": [ + "//test/phase/add_to_all_rules:phase_add_to_all_rules", + ], +} + +# The rule implementation for phase provider +def _add_to_all_rules_singleton_implementation(ctx): + return [ + _ScalaRulePhase( + custom_phases = [ + ("last", "", "add_to_all_rules", _phase_add_to_all_rules), + ], + ), + ] + +# The rule for phase provider +add_to_all_rules_singleton = rule( + implementation = _add_to_all_rules_singleton_implementation, +) + +add_to_all_rules_scala_binary = _make_scala_binary(ext_add_to_all_rules) +add_to_all_rules_scala_library = _make_scala_library(ext_add_to_all_rules) +add_to_all_rules_scala_library_for_plugin_bootstrapping = _make_scala_library_for_plugin_bootstrapping(ext_add_to_all_rules) +add_to_all_rules_scala_macro_library = _make_scala_macro_library(ext_add_to_all_rules) +add_to_all_rules_scala_test = _make_scala_test(ext_add_to_all_rules) +add_to_all_rules_scala_junit_test = _make_scala_junit_test(ext_add_to_all_rules) +add_to_all_rules_scala_repl = _make_scala_repl(ext_add_to_all_rules) diff --git a/test/phase/adjustment/BUILD b/test/phase/adjustment/BUILD new file mode 100644 index 000000000..947440aa0 --- /dev/null +++ b/test/phase/adjustment/BUILD @@ -0,0 +1,27 @@ +load( + "//test/phase/adjustment:phase_adjustment_test.bzl", + "adjustment_replace_scala_library", + "adjustment_replace_singleton", + "adjustment_scala_library", + "adjustment_singleton", +) + +adjustment_singleton( + name = "phase_adjustment", + visibility = ["//visibility:public"], +) + +adjustment_replace_singleton( + name = "phase_adjustment_replace", + visibility = ["//visibility:public"], +) + +adjustment_scala_library( + name = "PhaseLibrary", + srcs = ["PhaseLibrary.scala"], +) + +adjustment_replace_scala_library( + name = "PhaseLibraryReplace", + srcs = ["PhaseLibrary.scala"], +) diff --git a/test/phase/adjustment/PhaseLibrary.scala b/test/phase/adjustment/PhaseLibrary.scala new file mode 100644 index 000000000..20e661203 --- /dev/null +++ b/test/phase/adjustment/PhaseLibrary.scala @@ -0,0 +1,5 @@ +package scalarules.test.phase.adjustment + +object PhaseLibrary { + val message = "You can customize library phases!" +} diff --git a/test/phase/adjustment/phase_adjustment.bzl b/test/phase/adjustment/phase_adjustment.bzl new file mode 100644 index 000000000..ae47fd7d4 --- /dev/null +++ b/test/phase/adjustment/phase_adjustment.bzl @@ -0,0 +1,42 @@ +# +# PHASE: adjustment test +# +# Dummy test phases to make sure phase adjustment is working +# +def phase_first(ctx, p): + return struct( + info_first = "info from phase_first", + ) + +def phase_second(ctx, p): + return struct( + info_first = "phase_second redirect " + p.first.info_first, + info_second = "info from phase_second", + ) + +def phase_third(ctx, p): + ctx.actions.write( + output = ctx.outputs.custom_output, + content = "{} {} {}".format(p.first.info_first, p.second.info_first, p.second.info_second), + ) + +def phase_replace(ctx, p): + return struct( + info = "expected info from phase_replace", + ) + +def phase_being_replaced(ctx, p): + return struct( + info = "unexpected info from phase_being_replaced", + ) + +def phase_check_replacement(ctx, p): + final_info = "" + if getattr(p, "replace"): + final_info += p.replace.info + if hasattr(p, "being_replaced"): + final_info += p.being_replaced.info + ctx.actions.write( + output = ctx.outputs.custom_output, + content = "{} we should only see one info".format(final_info), + ) diff --git a/test/phase/adjustment/phase_adjustment_test.bzl b/test/phase/adjustment/phase_adjustment_test.bzl new file mode 100644 index 000000000..5d6699a4c --- /dev/null +++ b/test/phase/adjustment/phase_adjustment_test.bzl @@ -0,0 +1,79 @@ +""" +This test makes sure custom phases can be inserted to the desired position through phase API +""" + +load( + "//scala:advanced_usage/providers.bzl", + _ScalaRulePhase = "ScalaRulePhase", +) +load( + "//scala:advanced_usage/scala.bzl", + _make_scala_library = "make_scala_library", +) +load( + "//test/phase/adjustment:phase_adjustment.bzl", + _phase_being_replaced = "phase_being_replaced", + _phase_check_replacement = "phase_check_replacement", + _phase_first = "phase_first", + _phase_replace = "phase_replace", + _phase_second = "phase_second", + _phase_third = "phase_third", +) + +# Inputs for the customizable rules +ext_adjustment = { + "outputs": { + "custom_output": "%{name}.custom-output", + }, + "phase_providers": [ + "//test/phase/adjustment:phase_adjustment", + ], +} + +# The rule implementation for phase provider +def _adjustment_singleton_implementation(ctx): + return [ + _ScalaRulePhase( + custom_phases = [ + ("last", "", "second", _phase_second), + ("before", "second", "first", _phase_first), + ("after", "second", "third", _phase_third), + ], + ), + ] + +# The rule for phase provider +adjustment_singleton = rule( + implementation = _adjustment_singleton_implementation, +) + +adjustment_scala_library = _make_scala_library(ext_adjustment) + +# Inputs for the customizable rules +ext_adjustment_replace = { + "outputs": { + "custom_output": "%{name}.custom-output", + }, + "phase_providers": [ + "//test/phase/adjustment:phase_adjustment_replace", + ], +} + +# The rule implementation for phase provider +def _adjustment_replace_singleton_implementation(ctx): + return [ + _ScalaRulePhase( + custom_phases = [ + ("last", "", "check_replacement", _phase_check_replacement), + ("before", "check_replacement", "being_replaced", _phase_being_replaced), + ("replace", "being_replaced", "replace", _phase_replace), + ], + ), + ] + +# The rule for phase provider +adjustment_replace_singleton = rule( + implementation = _adjustment_replace_singleton_implementation, +) + +adjustment_replace_scala_library = _make_scala_library(ext_adjustment_replace) diff --git a/test/shell/test_phase.sh b/test/shell/test_phase.sh new file mode 100755 index 000000000..110f752ac --- /dev/null +++ b/test/shell/test_phase.sh @@ -0,0 +1,90 @@ +# 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}") + +output_file_should_contain_message() { + set +e + MSG=$1 + TEST_ARG=${@:2} + OUTPUT_FILE=$(echo ${@:3} | sed 's#//#/#g;s#:#/#g') + OUTPUT_PATH=$(bazel info bazel-bin)/$OUTPUT_FILE + bazel $TEST_ARG + RESPONSE_CODE=$? + cat $OUTPUT_PATH | 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 \"$MSG\" in file \"$OUTPUT_FILE\" but did not. $NC" + exit 1 + else + exit 0 + fi +} + +test_scala_binary_with_extra_phase() { + output_file_should_contain_message \ + "This is custom content" \ + build //test/phase/add_to_all_rules:PhaseBinary.custom-output +} + +test_scala_library_with_extra_phase_and_custom_content() { + output_file_should_contain_message \ + "This is custom content in library" \ + build //test/phase/add_to_all_rules:PhaseLibrary.custom-output +} + +test_scala_library_for_plugin_bootstrapping_with_extra_phase_and_custom_content() { + output_file_should_contain_message \ + "This is custom content in library_for_plugin_bootstrapping" \ + build //test/phase/add_to_all_rules:PhaseLibraryForPluginBootstrapping.custom-output +} + +test_scala_macro_library_with_extra_phase_and_custom_content() { + output_file_should_contain_message \ + "This is custom content in macro_library" \ + build //test/phase/add_to_all_rules:PhaseMacroLibrary.custom-output +} + +test_scala_test_with_extra_phase_and_custom_content() { + output_file_should_contain_message \ + "This is custom content in test" \ + build //test/phase/add_to_all_rules:PhaseTest.custom-output +} + +test_scala_junit_test_with_extra_phase_and_custom_content() { + output_file_should_contain_message \ + "This is custom content in junit_test" \ + build //test/phase/add_to_all_rules:PhaseJunitTest.custom-output +} + +test_scala_repl_with_extra_phase_and_custom_content() { + output_file_should_contain_message \ + "This is custom content in repl" \ + build //test/phase/add_to_all_rules:PhaseRepl.custom-output +} + +test_phase_adjustment_and_global_provider() { + output_file_should_contain_message \ + "info from phase_first phase_second redirect info from phase_first info from phase_second" \ + build //test/phase/adjustment:PhaseLibrary.custom-output +} + +test_phase_adjustment_replace() { + output_file_should_contain_message \ + "expected info from phase_replace we should only see one info" \ + build //test/phase/adjustment:PhaseLibraryReplace.custom-output +} + +$runner test_scala_binary_with_extra_phase +$runner test_scala_library_with_extra_phase_and_custom_content +$runner test_scala_library_for_plugin_bootstrapping_with_extra_phase_and_custom_content +$runner test_scala_macro_library_with_extra_phase_and_custom_content +$runner test_scala_test_with_extra_phase_and_custom_content +$runner test_scala_junit_test_with_extra_phase_and_custom_content +$runner test_scala_repl_with_extra_phase_and_custom_content +$runner test_phase_adjustment_and_global_provider +$runner test_phase_adjustment_replace diff --git a/test_rules_scala.sh b/test_rules_scala.sh index 04b1ced74..e8456fabf 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