15
15
16
16
package com .google .devtools .build .lib .bazel .bzlmod ;
17
17
18
- import com .google .auto . value . AutoValue ;
18
+ import com .google .common . collect . ImmutableList ;
19
19
import com .google .common .collect .ImmutableMap ;
20
20
import com .google .common .collect .Maps ;
21
21
import com .google .devtools .build .lib .analysis .BlazeDirectories ;
22
+ import com .google .devtools .build .lib .cmdline .Label ;
22
23
import com .google .devtools .build .lib .cmdline .PackageIdentifier ;
23
24
import com .google .devtools .build .lib .cmdline .RepositoryMapping ;
24
25
import com .google .devtools .build .lib .cmdline .RepositoryName ;
29
30
import com .google .devtools .build .lib .packages .RuleClass ;
30
31
import com .google .devtools .build .lib .packages .RuleFactory .InvalidRuleException ;
31
32
import com .google .devtools .build .lib .packages .StarlarkNativeModule .ExistingRulesShouldBeNoOp ;
32
- import java .util .HashMap ;
33
+ import java .util .LinkedHashMap ;
33
34
import java .util .Map ;
34
35
import javax .annotation .Nullable ;
35
36
import net .starlark .java .eval .Dict ;
36
37
import net .starlark .java .eval .EvalException ;
38
+ import net .starlark .java .eval .NoneType ;
37
39
import net .starlark .java .eval .Starlark ;
40
+ import net .starlark .java .eval .StarlarkInt ;
41
+ import net .starlark .java .eval .StarlarkList ;
42
+ import net .starlark .java .eval .StarlarkSemantics ;
38
43
import net .starlark .java .eval .StarlarkThread ;
39
44
import net .starlark .java .syntax .Location ;
40
45
@@ -56,94 +61,163 @@ public static ModuleExtensionEvalStarlarkThreadContext fromOrNull(StarlarkThread
56
61
return ctx instanceof ModuleExtensionEvalStarlarkThreadContext c ? c : null ;
57
62
}
58
63
59
- @ AutoValue
60
- abstract static class RepoSpecAndLocation {
61
- abstract RepoSpec getRepoSpec ();
62
-
63
- abstract Location getLocation ();
64
-
65
- static RepoSpecAndLocation create (RepoSpec repoSpec , Location location ) {
66
- return new AutoValue_ModuleExtensionEvalStarlarkThreadContext_RepoSpecAndLocation (
67
- repoSpec , location );
68
- }
69
- }
64
+ record RepoRuleCall (
65
+ RuleClass ruleClass ,
66
+ Dict <String , Object > kwargs ,
67
+ Location location ,
68
+ ImmutableList <StarlarkThread .CallStackEntry > callStack ) {}
70
69
70
+ private final ModuleExtensionId extensionId ;
71
71
private final String repoPrefix ;
72
72
private final PackageIdentifier basePackageId ;
73
- private final RepositoryMapping repoMapping ;
73
+ private final RepositoryMapping baseRepoMapping ;
74
74
private final BlazeDirectories directories ;
75
75
private final ExtendedEventHandler eventHandler ;
76
- private final Map <String , RepoSpecAndLocation > generatedRepos = new HashMap <>();
76
+ private final Map <String , RepoRuleCall > deferredRepos = new LinkedHashMap <>();
77
77
78
78
public ModuleExtensionEvalStarlarkThreadContext (
79
+ ModuleExtensionId extensionId ,
79
80
String repoPrefix ,
80
81
PackageIdentifier basePackageId ,
81
- RepositoryMapping repoMapping ,
82
+ RepositoryMapping baseRepoMapping ,
82
83
RepositoryMapping mainRepoMapping ,
83
84
BlazeDirectories directories ,
84
85
ExtendedEventHandler eventHandler ) {
85
86
super (() -> mainRepoMapping );
87
+ this .extensionId = extensionId ;
86
88
this .repoPrefix = repoPrefix ;
87
89
this .basePackageId = basePackageId ;
88
- this .repoMapping = repoMapping ;
90
+ this .baseRepoMapping = baseRepoMapping ;
89
91
this .directories = directories ;
90
92
this .eventHandler = eventHandler ;
91
93
}
92
94
93
- public void createRepo (StarlarkThread thread , Dict <String , Object > kwargs , RuleClass ruleClass )
94
- throws InterruptedException , EvalException {
95
+ /**
96
+ * Records a call to a repo rule that should be created at the end of the module extension
97
+ * evaluation.
98
+ */
99
+ @ SuppressWarnings ("unchecked" )
100
+ public void lazilyCreateRepo (
101
+ StarlarkThread thread , Dict <String , Object > kwargs , RuleClass ruleClass )
102
+ throws EvalException {
95
103
Object nameValue = kwargs .getOrDefault ("name" , Starlark .NONE );
96
104
if (!(nameValue instanceof String name )) {
97
105
throw Starlark .errorf (
98
106
"expected string for attribute 'name', got '%s'" , Starlark .type (nameValue ));
99
107
}
100
108
RepositoryName .validateUserProvidedRepoName (name );
101
- RepoSpecAndLocation conflict = generatedRepos .get (name );
109
+ RepoRuleCall conflict = deferredRepos .get (name );
102
110
if (conflict != null ) {
103
111
throw Starlark .errorf (
104
112
"A repo named %s is already generated by this module extension at %s" ,
105
- name , conflict .getLocation ());
113
+ name , conflict .location ());
106
114
}
107
- String prefixedName = repoPrefix + name ;
108
- try {
109
- Rule rule =
110
- BzlmodRepoRuleCreator .createRule (
111
- basePackageId ,
112
- repoMapping ,
113
- directories ,
114
- thread .getSemantics (),
115
- eventHandler ,
116
- thread .getCallStack (),
117
- ruleClass ,
118
- Maps .transformEntries (kwargs , (k , v ) -> k .equals ("name" ) ? prefixedName : v ));
115
+ deferredRepos .put (
116
+ name ,
117
+ new RepoRuleCall (
118
+ ruleClass ,
119
+ // The extension may mutate the values of the kwargs after this function returns.
120
+ (Dict <String , Object >) deepCloneAttrValue (kwargs ),
121
+ thread .getCallerLocation (),
122
+ thread .getCallStack ()));
123
+ }
119
124
120
- Map <String , Object > attributes =
121
- Maps .filterKeys (
122
- Maps .transformEntries (kwargs , (k , v ) -> rule .getAttr (k )), k -> !k .equals ("name" ));
123
- String bzlFile = ruleClass .getRuleDefinitionEnvironmentLabel ().getUnambiguousCanonicalForm ();
124
- var attributesValue = AttributeValues .create (attributes );
125
- AttributeValues .validateAttrs (
126
- attributesValue , String .format ("%s '%s'" , rule .getRuleClass (), name ));
127
- RepoSpec repoSpec =
128
- RepoSpec .builder ()
129
- .setBzlFile (bzlFile )
130
- .setRuleClassName (ruleClass .getName ())
131
- .setAttributes (attributesValue )
132
- .build ();
125
+ /**
126
+ * Evaluates the repo rule calls recorded by {@link #lazilyCreateRepo} and returns all repos
127
+ * generated by the extension. The key is the "internal name" (as specified by the extension) of
128
+ * the repo, and the value is the {@link RepoSpec}.
129
+ */
130
+ public ImmutableMap <String , RepoSpec > createRepos (StarlarkSemantics starlarkSemantics )
131
+ throws EvalException , InterruptedException {
132
+ // LINT.IfChange
133
+ // Make it possible to refer to extension repos in the label attributes of another extension
134
+ // repo. Wrapping a label in Label(...) ensures that it is evaluated with respect to the
135
+ // containing module's repo mapping instead.
136
+ var extensionRepos =
137
+ Maps .asMap (
138
+ deferredRepos .keySet (),
139
+ apparentName -> RepositoryName .createUnvalidated (repoPrefix + apparentName ));
140
+ RepositoryMapping fullRepoMapping =
141
+ RepositoryMapping .create (extensionRepos , baseRepoMapping .ownerRepo ())
142
+ .withAdditionalMappings (baseRepoMapping );
143
+ // LINT.ThenChange(//src/main/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionRepoMappingEntriesFunction.java)
144
+
145
+ ImmutableMap .Builder <String , RepoSpec > repoSpecs = ImmutableMap .builder ();
146
+ for (var entry : deferredRepos .entrySet ()) {
147
+ String name = entry .getKey ();
148
+ RepoRuleCall repoRuleCall = entry .getValue ();
149
+ try {
150
+ String prefixedName = repoPrefix + name ;
151
+ Rule rule =
152
+ BzlmodRepoRuleCreator .createRule (
153
+ basePackageId ,
154
+ fullRepoMapping ,
155
+ directories ,
156
+ starlarkSemantics ,
157
+ eventHandler ,
158
+ repoRuleCall .callStack ,
159
+ repoRuleCall .ruleClass ,
160
+ Maps .transformEntries (
161
+ repoRuleCall .kwargs , (k , v ) -> k .equals ("name" ) ? prefixedName : v ));
133
162
134
- generatedRepos .put (name , RepoSpecAndLocation .create (repoSpec , thread .getCallerLocation ()));
135
- } catch (InvalidRuleException | NoSuchPackageException e ) {
136
- throw Starlark .errorf ("%s" , e .getMessage ());
163
+ Map <String , Object > attributes =
164
+ Maps .filterKeys (
165
+ Maps .transformEntries (repoRuleCall .kwargs , (k , v ) -> rule .getAttr (k )),
166
+ k -> !k .equals ("name" ));
167
+ String bzlFile =
168
+ repoRuleCall
169
+ .ruleClass
170
+ .getRuleDefinitionEnvironmentLabel ()
171
+ .getUnambiguousCanonicalForm ();
172
+ var attributesValue = AttributeValues .create (attributes );
173
+ AttributeValues .validateAttrs (
174
+ attributesValue ,
175
+ String .format ("in the extension '%s'" , extensionId .asTargetString ()),
176
+ String .format ("%s '%s'" , rule .getRuleClass (), name ));
177
+ RepoSpec repoSpec =
178
+ RepoSpec .builder ()
179
+ .setBzlFile (bzlFile )
180
+ .setRuleClassName (repoRuleCall .ruleClass .getName ())
181
+ .setAttributes (attributesValue )
182
+ .build ();
183
+ repoSpecs .put (name , repoSpec );
184
+ } catch (EvalException e ) {
185
+ throw e .withCallStack (repoRuleCall .callStack );
186
+ } catch (InvalidRuleException | NoSuchPackageException e ) {
187
+ throw new EvalException (e ).withCallStack (repoRuleCall .callStack );
188
+ }
137
189
}
190
+ return repoSpecs .buildOrThrow ();
138
191
}
139
192
140
193
/**
141
- * Returns the repos generated by the extension so far. The key is the "internal name" (as
142
- * specified by the extension) of the repo, and the value is the package containing (only) the
143
- * repo rule.
194
+ * Deep-clones a potentially mutable Starlark object that is a valid repo rule attribute.
195
+ * Immutable (sub-)objects are not cloned.
144
196
*/
145
- public ImmutableMap <String , RepoSpec > getGeneratedRepoSpecs () {
146
- return ImmutableMap .copyOf (
147
- Maps .transformValues (generatedRepos , RepoSpecAndLocation ::getRepoSpec ));
197
+ private static Object deepCloneAttrValue (Object x ) throws EvalException {
198
+ if (x instanceof NoneType
199
+ || x instanceof Boolean
200
+ || x instanceof StarlarkInt
201
+ || x instanceof String
202
+ || x instanceof Label ) {
203
+ return x ;
204
+ }
205
+ // Mutable Starlark values have to be cloned deeply.
206
+ if (x instanceof Dict <?, ?> dict ) {
207
+ Dict .Builder <Object , Object > newDict = Dict .builder ();
208
+ for (Map .Entry <?, ?> e : dict .entrySet ()) {
209
+ newDict .put (deepCloneAttrValue (e .getKey ()), deepCloneAttrValue (e .getValue ()));
210
+ }
211
+ return newDict .buildImmutable ();
212
+ }
213
+ if (x instanceof Iterable <?> iterable ) {
214
+ ImmutableList .Builder <Object > newList = ImmutableList .builder ();
215
+ for (Object item : iterable ) {
216
+ newList .add (deepCloneAttrValue (item ));
217
+ }
218
+ return StarlarkList .immutableCopyOf (newList .build ());
219
+ }
220
+ throw Starlark .errorf (
221
+ "unexpected Starlark value: %s (of type %s)" , Starlark .repr (x ), Starlark .type (x ));
148
222
}
149
223
}
0 commit comments