This repository has been archived by the owner on Nov 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathbuck.py
650 lines (561 loc) · 16.6 KB
/
buck.py
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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
import argparse
import functools
import json
import re
import os
import os.path
import posixpath
# When BUILD files are executed, the functions in this file tagged with
# @provide_for_build will be provided in the BUILD file's local symbol table.
#
# When these functions are called from a BUILD file, they will be passed
# a keyword parameter, build_env, which is a dictionary with information about
# the environment of the BUILD file which is currently being processed.
# It contains the following keys:
#
# "BUILD_FILE_DIRECTORY" - The directory containing the BUILD file.
#
# "BASE" - The base path of the BUILD file.
#
# "PROJECT_ROOT" - An absolute path to the project root.
#
# "BUILD_FILE_SYMBOL_TABLE" - The global symbol table of the BUILD file.
BUILD_FUNCTIONS = []
BUILD_RULES_FILE_NAME = 'BUCK'
def provide_for_build(func):
BUILD_FUNCTIONS.append(func)
return func
def make_build_file_symbol_table(build_env):
"""Creates a symbol table with all the functions decorated by
@provide_for_build.
"""
symbol_table = {}
for func in BUILD_FUNCTIONS:
func_with_env = functools.partial(func, build_env=build_env)
symbol_table[func.__name__] = func_with_env
return symbol_table
def add_rule(rule, build_env):
# Include the base path of the BUILD file so the reader consuming this JSON will know which BUILD
# file the rule came from.
rule['buck_base_path'] = build_env['BASE']
print json.dumps(rule)
def glob_pattern_to_regex_string(pattern):
# Replace rules for glob pattern (roughly):
# . => \\.
# **/* => (.*)
# * => [^/]*
pattern = re.sub(r'\.', '\\.', pattern)
pattern = pattern.replace('**/*', '(.*)')
# This handles the case when there is a character preceding the asterisk.
pattern = re.sub(r'([^\.])\*', '\\1[^/]*', pattern)
# This handles the case when the asterisk is the first character.
pattern = re.sub(r'^\*', '[^/]*', pattern)
pattern = '^' + pattern + '$'
return pattern
def pattern_to_regex(pattern):
pattern = glob_pattern_to_regex_string(pattern)
return re.compile(pattern)
@provide_for_build
def glob(includes, excludes=[], build_env=None):
search_base = build_env['BUILD_FILE_DIRECTORY']
# Ensure the user passes lists of strings rather than just a string.
assert not isinstance(includes, basestring), \
"The first argument to glob() must be a list of strings."
assert not isinstance(excludes, basestring), \
"The excludes argument must be a list of strings."
inclusions = [pattern_to_regex(p) for p in includes]
exclusions = [pattern_to_regex(p) for p in excludes]
def passes_glob_filter(path):
for exclusion in exclusions:
if exclusion.match(path):
return False
for inclusion in inclusions:
if inclusion.match(path):
return True
return False
# Return the filtered set of includes as an array.
paths = []
def check_path(path):
if passes_glob_filter(path):
paths.append(path)
for root, dirs, files in os.walk(search_base):
if len(files) == 0:
continue
relative_root = os.path.relpath(root, search_base)
# The regexes generated by glob_pattern_to_regex_string don't
# expect a leading './'
if relative_root == '.':
for file_path in files:
check_path(file_path)
else:
relative_root += '/'
for file_path in files:
relative_path = relative_root + file_path
check_path(relative_path)
return paths
@provide_for_build
def genfile(src, build_env=None):
return 'BUCKGEN:' + src
@provide_for_build
def java_library(
name,
srcs=[],
resources=[],
proguard_config=None,
deps=[],
visibility=[],
source='6',
target='6',
build_env=None):
add_rule({
'type' : 'java_library',
'name' : name,
'srcs' : srcs,
'resources' : resources,
'proguard_config' : proguard_config,
'source' : source,
'target' : target,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def java_test(
name,
srcs=[],
labels=[],
resources=[],
vm_args=None,
source_under_test=[],
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'java_test',
'name' : name,
'srcs' : srcs,
'labels': labels,
'resources' : resources,
'vm_args' : vm_args,
'source_under_test' : source_under_test,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def robolectric_test(
name,
srcs=[],
labels=[],
resources=[],
vm_args=None,
source_under_test=[],
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'robolectric_test',
'name' : name,
'srcs' : srcs,
'labels': labels,
'resources' : resources,
'vm_args' : vm_args,
'source_under_test' : source_under_test,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def java_binary(
name,
main_class=None,
manifest_file=None,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'java_binary',
'name' : name,
'manifest_file': manifest_file,
'main_class' : main_class,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def prebuilt_jar(
name,
binary_jar,
source_jar=None,
javadoc_url=None,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type': 'prebuilt_jar',
'name': name,
'binary_jar': binary_jar,
'source_jar': source_jar,
'javadoc_url': javadoc_url,
'deps': deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def android_library(
name,
srcs=[],
resources=[],
manifest=None,
proguard_config=None,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'android_library',
'name' : name,
'srcs' : srcs,
'resources' : resources,
'manifest' : manifest,
'proguard_config' : proguard_config,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def android_resource(
name,
res=None,
package=None,
assets=None,
manifest=None,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'android_resource',
'name' : name,
'res' : res,
'package' : package,
'assets' : assets,
'manifest' : manifest,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def prebuilt_native_library(
name,
native_libs=None,
visibility=[],
build_env=None):
add_rule({
'type' : 'prebuilt_native_library',
'name' : name,
'native_libs' : native_libs,
'visibility' : visibility,
}, build_env)
@provide_for_build
def android_binary(
name,
manifest,
target,
keystore_properties,
package_type='debug',
no_dx=[],
use_split_dex=False,
minimize_primary_dex_size=False,
use_android_proguard_config_with_optimizations=False,
proguard_config=None,
compress_resources=False,
primary_dex_substrings=None,
resource_filter=None,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'android_binary',
'name' : name,
'manifest' : manifest,
'target' : target,
'keystore_properties' : keystore_properties,
'package_type' : package_type,
'no_dx' : no_dx,
'use_split_dex': use_split_dex,
'minimize_primary_dex_size': minimize_primary_dex_size,
'use_android_proguard_config_with_optimizations':
use_android_proguard_config_with_optimizations,
'proguard_config' : proguard_config,
'compress_resources' : compress_resources,
'primary_dex_substrings' : primary_dex_substrings,
'resource_filter' : resource_filter,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def android_instrumentation_apk(
name,
manifest,
apk,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'android_instrumentation_apk',
'name' : name,
'manifest' : manifest,
'apk' : apk,
'deps' : deps + [ apk ],
'visibility' : visibility,
}, build_env)
@provide_for_build
def ndk_library(
name,
flags=None,
deps=[],
visibility=[],
build_env=None):
EXTENSIONS = ("mk", "h", "hpp", "c", "cpp", "cc", "cxx")
srcs = glob(["**/*.%s" % ext for ext in EXTENSIONS], build_env=build_env)
add_rule({
'type' : 'ndk_library',
'name' : name,
'srcs' : srcs,
'flags' : flags,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def python_library(
name,
srcs=[],
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'python_library',
'name' : name,
'srcs' : srcs,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def python_binary(
name,
main=None,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'python_binary',
'name' : name,
'main' : main,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def android_manifest(
name,
skeleton,
no_dx=[],
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'android_manifest',
'name' : name,
'no_dx' : no_dx,
'skeleton' : skeleton,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def gen_aidl(name, aidl, import_path, deps=[], visibility=[], build_env=None):
add_rule({
'type' : 'gen_aidl',
'name' : name,
'aidl' : aidl,
'import_path' : import_path,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def gen_parcelable(
name,
srcs,
deps=[],
visibility=[],
build_env=None):
add_rule({
'type' : 'gen_parcelable',
'name' : name,
'srcs' : srcs,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def genrule(name, srcs, cmd, out, deps=[], visibility=[], build_env=None):
add_rule({
'type' : 'genrule',
'name' : name,
'srcs' : srcs,
'cmd' : cmd,
'out' : out,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def apk_genrule(name, srcs, apk, cmd, out, deps=[], visibility=[], build_env=None):
# Always include the apk as a dep, as it should be built before this rule.
deps = deps + [apk]
add_rule({
'type' : 'apk_genrule',
'name' : name,
'srcs' : srcs,
'apk': apk,
'cmd' : cmd,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def sh_test(name, test, labels=[], deps=[], visibility=[], build_env=None):
add_rule({
'type' : 'sh_test',
'name' : name,
'test' : test,
'labels' : labels,
'deps' : deps,
'visibility' : visibility,
}, build_env)
@provide_for_build
def export_file(name, src=None, out=None, visibility=[], build_env=None):
add_rule({
'type' : 'export_file',
'name' : name,
'src' : src,
'out' : out,
'visibility': visibility,
}, build_env)
@provide_for_build
def include_defs(name, build_env=None):
"""Loads a file in the context of the current build file.
Name must begin with "//" and references a file relative to the project root.
An example is the build file //first-party/orca/orcaapp/BUILD contains
include_defs('//BUILD_DEFS')
which loads a list called NO_DX which can then be used in the build file.
"""
if name[:2] != '//':
raise ValueError('include_defs argument must begin with //')
relative_path = name[2:]
include_file = os.path.join(build_env['PROJECT_ROOT'], relative_path)
execfile(include_file, build_env['BUILD_FILE_SYMBOL_TABLE'])
@provide_for_build
def project_config(
src_target=None,
src_roots=[],
test_target=None,
test_roots=[],
is_intellij_plugin=False,
build_env=None):
deps = []
if src_target:
deps.append(src_target)
if test_target:
deps.append(test_target)
add_rule({
'type' : 'project_config',
'name' : 'project_config',
'src_target' : src_target,
'src_roots' : src_roots,
'test_target' : test_target,
'test_roots' : test_roots,
'is_intellij_plugin': is_intellij_plugin,
'deps' : deps,
'visibility' : [],
}, build_env)
@provide_for_build
def get_base_path(build_env=None):
"""Get the base path to the build file that was initially evaluated.
This function is intended to be used from within a build defs file that
likely contains macros that could be called from any build file.
Such macros may need to know the base path of the file in which they
are defining new build rules.
Returns: a string, such as "java/com/facebook". Note there is no
trailing slash. The return value will be "" if called from
the build file in the root of the project.
"""
return build_env['BASE']
def parse_git_ignore(gitignore_data):
"""Parse the patterns stored in a .gitignore file, returning only top-level
directories found in it.
Returns: a list of patterns parsed from the file.
"""
lines = gitignore_data
dirs = []
for line in lines:
line = line.strip()
if line.startswith('/') and line.endswith('/'):
# line.split('/x/') results in an array with 3 elements:
# ['', 'x', ''], so detect paths like this based on this condition.
path_elements = line.split('/')
if len(path_elements) == 3:
dirs.append(path_elements[1])
return dirs
# Inexplicably, this script appears to run faster when the arguments passed into it are absolute
# paths. However, we want the "buck_base_path" property of each rule to be printed out to be the
# base path of the build target that identifies the rule. That means that when parsing a BUILD file,
# we must know its path relative to the root of the project to produce the base path.
#
# To that end, the first argument to this script must be an absolute path to the project root.
# It must be followed by one or more absolute paths to BUILD files under the project root.
# If no paths to BUILD files are specified, then it will traverse the project root for BUILD files,
# excluding directories of generated files produced by Buck.
#
# All of the build rules that are parsed from the BUILD files will be printed to stdout by a JSON
# parser. That means that printing out other information for debugging purposes will likely break
# the JSON parsing, so be careful!
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--project_root')
parser.add_argument('--include', action='append')
parser.add_argument('build_files', nargs='*')
args = parser.parse_args()
project_root = args.project_root
len_suffix = -len('/' + BUILD_RULES_FILE_NAME)
build_files = None
if args.build_files:
# The user has specified which build files to parse.
build_files = args.build_files
else:
# Find all of the build files in the project root. Symlinks will not be traversed.
# Search must be done top-down so that directory filtering works as desired.
build_files = []
for dirpath, dirnames, filenames in os.walk(project_root, topdown=True, followlinks=False):
# Do not walk directories that contain generated/non-source files.
if dirpath == project_root:
# TODO(user): do a better job parsing gitignore and matching patterns therein.
gitignore_dirs = []
try:
with open('.gitignore') as f:
gitignore_data = f.readlines()
f.close()
gitignore_dirs = parse_git_ignore(gitignore_data)
except:
# Ignore failure.
pass
excluded = ['.git'] + gitignore_dirs
# All modifications to dirnames must occur in-place.
dirnames[:] = [d for d in dirnames if not (d in excluded)]
if BUILD_RULES_FILE_NAME in filenames:
build_file = os.path.join(dirpath, BUILD_RULES_FILE_NAME)
build_files.append(build_file)
for build_file in build_files:
# Reset build_env for each build file so that the variables declared in the build file
# or the files in includes through include_defs() don't pollute the namespace for
# subsequent build files.
build_env = {}
relative_path_to_build_file = os.path.relpath(build_file, project_root)
build_env['BASE'] = relative_path_to_build_file[:len_suffix]
build_env['BUILD_FILE_DIRECTORY'] = os.path.dirname(build_file)
build_env['PROJECT_ROOT'] = project_root
build_env['BUILD_FILE_SYMBOL_TABLE'] = make_build_file_symbol_table(build_env)
# If there are any default includes, evaluate those first to populate the build_env.
includes = args.include or []
for include in includes:
include_defs(include, build_env)
execfile(os.path.join(project_root, build_file), build_env['BUILD_FILE_SYMBOL_TABLE'])
if __name__ == '__main__':
main()