aboutsummaryrefslogtreecommitdiffhomepage
path: root/site/designs/skylark/declared-providers.md
blob: 51aab95a938a71125a1ee9d143b662ca3d8089a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
---
layout: documentation
title: Declared Providers
---

# Declared Providers

Authors: [Dmitry Lomov](mailto:dslomov@google.com),
[Laurent Le Brun](mailto:laurentlb@google.com)

Status: Approved

Date: 2016-06-06

## Motivation

Skylark rules use simple Skylark structs as their providers. Skylark providers
are identified as simple names, such as 'java' or 'files'. This approach has the
advantage of simplicity, but as the number and complexity of Skylark rules grow,
we run into engineering scalability problems:

*   Using simple names for providers might lead to name conflicts (when
    unrelated rules call their providers the same simple name).
*   There is no clear formal way to add documentation for those providers; if
    any, the documentation is in prose in rule's doc comment, where it tends to
    become obsolete/incomplete; most existing providers have no documentation
    explaining their contracts at all.
*   It’s hard to know which fields to expect in a provider.
*   It’s hard to know which rule can depend on which rule.

## Goals

*   Solve name-conflict problem for providers
*   Allow to specify providers in Skylark rules with the same level of
    robustness as other components of the language, such as rules and aspects
*   Enable the same or better documentability of Skylark providers as native
    providers allow
*   Improve providers interoperability with native code.

## Proposal

We propose a redesign of how Skylark rules deal with providers to address the
above concerns. The redesign can occur in stages; those stages represent
implementation stages, but allow Skylark users to gradually opt for "more and
more engineering" as their custom rules progress from a small project on the
side to a public release.

Our proposal is backwards compatible with the existing providers in Bazel and
allows easy, gradual piecemeal replacement of them.

### Stage 1: Solving the Naming Problem

Under the new proposal, a minimum implementation of a custom provider looks like
this:

```
# rust.bzl

# Introduces a provider. `rust_provider` is now both a function
# that can be used to construct the provider instance,
# and a "symbol" that can be used to access it.
rust_provider = provider()

def _impl(ctx):
  # `rust_provider` is used as struct-like constructor
  # it accepts the same arguments as a standard `struct` function
  rust = rust_provider(defines = "-DFOO", ...)
  # return value of rule implementation function
  # is just a list of providers; their "names" are specified
  # by their constructors, see below
  return [ctx.provider(files = ...), rust]
rust_library = rule(implementation = _impl,
  # Optional declaration; the rule MUST provide all the
  # providers in this list
  providers = [rust_provider])
```

```
# Example of how to access the provider

load(":rust.bzl", "rust_provider")

def _impl(ctx):
  dep = ctx.attr.deps[0] # Target object
  # `rust_provider` is used as a key to access a particular
  # provider
  defines = dep[rust_provider].defines ...
```

#### The provider function

*   We introduce two new kinds of Skylark values, a *provider declaration* and a
    *provider*.
*   Provider declaration (`rust_provider` in the example) is created using the
    `provider` function.
*   Provider declaration can be used to construct a *provider* (`rust` in the
    example). Provider is a struct-like Skylark value, with the only difference
    that every provider is associated with its declaration (it is a different
    type). Arguments of a provider declaration when used as a function are
    exactly the same as that of a built-in `struct` function.
*   [Target](http://www.bazel.io/docs/skylark/lib/Target.html) objects become
    dictionaries of providers indexed by their declaration. Bracket notation can
    be used to retrieve a particular provider. Thus, provider declarations are
    [symbol-like](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
    values.
*   Providers can be private to an extension file; in that case the provider
    cannot be accessed outside that file.

#### Default providers (ctx.provider)

There is a set of default providers (`files`, `runfiles`, `data_runfiles`,
`executable`, `output_groups`, etc.). We group them in a single provider,
`ctx.provider`:

```
defaults = ctx.provider(files = set(), runfiles = ...)
```

The current set of APIs on Target objects that access these providers
(`target.files`, `target.output_group("name")` etc.) will continue to work.

#### Return value

The implementation function can return either a provider, or a list of
providers. It is an error to return two providers of the same type.

```
return [defaults, rust, cc]
return ctx.provider(files = set())
```

#### Declaring providers returned by a rule

Users need to know which rules provide which providers. This is important for
documentation and for knowing which dependencies are allowed (e.g. we want to
find easily what can go in the deps attribute of cc_library).

We allow rules to declare the providers they intend to return with a `providers`
argument of a
<code>[rule](http://www.bazel.io/docs/skylark/lib/globals.html#rule)</code>
function. It is an error if the rule implementation function does not return all
the providers listed in `providers`. It may however return additional providers.

```
rust_provider = provider()

rust_library = rule(implementation = _impl,
  # Optional declaration; the rule MUST provide all the
  # providers in this list
  providers = [rust_provider])
```

#### Migration path and support for "legacy" providers

To support current model of returning providers, where they are identified by a
simple name, we continue to allow providers name in the return struct:

```
def _impl(ctx):
  ...
  return struct(
    legacy_provider = struct(...),
    files = set(...),
    providers = [rust])
```

This also works for “default” providers, such as “files”, “runfiles” etc.
However if one of those legacy names is specified, it is an error to have
ctx.provider instance in the list of `providers`.

We also allow returning a declared provider both directly and with a simple
name:

```
def _impl(ctx):
  ...
  return struct(rust = rust, providers = [rust])
```

This allows the rules to mix old and new style, and migrate rule definition to a
new style without changing all the uses of that rule.

Old-style providers with simple names can still be accessed with dot-notation on
Target object, so all of the following is valid.

Old-style usage:

*   `target.rust` (=> rust)
*   `getattr(target, "rust")` (=> rust)
*   `hasattr(target, "rust")` (=> True)

New-style usage:

*   `target[rust_provider]` (=> rust)
*   `rust_provider in target` (=> True)
*   `target.keys` (=> [rust_provider])
*   `target.values` (=> [rust])
*   `target.items` (=> [(rust_provider, rust)])

#### type function

Type function on providers returns a string `"provider"`. Type function on a
provider instance returns a string `"struct"`.

### Stage 2: Documentation and Fields

Provider declarations are a convenient place to add more annotations to
providers. We propose 2 specific things there:

```
rust_provider = provider(
  doc = "This provider contains Rust information ...",
  fields = ["defines", "transitive_deps"]
)
```

This specifies documentation for the provider and a list of fields that the
provider can have.

If `fields` argument is present, extra, undeclared fields are not allowed.

Both `doc` and `fields` arguments to `provider` function are optional.

`fields` argument can also be a dictionary (from string to string), in that case
the keys are names of fields, and the values are documentation strings about
individual fields

```
rust_provider = provider(
  doc = "This provider contains Rust information ...",
  fields = {
    "defines": "doc for define",
    "transitive_deps": "doc for transitive deps,
  })
```

### Native Providers

Providers (as Skylark values) can be also declared natively. A set of
annotations can be developed to facilitate declaring them with little effort.

As a strawman example:

```
/**
 * Hypothetical implementation of Skylark provider value (result of
 * provider(..) function.
 */
class SkylarkProviderValue extends SkylarkValue {
  ...
  /**
   * Creates a SkylarkProviderValue for a native provider
   * `native` must be annotated with @SkylarkProvider annotation.
   * Field accessors and constructor function appear magically.
   */
  static <T> SkylarkProviderValue forNative(Class<T> native) { ... }
}

@SkylarkProvider(builder = Builder.class)
// A class with this annotation can be used as provider declaration
class rustProvider implements TransitiveInfoProvider {
  @SkylarkProviderField(doc = ...)
  // Skylark name is 'defines'
  String getDefines() { ... }

  @SkylarkProviderField(doc = ...)
  // Skylark name is 'transitive_deps'
  NestedSet<Artifact> getTransitiveDeps() { ... }

  @SkylarkProviderField(doc = ...)
  // Not allowed, the set of types exposed to Skylark is restricted
  DottedVersion getVersion() { ... }

  // Automatically used to provide an implementation for
  // construction function.
  static class Builder {
    // a setter for 'defines' field, based on name.
    void setDefines(String defines) { ... }
    // a setter for 'transitive_deps' field, based on name.
    void setTransitiveDeps(...) {...}
    rustProvider build() { ... }
  }
}
```