Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Deps Toolchain Infrastructure #1067

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions scala/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ load(
_declare_scalac_provider = "declare_scalac_provider",
)
load("//scala:scala_toolchain.bzl", "scala_toolchain")
load("//scala/toolchains:toolchains.bzl", "declare_deps_toolchain")
load("//scala:providers.bzl", "declare_deps_provider")

toolchain_type(
name = "toolchain_type",
Expand Down Expand Up @@ -94,3 +96,36 @@ java_library(
srcs = ["PlaceHolderClassToCreateEmptyJarForScalaImport.java"],
visibility = ["//visibility:public"],
)

toolchain_type(
name = "deps_toolchain_type",
visibility = ["//visibility:public"],
)

toolchain(
name = "deps_toolchain",
toolchain = "@io_bazel_rules_scala//scala:deps_toolchain_impl",
toolchain_type = "@io_bazel_rules_scala//scala:deps_toolchain_type",
visibility = ["//visibility:public"],
)

declare_deps_provider(
name = "scala_xml_provider",
visibility = ["//visibility:public"],
deps = ["//external:io_bazel_rules_scala/dependency/scala/scala_xml"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this for backwards compatibility? if so have you thought of how/when we should break it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it's for backwards compatibility. To get rid of it we need to provide users with an alternative for binds (*_repositories without binds, toolchains, or something loader specific).

)

declare_deps_provider(
name = "parser_combinators_provider",
visibility = ["//visibility:public"],
deps = ["//external:io_bazel_rules_scala/dependency/scala/parser_combinators"],
)

declare_deps_toolchain(
name = "deps_toolchain_impl",
dep_providers = {
"@io_bazel_rules_scala//scala:scala_xml_provider": "scala_xml",
"@io_bazel_rules_scala//scala:parser_combinators_provider": "parser_combinators",
},
visibility = ["//visibility:public"],
)
2 changes: 1 addition & 1 deletion scala/private/common_attributes.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ resolve_deps = {
"_scala_toolchain": attr.label_list(
default = [
Label(
"//external:io_bazel_rules_scala/dependency/scala/scala_library",
"@io_bazel_rules_scala//scala/private/toolchain_deps:scala_library_classpath",
),
],
allow_files = False,
Expand Down
2 changes: 1 addition & 1 deletion scala/private/rules/scala_junit_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ _junit_resolve_deps = {
"_scala_toolchain": attr.label_list(
default = [
Label(
"//external:io_bazel_rules_scala/dependency/scala/scala_library",
"@io_bazel_rules_scala//scala/private/toolchain_deps:scala_library_classpath",
),
Label("//external:io_bazel_rules_scala/dependency/junit/junit"),
Label(
Expand Down
2 changes: 1 addition & 1 deletion scala/private/rules/scala_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ _test_resolve_deps = {
"_scala_toolchain": attr.label_list(
default = [
Label(
"//external:io_bazel_rules_scala/dependency/scala/scala_library",
"@io_bazel_rules_scala//scala/private/toolchain_deps:scala_library_classpath",
),
Label(
"//external:io_bazel_rules_scala/dependency/scalatest/scalatest",
Expand Down
27 changes: 27 additions & 0 deletions scala/private/toolchain_deps/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
load(":toolchain_dep_rules.bzl", "common_toolchain_deps", "scala_toolchain_deps")

# dependencies from ScalacProvider
scala_toolchain_deps(
name = "scala_compile_classpath",
from_classpath = "default_repl_classpath",
visibility = ["//visibility:public"],
)

scala_toolchain_deps(
name = "scala_library_classpath",
from_classpath = "default_classpath",
visibility = ["//visibility:public"],
)

# dependencies from DepsInfo
common_toolchain_deps(
name = "scala_xml",
provider_id = "scala_xml",
visibility = ["//visibility:public"],
)

common_toolchain_deps(
name = "parser_combinators",
provider_id = "parser_combinators",
visibility = ["//visibility:public"],
)
29 changes: 29 additions & 0 deletions scala/private/toolchain_deps/toolchain_dep_rules.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
load("@io_bazel_rules_scala//scala:providers.bzl", _ScalacProvider = "ScalacProvider")
load("//scala/private/toolchain_deps:toolchain_deps.bzl", "expose_toolchain_deps", "java_info_for_deps")

def _scala_toolchain_deps(ctx):
_toolchain_type = "@io_bazel_rules_scala//scala:toolchain_type"
from_classpath = ctx.attr.from_classpath

scalac_provider = ctx.toolchains[_toolchain_type].scalac_provider_attr[_ScalacProvider]
classpath_deps = getattr(scalac_provider, from_classpath)
return java_info_for_deps(classpath_deps)

scala_toolchain_deps = rule(
implementation = _scala_toolchain_deps,
attrs = {
"from_classpath": attr.string(mandatory = True),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check my understanding: Is it correct to say that we need this (and we can't use expose_toolchain_deps here because of preexisting code that defines ScalacProvider and some other things, right?
Is there an intrinsic reason why we can't drop ScalacProvider and model everything with expose_toolchain_deps, or is it because it would be hard to change?

Either way is fine by me, even if we were to remove ScalacProvider I'd vote for doing it in a follow-up PR. I just want to wrap my head around this :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ScalacProvider comes from the existing scala toolchain, and it would be a breaking change for some users if it gets modified. I would like, if possible, to have no breaking changes with the introduction of this toolchains infra. If it looks something that needs to be refactored into unified version, I think it's better to have a separate PR, which would be easier to revert if that changes becomes problematic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks! Seems reasonable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate PR sounds very reasonable while also important since I think having multiple mental models would make things really hard

},
toolchains = ["@io_bazel_rules_scala//scala:toolchain_type"],
)

def _common_toolchain_deps(ctx):
return expose_toolchain_deps(ctx, "@io_bazel_rules_scala//scala:deps_toolchain_type")

common_toolchain_deps = rule(
implementation = _common_toolchain_deps,
attrs = {
"provider_id": attr.string(mandatory = True),
},
toolchains = ["@io_bazel_rules_scala//scala:deps_toolchain_type"],
)
18 changes: 18 additions & 0 deletions scala/private/toolchain_deps/toolchain_deps.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
load("@io_bazel_rules_scala//scala:providers.bzl", "DepsInfo")

def _log_required_provider_id(target, toolchain_type_label, provider_id):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't it misleading? it's called log but you're actually failing

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

fail(target + " requires mapping of " + provider_id + " provider id on the toolchain " + toolchain_type_label)

def java_info_for_deps(deps):
return [java_common.merge([dep[JavaInfo] for dep in deps])]

def expose_toolchain_deps(ctx, toolchain_type_label):
dep_provider_id = ctx.attr.provider_id
dep_providers_map = getattr(ctx.toolchains[toolchain_type_label], "dep_providers")
dep_provider = {v: k for k, v in dep_providers_map.items()}.get(dep_provider_id)

if dep_provider == None:
_log_required_provider_id(ctx.attr.name, toolchain_type_label, dep_provider_id)

deps = dep_provider[DepsInfo].deps
return java_info_for_deps(deps)
21 changes: 21 additions & 0 deletions scala/providers.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,24 @@ declare_scalac_provider = rule(
"default_macro_classpath": attr.label_list(allow_files = True),
},
)

DepsInfo = provider(
doc = "Dependencies needed for specifc rules",
fields = [
"deps",
],
)

def _declare_deps_provider(ctx):
return [
DepsInfo(
deps = ctx.attr.deps,
),
]

declare_deps_provider = rule(
implementation = _declare_deps_provider,
attrs = {
"deps": attr.label_list(allow_files = True),
},
)
2 changes: 1 addition & 1 deletion scala/support/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ scala_library(
],
visibility = ["//visibility:public"],
deps = [
"//external:io_bazel_rules_scala/dependency/scala/scala_xml",
"//external:io_bazel_rules_scala/dependency/scalatest/scalatest",
"//scala/private/toolchain_deps:scala_xml",
],
)
6 changes: 5 additions & 1 deletion scala/toolchains.bzl
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
def scala_register_toolchains():
native.register_toolchains("@io_bazel_rules_scala//scala:default_toolchain")
native.register_toolchains(
"@io_bazel_rules_scala//scala:deps_toolchain",
"@io_bazel_rules_scala//scala:default_toolchain",
)

def scala_register_unused_deps_toolchains():
native.register_toolchains(
"@io_bazel_rules_scala//scala:deps_toolchain",
"@io_bazel_rules_scala//scala:unused_dependency_checker_error_toolchain",
)
1 change: 1 addition & 0 deletions scala/toolchains/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

176 changes: 176 additions & 0 deletions scala/toolchains/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Toolchains for Deps

## Motivation and design patterns

Provide a way to configure rules_scala without using [problematic](https://github.com/bazelbuild/bazel/issues/1952) bind.

### Patterns
- Dependency providers on toolchains for toolchain aware rules

This is recommended pattern, when a rule implementation knows how to consume information from a toolchain.

- Rules to export deps as targets to be depended on by other rules not aware of toolchains

This pattern is used to pass dependencies to rules, which are not aware of particular toolchain. For example, Scala
compile classpath deps which are defined on Scala toolchains can be made available to non scala rules by creating a
toolchain aware rule to export deps.

### Usage
Users who want to customize dependecies for a feature will have to declare deps providers and wire them up in the
toolchain. Eg.:

```python
declare_deps_provider(
name = "scalapb_compile_deps_provider",
visibility = ["//visibility:public"],
deps = [
"@com_lihaoyi_fastparse_2_12",
"@com_thesamet_scalapb_lenses_2_12",
"@com_thesamet_scalapb_scalapb_runtime_2_12",
"@io_grpc_grpc_protobuf",
"@org_scala_lang_scala_library",
],
)

declare_deps_provider(
name = "scalapb_grpc_deps_provider",
visibility = ["//visibility:public"],
deps = [
"@com_google_guava_guava",
"@com_google_instrumentation_instrumentation_api",
"@com_lmax_disruptor",
"@com_thesamet_scalapb_scalapb_runtime_grpc_2_12",
"@io_grpc_grpc_api",
"@io_grpc_grpc_context",
"@io_grpc_grpc_core",
"@io_grpc_grpc_netty",
"@io_grpc_grpc_protobuf",
"@io_grpc_grpc_stub",
"@io_netty_netty_buffer",
"@io_netty_netty_codec",
"@io_netty_netty_codec_http",
"@io_netty_netty_codec_http2",
"@io_netty_netty_codec_socks",
"@io_netty_netty_common",
"@io_netty_netty_handler",
"@io_netty_netty_handler_proxy",
"@io_netty_netty_resolver",
"@io_netty_netty_transport",
"@io_opencensus_opencensus_api",
"@io_opencensus_opencensus_contrib_grpc_metrics",
"@io_opencensus_opencensus_impl",
"@io_opencensus_opencensus_impl_core",
"@io_perfmark_perfmark_api",
],
)

declare_deps_toolchain(
name = "proto_deps_toolchain_impl",
dep_providers = {
":scalapb_compile_deps_provider": "compile_deps",
":scalapb_grpc_deps_provider": "grpc_deps",
},
visibility = ["//visibility:public"],
)

toolchain(
name = "proto_deps_toolchain",
toolchain = ":proto_deps_toolchain_impl",
toolchain_type = "@io_bazel_rules_scala//scala_proto/toolchain:proto_toolchain_type",
visibility = ["//visibility:public"],
)
```
## Exporting deps via toolchains

Toolchains can be used to provide dependencies indirectly. For rules which are not aware of specific toolchains,
dependencies can be provided by adding to deps a target which knows how to export from a toolchain. Eg.:
```python
# target which exports toolchain deps from provider with ID "compile_deps"
proto_toolchain_deps(
name = "default_scalapb_compile_dependencies",
provider_id = "compile_deps",
visibility = ["//visibility:public"],
)

# provider declaration
declare_deps_provider(
name = "scalapb_grpc_deps_provider",
deps = ["@dep1", "@dep2"],
visibility = ["//visibility:public"],
)

# toolchain declaration:
toolchain_type(
name = "proto_toolchain_type",
visibility = ["//visibility:public"],
)

toolchain(
name = "proto_toolchain",
toolchain = ":proto_toolchain_impl",
toolchain_type = ":proto_toolchain_type",
visibility = ["//visibility:public"],
)

declare_deps_toolchain(
name = "proto_toolchain_impl",
dep_providers = {
":scalapb_compile_deps_provider": "compile_deps",
":scalapb_grpc_deps_provider": "grpc_deps"
},
visibility = ["//visibility:public"],
)
```

To define toolchain deps with deps exporting, the follwoing steps need to be taken:
1. Declare dep providers with `declare_deps_provider`
2. Define `toolchain_type`, declare toolchain with infra helper `declare_deps_toolchain`, wire them up with `toolchain`
3. Create rule exposing toolchain deps using infra helper `expose_toolchain_deps`
4. Declare deps targets
5. Use deps targets instead of bind targets!

## Reusable infra code to define toolchains for dependencies

### Reusable symbols
- provider `DepsProvider` - provider with a field `deps`, which contains dep list to be provided via toolchain
- rule `declare_deps_provider` - used to declare target with `DepsProvider`. Eg.:
```python
declare_deps_provider(
name = "scalapb_grpc_deps_provider",
deps = ["@dep1", "@dep2"],
visibility = ["//visibility:public"],
)
```
- rule `declare_deps_toolchain` - used to declare toolchains for deps providers. Eg.:
```python
declare_deps_toolchain(
name = "proto_toolchain_impl",
dep_providers = {
":scalapb_compile_deps_provider": "compile_deps",
":scalapb_grpc_deps_provider": "grpc_deps"
},
visibility = ["//visibility:public"],
)

```
Attribute `dep_providers` is maps dep provider label to an id used for indirection in toolchain exporting rules

- `def expose_toolchain_deps(ctx, toolchain_type_label)` - helper to export export deps from toolchain. Intended to be
used when defining toolchain deps exporting rules. Eg.:
```python
load("//scala/private/toolchain_deps:toolchain_deps.bzl", "expose_toolchain_deps")

def _toolchain_deps(ctx):
toolchain_type_label = "@io_bazel_rules_scala//scala_proto/toolchain:proto_toolchain_type"
return expose_toolchain_deps(ctx, toolchain_type_label)

proto_toolchain_deps = rule(
implementation = _toolchain_deps,
attrs = {
"provider_id": attr.string(
mandatory = True,
),
},
toolchains = ["@io_bazel_rules_scala//scala_proto/toolchain:proto_toolchain_type"],
)
```
16 changes: 16 additions & 0 deletions scala/toolchains/toolchains.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("@io_bazel_rules_scala//scala:providers.bzl", "DepsInfo")

def _deps_toolchain(ctx):
toolchain_info = platform_common.ToolchainInfo(
dep_providers = ctx.attr.dep_providers,
)
return [toolchain_info]

declare_deps_toolchain = rule(
attrs = {
"dep_providers": attr.label_keyed_string_dict(
providers = [DepsInfo],
),
},
implementation = _deps_toolchain,
)
Loading