1#!/usr/bin/env python 2# Copyright (c) Facebook, Inc. and its affiliates. 3from __future__ import absolute_import 4from __future__ import division 5from __future__ import print_function 6from __future__ import unicode_literals 7 8""" 9shell_builder.py allows running the fbcode_builder logic 10on the host rather than in a container. 11 12It emits a bash script with set -exo pipefail configured such that 13any failing step will cause the script to exit with failure. 14 15== How to run it? == 16 17cd build 18python fbcode_builder/shell_builder.py > ~/run.sh 19bash ~/run.sh 20""" 21 22import distutils.spawn 23import os 24 25from fbcode_builder import FBCodeBuilder 26from shell_quoting import raw_shell, shell_comment, shell_join, ShellQuoted 27from utils import recursively_flatten_list 28 29 30class ShellFBCodeBuilder(FBCodeBuilder): 31 def _render_impl(self, steps): 32 return raw_shell(shell_join("\n", recursively_flatten_list(steps))) 33 34 def set_env(self, key, value): 35 return ShellQuoted("export {key}={val}").format(key=key, val=value) 36 37 def workdir(self, dir): 38 return [ 39 ShellQuoted("mkdir -p {d} && cd {d}").format(d=dir), 40 ] 41 42 def run(self, shell_cmd): 43 return ShellQuoted("{cmd}").format(cmd=shell_cmd) 44 45 def step(self, name, actions): 46 assert "\n" not in name, "Name {0} would span > 1 line".format(name) 47 b = ShellQuoted("") 48 return [ShellQuoted("### {0} ###".format(name)), b] + actions + [b] 49 50 def setup(self): 51 steps = ( 52 [ 53 ShellQuoted("set -exo pipefail"), 54 ] 55 + self.create_python_venv() 56 + self.python_venv() 57 ) 58 if self.has_option("ccache_dir"): 59 ccache_dir = self.option("ccache_dir") 60 steps += [ 61 ShellQuoted( 62 # Set CCACHE_DIR before the `ccache` invocations below. 63 "export CCACHE_DIR={ccache_dir} " 64 'CC="ccache ${{CC:-gcc}}" CXX="ccache ${{CXX:-g++}}"' 65 ).format(ccache_dir=ccache_dir) 66 ] 67 return steps 68 69 def comment(self, comment): 70 return shell_comment(comment) 71 72 def copy_local_repo(self, dir, dest_name): 73 return [ 74 ShellQuoted("cp -r {dir} {dest_name}").format(dir=dir, dest_name=dest_name), 75 ] 76 77 78def find_project_root(): 79 here = os.path.dirname(os.path.realpath(__file__)) 80 maybe_root = os.path.dirname(os.path.dirname(here)) 81 if os.path.isdir(os.path.join(maybe_root, ".git")): 82 return maybe_root 83 raise RuntimeError( 84 "I expected shell_builder.py to be in the " 85 "build/fbcode_builder subdir of a git repo" 86 ) 87 88 89def persistent_temp_dir(repo_root): 90 escaped = repo_root.replace("/", "sZs").replace("\\", "sZs").replace(":", "") 91 return os.path.join(os.path.expandvars("$HOME"), ".fbcode_builder-" + escaped) 92 93 94if __name__ == "__main__": 95 from utils import read_fbcode_builder_config, build_fbcode_builder_config 96 97 repo_root = find_project_root() 98 temp = persistent_temp_dir(repo_root) 99 100 config = read_fbcode_builder_config("fbcode_builder_config.py") 101 builder = ShellFBCodeBuilder(projects_dir=temp) 102 103 if distutils.spawn.find_executable("ccache"): 104 builder.add_option( 105 "ccache_dir", os.environ.get("CCACHE_DIR", os.path.join(temp, ".ccache")) 106 ) 107 builder.add_option("prefix", os.path.join(temp, "installed")) 108 builder.add_option("make_parallelism", 4) 109 builder.add_option( 110 "{project}:local_repo_dir".format(project=config["github_project"]), repo_root 111 ) 112 make_steps = build_fbcode_builder_config(config) 113 steps = make_steps(builder) 114 print(builder.render(steps)) 115