| from __future__ import with_statement |
| |
| import copy |
| import functools |
| import optparse |
| import os |
| import os.path |
| import re |
| import sys |
| |
| try: |
| from com.xhaus.jyson import JysonCodec as json # jython embedded in buck |
| except ImportError: |
| import json # python test case |
| |
| |
| # TODO(user): upgrade to a jython including os.relpath |
| def relpath(path, start=os.path.curdir): |
| """ |
| Return a relative filepath to path from the current directory or an optional start point. |
| """ |
| if not path: |
| raise ValueError("no path specified") |
| start_list = os.path.abspath(start).split(os.path.sep) |
| path_list = os.path.abspath(path).split(os.path.sep) |
| # Work out how much of the filepath is shared by start and path. |
| common = len(os.path.commonprefix([start_list, path_list])) |
| rel_list = [os.path.pardir] * (len(start_list) - common) + path_list[common:] |
| if not rel_list: |
| return os.path.curdir |
| return os.path.join(*rel_list) |
| |
| |
| # 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 |
| |
| |
| class LazyBuildEnvPartial: |
| """Pairs a function with a build environment in which it should be executed. |
| |
| Note that although both the function and build environment must be specified |
| via the constructor, the build environment may be reassigned after |
| construction. |
| |
| To call the function with its build environment, use the invoke() method of |
| this class, which will forward the arguments from invoke() to the underlying |
| function. |
| """ |
| |
| def __init__(self, func, default_build_env): |
| self.func = func |
| self.build_env = default_build_env |
| |
| def invoke(self, *args, **kwargs): |
| """Invokes the bound function injecting 'build_env' into **kwargs.""" |
| updated_kwargs = kwargs.copy() |
| updated_kwargs.update({'build_env': self.build_env}) |
| return self.func(*args, **updated_kwargs) |
| |
| |
| def make_build_file_symbol_table(build_env): |
| """Creates a symbol table with functions decorated by @provide_for_build.""" |
| symbol_table = {} |
| lazy_functions = [] |
| for func in BUILD_FUNCTIONS: |
| func_with_env = LazyBuildEnvPartial(func, build_env) |
| symbol_table[func.__name__] = func_with_env.invoke |
| lazy_functions.append(func_with_env) |
| return { |
| 'symbol_table': symbol_table, |
| 'lazy_functions': lazy_functions} |
| |
| |
| def update_lazy_functions(lazy_functions, build_env): |
| """Updates a list of LazyBuildEnvPartials with build_env.""" |
| for lazy_function in lazy_functions: |
| lazy_function.build_env = build_env |
| |
| |
| 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. |
| if 'name' not in rule: |
| raise ValueError('rules must contain the field \'name\'. Found %s.' % rule) |
| rule_name = rule['name'] |
| if rule_name in build_env['RULES']: |
| raise ValueError('Duplicate rule definition found. Found %s and %s' % |
| (rule, build_env['RULES'][rule_name])) |
| rule['buck.base_path'] = build_env['BASE'] |
| build_env['RULES'][rule_name] = 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) |
| |
| |
| def symlink_aware_walk(base): |
| """ Recursive symlink aware version of `os.walk`. |
| |
| Will not emit an entry for a directory it has previously visited. |
| """ |
| visited_dirs = set() |
| for entry in os.walk(base, topdown=True, followlinks=True): |
| (root, dirs, _files) = entry |
| realdirpath = os.path.realpath(root) |
| if realdirpath in visited_dirs: |
| dirs[:] = [] |
| continue |
| visited_dirs.add(realdirpath) |
| yield entry |
| raise StopIteration |
| |
| |
| @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 symlink_aware_walk(search_base): |
| if len(files) == 0: |
| continue |
| relative_root = 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=[], |
| export_deps=False, |
| source='6', |
| target='6', |
| proguard_config=None, |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'java_library', |
| 'name' : name, |
| 'srcs' : srcs, |
| 'resources' : resources, |
| 'export_deps' : export_deps, |
| 'source' : source, |
| 'target' : target, |
| 'proguard_config' : proguard_config, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def java_test( |
| name, |
| srcs=[], |
| labels=[], |
| resources=[], |
| source='6', |
| target='6', |
| vm_args=[], |
| source_under_test=[], |
| contacts=[], |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'java_test', |
| 'name' : name, |
| 'srcs' : srcs, |
| 'labels': labels, |
| 'resources' : resources, |
| 'source' : source, |
| 'target' : target, |
| 'vm_args' : vm_args, |
| 'source_under_test' : source_under_test, |
| 'contacts' : contacts, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def robolectric_test( |
| name, |
| srcs=[], |
| labels=[], |
| resources=[], |
| vm_args=[], |
| source_under_test=[], |
| contacts=[], |
| 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, |
| 'contacts' : contacts, |
| '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): |
| if res: |
| res_srcs = glob([res + '/**/*'], build_env=build_env) |
| else: |
| res_srcs = None |
| |
| if assets: |
| assets_srcs = glob([assets + '/**/*'], build_env=build_env) |
| else: |
| assets_srcs = None |
| |
| add_rule({ |
| 'type' : 'android_resource', |
| 'name' : name, |
| 'res' : res, |
| 'res_srcs' : res_srcs, |
| 'package' : package, |
| 'assets' : assets, |
| 'assets_srcs' : assets_srcs, |
| 'manifest' : manifest, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def prebuilt_native_library( |
| name, |
| native_libs=None, |
| is_asset=False, |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'prebuilt_native_library', |
| 'name' : name, |
| 'native_libs' : native_libs, |
| 'is_asset' : is_asset, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def android_binary( |
| name, |
| manifest, |
| target, |
| keystore, |
| package_type='debug', |
| no_dx=[], |
| use_split_dex=False, |
| use_linear_alloc_split_dex=False, |
| minimize_primary_dex_size=False, |
| dex_compression='jar', |
| use_android_proguard_config_with_optimizations=False, |
| proguard_config=None, |
| resource_compression=None, |
| primary_dex_substrings=None, |
| primary_dex_classes_file=None, |
| # By default, assume we have 5MB of linear alloc, |
| # 1MB of which is taken up by the framework, so that leaves 4MB. |
| linear_alloc_hard_limit=4 * 1024 * 1024, |
| resource_filter=None, |
| cpu_filters=[], |
| preprocess_java_classes_deps=[], |
| preprocess_java_classes_bash=None, |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'android_binary', |
| 'name' : name, |
| 'manifest' : manifest, |
| 'target' : target, |
| 'keystore' : keystore, |
| 'package_type' : package_type, |
| 'no_dx' : no_dx, |
| 'use_split_dex': use_split_dex, |
| 'use_linear_alloc_split_dex': use_linear_alloc_split_dex, |
| 'minimize_primary_dex_size': minimize_primary_dex_size, |
| 'dex_compression': dex_compression, |
| 'use_android_proguard_config_with_optimizations': |
| use_android_proguard_config_with_optimizations, |
| 'proguard_config' : proguard_config, |
| 'resource_compression' : resource_compression, |
| 'primary_dex_substrings' : primary_dex_substrings, |
| 'primary_dex_classes_file' : primary_dex_classes_file, |
| 'linear_alloc_hard_limit' : linear_alloc_hard_limit, |
| 'resource_filter' : resource_filter, |
| 'cpu_filters' : cpu_filters, |
| 'preprocess_java_classes_deps' : preprocess_java_classes_deps, |
| 'preprocess_java_classes_bash' : preprocess_java_classes_bash, |
| 'classpath_deps' : deps, |
| # Always include the keystore as a dep, as it should be built before this rule. |
| 'deps' : deps + [keystore] + preprocess_java_classes_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, |
| 'classpath_deps' : deps, |
| 'deps' : deps + [ apk ], |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def ndk_library( |
| name, |
| flags=None, |
| is_asset=False, |
| 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, |
| 'is_asset' : is_asset, |
| '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, |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'android_manifest', |
| 'name' : name, |
| 'skeleton' : skeleton, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def keystore( |
| name, |
| store, |
| properties, |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'keystore', |
| 'name' : name, |
| 'store' : store, |
| 'properties' : properties, |
| '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, |
| out, |
| cmd=None, |
| bash=None, |
| cmd_exe=None, |
| srcs=[], |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'genrule', |
| 'name' : name, |
| 'srcs' : srcs, |
| 'cmd' : cmd, |
| 'bash' : bash, |
| 'cmd_exe' : cmd_exe, |
| 'out' : out, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def apk_genrule(name, |
| srcs, |
| apk, |
| out, |
| cmd=None, |
| bash=None, |
| cmd_exe=None, |
| 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, |
| 'bash' : bash, |
| 'cmd_exe' : cmd_exe, |
| 'deps' : deps, |
| 'visibility' : visibility, |
| }, build_env) |
| |
| |
| @provide_for_build |
| def sh_binary( |
| name, |
| main, |
| resources=[], |
| deps=[], |
| visibility=[], |
| build_env=None): |
| add_rule({ |
| 'type' : 'sh_binary', |
| 'name' : name, |
| 'main' : main, |
| 'resources' : resources, |
| '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 "%s" must begin with //' % name) |
| relative_path = name[2:] |
| include_file = os.path.join(build_env['PROJECT_ROOT'], relative_path) |
| build_env['INCLUDES'].append(include_file) |
| 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'] |
| |
| |
| @provide_for_build |
| def add_deps(name, deps=[], build_env=None): |
| if name not in build_env['RULES']: |
| raise ValueError('Invoked \'add_deps\' on non-existent rule %s.' % name) |
| |
| rule = build_env['RULES'][name] |
| if 'deps' not in rule: |
| raise ValueError('Invoked \'add_deps\' on rule %s that has no \'deps\' field' % name) |
| rule['deps'] = rule['deps'] + deps |
| |
| class BuildFileProcessor: |
| def __init__(self, project_root, includes, server): |
| self.project_root = project_root |
| self.includes = includes |
| self.server = server |
| self.len_suffix = -len('/' + BUILD_RULES_FILE_NAME) |
| |
| # Create root_build_env |
| build_env = {} |
| build_env['PROJECT_ROOT'] = self.project_root |
| build_symbols = make_build_file_symbol_table(build_env) |
| build_env['BUILD_FILE_SYMBOL_TABLE'] = build_symbols['symbol_table'] |
| build_env['LAZY_FUNCTIONS'] = build_symbols['lazy_functions'] |
| build_env['INCLUDES'] = [] |
| |
| # If there are any default includes, evaluate those first to populate the |
| # build_env. |
| for include in self.includes: |
| include_defs(include, build_env) |
| |
| self.root_build_env = build_env |
| |
| def process(self, build_file): |
| """Process an individual build file and output JSON of result to stdout.""" |
| |
| # 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 = copy.copy(self.root_build_env) |
| relative_path_to_build_file = relpath(build_file, self.project_root).replace('\\', '/') |
| build_env['BASE'] = relative_path_to_build_file[:self.len_suffix] |
| build_env['BUILD_FILE_DIRECTORY'] = os.path.dirname(build_file) |
| build_env['RULES'] = {} |
| |
| # Copy BUILD_FILE_SYMBOL_TABLE over. This is the only dict that we need |
| # a sperate copy of since update_lazy_functions will modify it. |
| build_env['BUILD_FILE_SYMBOL_TABLE'] = copy.copy( |
| self.root_build_env['BUILD_FILE_SYMBOL_TABLE']) |
| |
| # Re-apply build_env to the rules added in this file with |
| # @provide_for_build. |
| update_lazy_functions(build_env['LAZY_FUNCTIONS'], build_env) |
| execfile(os.path.join(self.project_root, build_file), |
| build_env['BUILD_FILE_SYMBOL_TABLE']) |
| |
| values = build_env['RULES'].values() |
| values.append({"__includes": [build_file] + build_env['INCLUDES']}) |
| if self.server: |
| print json.dumps(values) |
| else: |
| for value in values: |
| print json.dumps(value) |
| |
| # 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 = optparse.OptionParser() |
| parser.add_option('--project_root', action='store', type='string', dest='project_root') |
| parser.add_option('--include', action='append', dest='include') |
| parser.add_option('--ignore_path', action='append', dest='ignore_paths') |
| parser.add_option('--server', action='store_true', dest='server', |
| help='Invoke as a server to parse individual BUCK files on demand.') |
| (options, args) = parser.parse_args() |
| |
| # Even though project_root is absolute path, it may not be concise. For example, it might be |
| # like "C:\project\.\rule". We normalize it in order to make it consistent with ignore_paths. |
| project_root = os.path.abspath(options.project_root) |
| |
| build_files = [] |
| if args: |
| # The user has specified which build files to parse. |
| build_files = args |
| elif not options.server: |
| # 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. |
| # options.ignore_paths may contain /, which is needed to be normalized in order to do string |
| # pattern matching. |
| ignore_paths = [os.path.abspath(os.path.join(project_root, d)) |
| for d in options.ignore_paths or []] |
| build_files = [] |
| for dirpath, dirnames, filenames in symlink_aware_walk(project_root): |
| # Do not walk directories that contain generated/non-source files. |
| # All modifications to dirnames must occur in-place. |
| dirnames[:] = [d for d in dirnames if not (os.path.join(dirpath, d) in ignore_paths)] |
| |
| if BUILD_RULES_FILE_NAME in filenames: |
| build_file = os.path.join(dirpath, BUILD_RULES_FILE_NAME) |
| build_files.append(build_file) |
| |
| buildFileProcessor = BuildFileProcessor(project_root, options.include or [], options.server) |
| |
| for build_file in build_files: |
| buildFileProcessor.process(build_file) |
| |
| if options.server: |
| # Apparently for ... in sys.stdin doesn't work with Jython when a custom stdin is |
| # provided by the caller in Java-land. Claims that sys.stdin is a filereader which doesn't |
| # offer an iterator. |
| for build_file in iter(sys.stdin.readline, ''): |
| buildFileProcessor.process(build_file.rstrip()) |
| |
| |
| if __name__ == '__main__': |
| main() |