@@ -94,22 +94,48 @@ def is_windows(ctx):
94
94
return ctx .configuration .host_path_separator == ";"
95
95
96
96
def _proto_compile_impl (ctx ):
97
- # mut <list<File>>
98
- outputs = [] + ctx .outputs .outputs
99
-
100
- # mut <?string> If defined, we are using the srcs to predict the outputs
101
- # srcgen_ext = None
102
- if len (ctx .attr .srcs ) > 0 :
103
- if len (ctx .outputs .outputs ) > 0 :
104
- fail ("rule must provide 'srcs' or 'outputs', but not both" )
105
-
106
- # srcgen_ext = ctx.attr.srcgen_ext
107
- outputs = [ctx .actions .declare_file (name ) for name in ctx .attr .srcs ]
108
-
109
97
###
110
98
### Part 1: setup variables used in scope
111
99
###
112
100
101
+ # out_dir is used in conjunction with file.short_path to determine root
102
+ # output file paths
103
+ out_dir = ctx .bin_dir .path
104
+ if ctx .label .workspace_root :
105
+ out_dir = "/" .join ([out_dir , ctx .label .workspace_root ])
106
+
107
+ if len (ctx .attr .srcs ) > 0 and len (ctx .outputs .outputs ) > 0 :
108
+ fail ("rule must provide 'srcs' or 'outputs' (but not both)" )
109
+
110
+ # <dict<string,File>: output files mapped by their package-relative path.
111
+ # This struct is given to the provider.
112
+ output_files_by_rel_path = {}
113
+
114
+ # const <dict<string,string>. The key is the file basename, value is the
115
+ # short_path of the output file.
116
+ output_short_paths_by_basename = {}
117
+
118
+ # renames is a mapping from the output filename that was produced by the
119
+ # plugin to the actual name we want to output.
120
+ renames = {}
121
+
122
+ if len (ctx .attr .srcs ):
123
+ # assume filenames in srcs are already package-relative
124
+ for name in ctx .attr .srcs :
125
+ rel = "/" .join ([ctx .label .package , name ])
126
+ actual_name = name + ctx .attr .output_file_suffix
127
+ if actual_name != name :
128
+ renames [rel ] = "/" .join ([ctx .label .package , actual_name ])
129
+ f = ctx .actions .declare_file (actual_name )
130
+ output_files_by_rel_path [rel ] = f
131
+ output_short_paths_by_basename [name ] = rel
132
+ else :
133
+ for f in ctx .outputs .outputs :
134
+ # rel = _get_package_relative_path(ctx.label, f.short_path)
135
+ rel = f .short_path
136
+ output_files_by_rel_path [rel ] = f
137
+ output_short_paths_by_basename [f .basename ] = rel
138
+
113
139
# const <bool> verbosity flag
114
140
verbose = ctx .attr .verbose
115
141
@@ -125,9 +151,6 @@ def _proto_compile_impl(ctx):
125
151
# const <dict<string,string>>
126
152
outs = {_plugin_label_key (Label (k )): v for k , v in ctx .attr .outs .items ()}
127
153
128
- # const <dict<string,File>. outputs indexed by basename.
129
- outputs_by_basename = {f .basename : f for f in outputs }
130
-
131
154
# mut <list<File>> set of descriptors for the compile action
132
155
descriptors = proto_info .transitive_descriptor_sets .to_list ()
133
156
@@ -279,16 +302,16 @@ def _proto_compile_impl(ctx):
279
302
# into place
280
303
if len (ctx .attr .output_mappings ) > 0 :
281
304
copy_commands = []
282
- out_dir = ctx .bin_dir .path
283
- if ctx .label .workspace_root :
284
- out_dir = "/" .join ([out_dir , ctx .label .workspace_root ])
285
305
for mapping in ctx .attr .output_mappings :
286
306
basename , _ , intermediate_filename = mapping .partition ("=" )
287
- intermediate_filename = "/" .join ([out_dir , intermediate_filename ])
288
- output = outputs_by_basename .get (basename , None )
289
- if not output :
307
+ output_short_path = output_short_paths_by_basename .get (basename )
308
+ if not output_short_path :
290
309
fail ("the mapped file '%s' was not listed in outputs" % basename )
291
- copy_commands .append ("cp '{}' '{}'" .format (intermediate_filename , output .path ))
310
+ copy_commands .append ("cp '{dir}/{src}' '{dir}/{dst}'" .format (
311
+ dir = out_dir ,
312
+ src = intermediate_filename ,
313
+ dst = output_short_path ,
314
+ ))
292
315
copy_script = ctx .actions .declare_file (ctx .label .name + "_copy.sh" )
293
316
ctx .actions .write (copy_script , "\n " .join (copy_commands ), is_executable = True )
294
317
inputs .append (copy_script )
@@ -298,17 +321,53 @@ def _proto_compile_impl(ctx):
298
321
if len (mods ):
299
322
mv_commands = []
300
323
for suffix , action in mods .items ():
301
- for f in outputs :
302
- if f .short_path .endswith (suffix ):
303
- mv_commands .append ("awk '%s' %s > %s.tmp" % (action , f .path , f .path ))
304
- mv_commands .append ("mv %s.tmp %s" % (f .path , f .path ))
324
+ for output_short_path in output_short_paths_by_basename .values ():
325
+ if output_short_path .endswith (suffix ):
326
+ mv_commands .append ("awk '{action}' {dir}/{short_path} > {dir}/{short_path}.tmp" .format (
327
+ action = action ,
328
+ dir = out_dir ,
329
+ short_path = output_short_path ,
330
+ ))
331
+ mv_commands .append ("mv {dir}/{short_path}.tmp {dir}/{short_path}" .format (
332
+ dir = out_dir ,
333
+ short_path = output_short_path ,
334
+ ))
305
335
mv_script = ctx .actions .declare_file (ctx .label .name + "_mv.sh" )
306
336
ctx .actions .write (mv_script , "\n " .join (mv_commands ), is_executable = True )
307
337
inputs .append (mv_script )
308
338
commands .append (mv_script .path )
309
339
340
+ # if the ctx.attr.output_file_suffix was set in conjunction with
341
+ # ctx.attr.srcs, we want to rename all the output files to a different
342
+ # suffix (e.g. foo.ts -> foo.ts.gen). The relocates the files that were
343
+ # generated by protoc plugins to a different name. This is used by the
344
+ # 'proto_compiled_sources' rule. The reason is that if we also have a
345
+ # `foo.ts` source file sitting in the workspace (checked into git), rules
346
+ # like `ts_project` will perform a 'copy_to_bin' action on the file. If we
347
+ # didn't do this rename, the ts_project rule and the proto_compile rule
348
+ # would attempt to create the same output file in bazel-bin (foo.ts),
349
+ # causing an error.
350
+ #
351
+ # In the case of proto_compiled_sources, executing `bazel run
352
+ # //proto:foo_ts.update` would generate the file
353
+ # `bazel-bin/proto/foo.ts.gen` and the gencopy operation will copy that file
354
+ # to `WORKSPACE/proto/foo.ts`, essentially making the `.gen` a
355
+ # temporary-like file.
356
+ if len (renames ):
357
+ rename_commands = []
358
+ for src , dst in renames .items ():
359
+ rename_commands .append ("mv {dir}/{src} {dir}/{dst}" .format (
360
+ dir = out_dir ,
361
+ src = src ,
362
+ dst = dst ,
363
+ ))
364
+ rename_script = ctx .actions .declare_file (ctx .label .name + "_rename.sh" )
365
+ ctx .actions .write (rename_script , "\n " .join (rename_commands ), is_executable = True )
366
+ inputs .append (rename_script )
367
+ commands .append (rename_script .path )
368
+
310
369
if verbose :
311
- before = ["env" , "pwd" , "ls -al ." , "echo '\n ##### SANDBOX BEFORE RUNNING PROTOC'" , "find * -type l" ]
370
+ before = ["env" , "pwd" , "ls -al ." , "echo '\n ##### SANDBOX BEFORE RUNNING PROTOC'" , "find * -type l | grep -v node_modules " ]
312
371
after = ["echo '\n ##### SANDBOX AFTER RUNNING PROTOC'" , "find * -type f" ]
313
372
commands = before + commands + after
314
373
@@ -327,7 +386,7 @@ def _proto_compile_impl(ctx):
327
386
for f in inputs :
328
387
# buildifier: disable=print
329
388
print ("INPUT:" , f .path )
330
- for f in outputs :
389
+ for f in output_files_by_rel_path . values () :
331
390
# buildifier: disable=print
332
391
print ("EXPECTED OUTPUT:" , f .path )
333
392
@@ -336,17 +395,26 @@ def _proto_compile_impl(ctx):
336
395
command = "\n " .join (commands ),
337
396
inputs = inputs ,
338
397
mnemonic = "Protoc" ,
339
- outputs = outputs ,
398
+ outputs = output_files_by_rel_path . values () ,
340
399
progress_message = "Compiling protoc outputs for %r" % [f .basename for f in protos ],
341
400
tools = tools ,
342
401
input_manifests = input_manifests ,
343
402
env = {"BAZEL_BINDIR" : ctx .bin_dir .path },
344
403
)
345
404
346
- return [
347
- ProtoCompileInfo (label = ctx .label , outputs = outputs ),
348
- DefaultInfo (files = depset (outputs )),
405
+ outputs = output_files_by_rel_path .values ()
406
+
407
+ providers = [
408
+ ProtoCompileInfo (
409
+ label = ctx .label ,
410
+ outputs = outputs ,
411
+ output_files_by_rel_path = output_files_by_rel_path ,
412
+ ),
349
413
]
414
+ if ctx .attr .default_info :
415
+ providers .append (DefaultInfo (files = depset (outputs )))
416
+
417
+ return providers
350
418
351
419
proto_compile = rule (
352
420
implementation = _proto_compile_impl ,
@@ -387,6 +455,14 @@ proto_compile = rule(
387
455
),
388
456
"verbose" : attr .bool (
389
457
doc = "The verbosity flag." ,
458
+ default = False ,
459
+ ),
460
+ "default_info" : attr .bool (
461
+ doc = "If false, do not return the DefaultInfo provider" ,
462
+ default = True ,
463
+ ),
464
+ "output_file_suffix" : attr .string (
465
+ doc = "If set, copy the output files to a new set having this suffix" ,
390
466
),
391
467
},
392
468
toolchains = ["@build_stack_rules_proto//toolchain:protoc" ],
0 commit comments