Commit 40803d69 authored by Zsolt Dollenstein's avatar Zsolt Dollenstein Committed by Facebook GitHub Bot

Opt in opensource/fbcode_builder to pyfmt

Reviewed By: zertosh

Differential Revision: D29612107

fbshipit-source-id: ac450058134e23a3831db35d2e49c80eb8cde36a
parent c2ea3761
...@@ -485,7 +485,7 @@ class Loader(object): ...@@ -485,7 +485,7 @@ class Loader(object):
return loader.suiteClass(suites) return loader.suiteClass(suites)
_COVERAGE_INI = '''\ _COVERAGE_INI = """\
[report] [report]
exclude_lines = exclude_lines =
pragma: no cover pragma: no cover
...@@ -495,7 +495,7 @@ exclude_lines = ...@@ -495,7 +495,7 @@ exclude_lines =
pragma:.*no${PY_IMPL}${PY_MAJOR} pragma:.*no${PY_IMPL}${PY_MAJOR}
pragma:.*nopy${PY_MAJOR} pragma:.*nopy${PY_MAJOR}
pragma:.*nopy${PY_MAJOR}${PY_MINOR} pragma:.*nopy${PY_MAJOR}${PY_MINOR}
''' """
class MainProgram(object): class MainProgram(object):
...@@ -734,7 +734,7 @@ class MainProgram(object): ...@@ -734,7 +734,7 @@ class MainProgram(object):
if not self.options.collect_coverage: if not self.options.collect_coverage:
return return
with tempfile.NamedTemporaryFile('w', delete=False) as coverage_ini: with tempfile.NamedTemporaryFile("w", delete=False) as coverage_ini:
coverage_ini.write(_COVERAGE_INI) coverage_ini.write(_COVERAGE_INI)
self._coverage_ini_path = coverage_ini.name self._coverage_ini_path = coverage_ini.name
......
...@@ -124,7 +124,7 @@ def populate_install_tree(inst_dir, path_map): ...@@ -124,7 +124,7 @@ def populate_install_tree(inst_dir, path_map):
def build_zipapp(args, path_map): def build_zipapp(args, path_map):
""" Create a self executing python binary using Python 3's built-in """Create a self executing python binary using Python 3's built-in
zipapp module. zipapp module.
This type of Python binary is relatively simple, as zipapp is part of the This type of Python binary is relatively simple, as zipapp is part of the
...@@ -165,7 +165,7 @@ if __name__ == "__main__": ...@@ -165,7 +165,7 @@ if __name__ == "__main__":
def build_install_dir(args, path_map): def build_install_dir(args, path_map):
""" Create a directory that contains all of the sources, with a __main__ """Create a directory that contains all of the sources, with a __main__
module to run the program. module to run the program.
""" """
# Populate a temporary directory first, then rename to the destination # Populate a temporary directory first, then rename to the destination
...@@ -188,7 +188,7 @@ def ensure_directory(path): ...@@ -188,7 +188,7 @@ def ensure_directory(path):
def install_library(args, path_map): def install_library(args, path_map):
""" Create an installation directory a python library. """ """Create an installation directory a python library."""
out_dir = args.output out_dir = args.output
out_manifest = args.output + ".manifest" out_manifest = args.output + ".manifest"
......
...@@ -4,7 +4,8 @@ from __future__ import absolute_import ...@@ -4,7 +4,8 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
'''
"""
Extends FBCodeBuilder to produce Docker context directories. Extends FBCodeBuilder to produce Docker context directories.
...@@ -15,26 +16,23 @@ caching, you will want to: ...@@ -15,26 +16,23 @@ caching, you will want to:
that change the least often, and that change the least often, and
- Put the steps that you are debugging towards the very end. - Put the steps that you are debugging towards the very end.
''' """
import logging import logging
import os import os
import shutil import shutil
import tempfile import tempfile
from fbcode_builder import FBCodeBuilder from fbcode_builder import FBCodeBuilder
from shell_quoting import ( from shell_quoting import raw_shell, shell_comment, shell_join, ShellQuoted, path_join
raw_shell, shell_comment, shell_join, ShellQuoted, path_join
)
from utils import recursively_flatten_list, run_command from utils import recursively_flatten_list, run_command
class DockerFBCodeBuilder(FBCodeBuilder): class DockerFBCodeBuilder(FBCodeBuilder):
def _user(self): def _user(self):
return self.option('user', 'root') return self.option("user", "root")
def _change_user(self): def _change_user(self):
return ShellQuoted('USER {u}').format(u=self._user()) return ShellQuoted("USER {u}").format(u=self._user())
def setup(self): def setup(self):
# Please add RPM-based OSes here as appropriate. # Please add RPM-based OSes here as appropriate.
...@@ -63,17 +61,18 @@ class DockerFBCodeBuilder(FBCodeBuilder): ...@@ -63,17 +61,18 @@ class DockerFBCodeBuilder(FBCodeBuilder):
# it is present when the resulting container is run add to PATH # it is present when the resulting container is run add to PATH
actions = [] actions = []
if self.option("PYTHON_VENV", "OFF") == "ON": if self.option("PYTHON_VENV", "OFF") == "ON":
actions = ShellQuoted('ENV PATH={p}:$PATH').format( actions = ShellQuoted("ENV PATH={p}:$PATH").format(
p=path_join(self.option('prefix'), "venv", "bin")) p=path_join(self.option("prefix"), "venv", "bin")
return(actions) )
return actions
def step(self, name, actions): def step(self, name, actions):
assert '\n' not in name, 'Name {0} would span > 1 line'.format(name) assert "\n" not in name, "Name {0} would span > 1 line".format(name)
b = ShellQuoted('') b = ShellQuoted("")
return [ShellQuoted('### {0} ###'.format(name)), b] + actions + [b] return [ShellQuoted("### {0} ###".format(name)), b] + actions + [b]
def run(self, shell_cmd): def run(self, shell_cmd):
return ShellQuoted('RUN {cmd}').format(cmd=shell_cmd) return ShellQuoted("RUN {cmd}").format(cmd=shell_cmd)
def set_env(self, key, value): def set_env(self, key, value):
return ShellQuoted("ENV {key}={val}").format(key=key, val=value) return ShellQuoted("ENV {key}={val}").format(key=key, val=value)
...@@ -84,12 +83,12 @@ class DockerFBCodeBuilder(FBCodeBuilder): ...@@ -84,12 +83,12 @@ class DockerFBCodeBuilder(FBCodeBuilder):
# by root:root -- the explicit `mkdir` works around the bug: # by root:root -- the explicit `mkdir` works around the bug:
# USER nobody # USER nobody
# WORKDIR build # WORKDIR build
ShellQuoted('USER root'), ShellQuoted("USER root"),
ShellQuoted('RUN mkdir -p {d} && chown {u} {d}').format( ShellQuoted("RUN mkdir -p {d} && chown {u} {d}").format(
d=dir, u=self._user() d=dir, u=self._user()
), ),
self._change_user(), self._change_user(),
ShellQuoted('WORKDIR {dir}').format(dir=dir), ShellQuoted("WORKDIR {dir}").format(dir=dir),
] ]
def comment(self, comment): def comment(self, comment):
...@@ -99,60 +98,58 @@ class DockerFBCodeBuilder(FBCodeBuilder): ...@@ -99,60 +98,58 @@ class DockerFBCodeBuilder(FBCodeBuilder):
def copy_local_repo(self, repo_dir, dest_name): def copy_local_repo(self, repo_dir, dest_name):
fd, archive_path = tempfile.mkstemp( fd, archive_path = tempfile.mkstemp(
prefix='local_repo_{0}_'.format(dest_name), prefix="local_repo_{0}_".format(dest_name),
suffix='.tgz', suffix=".tgz",
dir=os.path.abspath(self.option('docker_context_dir')), dir=os.path.abspath(self.option("docker_context_dir")),
) )
os.close(fd) os.close(fd)
run_command('tar', 'czf', archive_path, '.', cwd=repo_dir) run_command("tar", "czf", archive_path, ".", cwd=repo_dir)
return [ return [
ShellQuoted('ADD {archive} {dest_name}').format( ShellQuoted("ADD {archive} {dest_name}").format(
archive=os.path.basename(archive_path), dest_name=dest_name archive=os.path.basename(archive_path), dest_name=dest_name
), ),
# Docker permissions make very little sense... see also workdir() # Docker permissions make very little sense... see also workdir()
ShellQuoted('USER root'), ShellQuoted("USER root"),
ShellQuoted('RUN chown -R {u} {d}').format( ShellQuoted("RUN chown -R {u} {d}").format(d=dest_name, u=self._user()),
d=dest_name, u=self._user()
),
self._change_user(), self._change_user(),
] ]
def _render_impl(self, steps): def _render_impl(self, steps):
return raw_shell(shell_join('\n', recursively_flatten_list(steps))) return raw_shell(shell_join("\n", recursively_flatten_list(steps)))
def debian_ccache_setup_steps(self): def debian_ccache_setup_steps(self):
source_ccache_tgz = self.option('ccache_tgz', '') source_ccache_tgz = self.option("ccache_tgz", "")
if not source_ccache_tgz: if not source_ccache_tgz:
logging.info('Docker ccache not enabled') logging.info("Docker ccache not enabled")
return [] return []
dest_ccache_tgz = os.path.join( dest_ccache_tgz = os.path.join(self.option("docker_context_dir"), "ccache.tgz")
self.option('docker_context_dir'), 'ccache.tgz'
)
try: try:
try: try:
os.link(source_ccache_tgz, dest_ccache_tgz) os.link(source_ccache_tgz, dest_ccache_tgz)
except OSError: except OSError:
logging.exception( logging.exception(
'Hard-linking {s} to {d} failed, falling back to copy' "Hard-linking {s} to {d} failed, falling back to copy".format(
.format(s=source_ccache_tgz, d=dest_ccache_tgz) s=source_ccache_tgz, d=dest_ccache_tgz
)
) )
shutil.copyfile(source_ccache_tgz, dest_ccache_tgz) shutil.copyfile(source_ccache_tgz, dest_ccache_tgz)
except Exception: except Exception:
logging.exception( logging.exception(
'Failed to copy or link {s} to {d}, aborting' "Failed to copy or link {s} to {d}, aborting".format(
.format(s=source_ccache_tgz, d=dest_ccache_tgz) s=source_ccache_tgz, d=dest_ccache_tgz
)
) )
raise raise
return [ return [
# Separate layer so that in development we avoid re-downloads. # Separate layer so that in development we avoid re-downloads.
self.run(ShellQuoted('apt-get install -yq ccache')), self.run(ShellQuoted("apt-get install -yq ccache")),
ShellQuoted('ADD ccache.tgz /'), ShellQuoted("ADD ccache.tgz /"),
ShellQuoted( ShellQuoted(
# Set CCACHE_DIR before the `ccache` invocations below. # Set CCACHE_DIR before the `ccache` invocations below.
'ENV CCACHE_DIR=/ccache ' "ENV CCACHE_DIR=/ccache "
# No clang support for now, so it's easiest to hardcode gcc. # No clang support for now, so it's easiest to hardcode gcc.
'CC="ccache gcc" CXX="ccache g++" ' 'CC="ccache gcc" CXX="ccache g++" '
# Always log for ease of debugging. For real FB projects, # Always log for ease of debugging. For real FB projects,
...@@ -166,9 +163,10 @@ class DockerFBCodeBuilder(FBCodeBuilder): ...@@ -166,9 +163,10 @@ class DockerFBCodeBuilder(FBCodeBuilder):
# #
# apt-get install sharutils # apt-get install sharutils
# bzip2 -9 < /tmp/ccache.log | uuencode -m ccache.log.bz2 # bzip2 -9 < /tmp/ccache.log | uuencode -m ccache.log.bz2
'CCACHE_LOGFILE=/tmp/ccache.log' "CCACHE_LOGFILE=/tmp/ccache.log"
), ),
self.run(ShellQuoted( self.run(
ShellQuoted(
# Future: Skipping this part made this Docker step instant, # Future: Skipping this part made this Docker step instant,
# saving ~1min of build time. It's unclear if it is the # saving ~1min of build time. It's unclear if it is the
# chown or the du, but probably the chown -- since a large # chown or the du, but probably the chown -- since a large
...@@ -176,16 +174,17 @@ class DockerFBCodeBuilder(FBCodeBuilder): ...@@ -176,16 +174,17 @@ class DockerFBCodeBuilder(FBCodeBuilder):
# #
# ccache.tgz may be empty, or may have the wrong # ccache.tgz may be empty, or may have the wrong
# permissions. # permissions.
'mkdir -p /ccache && time chown -R nobody /ccache && ' "mkdir -p /ccache && time chown -R nobody /ccache && "
'time du -sh /ccache && ' "time du -sh /ccache && "
# Reset stats so `docker_build_with_ccache.sh` can print # Reset stats so `docker_build_with_ccache.sh` can print
# useful values at the end of the run. # useful values at the end of the run.
'echo === Prev run stats === && ccache -s && ccache -z && ' "echo === Prev run stats === && ccache -s && ccache -z && "
# Record the current time to let travis_build.sh figure out # Record the current time to let travis_build.sh figure out
# the number of bytes in the cache that are actually used -- # the number of bytes in the cache that are actually used --
# this is crucial for tuning the maximum cache size. # this is crucial for tuning the maximum cache size.
'date +%s > /FBCODE_BUILDER_CCACHE_START_TIME && ' "date +%s > /FBCODE_BUILDER_CCACHE_START_TIME && "
# The build running as `nobody` should be able to write here # The build running as `nobody` should be able to write here
'chown nobody /tmp/ccache.log' "chown nobody /tmp/ccache.log"
)), )
),
] ]
This diff is collapsed.
...@@ -4,12 +4,13 @@ from __future__ import absolute_import ...@@ -4,12 +4,13 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
'Demo config, so that `make_docker_context.py --help` works in this directory.'
"Demo config, so that `make_docker_context.py --help` works in this directory."
config = { config = {
'fbcode_builder_spec': lambda _builder: { "fbcode_builder_spec": lambda _builder: {
'depends_on': [], "depends_on": [],
'steps': [], "steps": [],
}, },
'github_project': 'demo/project', "github_project": "demo/project",
} }
...@@ -4,7 +4,8 @@ from __future__ import absolute_import ...@@ -4,7 +4,8 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
'''
"""
Reads `fbcode_builder_config.py` from the current directory, and prepares a Reads `fbcode_builder_config.py` from the current directory, and prepares a
Docker context directory to build this project. Prints to stdout the path Docker context directory to build this project. Prints to stdout the path
to the context directory. to the context directory.
...@@ -14,7 +15,7 @@ Try `.../make_docker_context.py --help` from a project's `build/` directory. ...@@ -14,7 +15,7 @@ Try `.../make_docker_context.py --help` from a project's `build/` directory.
By default, the Docker context directory will be in /tmp. It will always By default, the Docker context directory will be in /tmp. It will always
contain a Dockerfile, and might also contain copies of your local repos, and contain a Dockerfile, and might also contain copies of your local repos, and
other data needed for the build container. other data needed for the build container.
''' """
import os import os
import tempfile import tempfile
...@@ -27,7 +28,7 @@ from parse_args import parse_args_to_fbcode_builder_opts ...@@ -27,7 +28,7 @@ from parse_args import parse_args_to_fbcode_builder_opts
def make_docker_context( def make_docker_context(
get_steps_fn, github_project, opts=None, default_context_dir=None get_steps_fn, github_project, opts=None, default_context_dir=None
): ):
''' """
Returns a path to the Docker context directory. See parse_args.py. Returns a path to the Docker context directory. See parse_args.py.
Helper for making a command-line utility that writes your project's Helper for making a command-line utility that writes your project's
...@@ -38,83 +39,97 @@ def make_docker_context( ...@@ -38,83 +39,97 @@ def make_docker_context(
lambda builder: [builder.step(...), ...], lambda builder: [builder.step(...), ...],
'facebook/your_project', 'facebook/your_project',
)) ))
''' """
if opts is None: if opts is None:
opts = {} opts = {}
valid_versions = ( valid_versions = (
('ubuntu:16.04', '5'), ("ubuntu:16.04", "5"),
('ubuntu:18.04', '7'), ("ubuntu:18.04", "7"),
) )
def add_args(parser): def add_args(parser):
parser.add_argument( parser.add_argument(
'--docker-context-dir', metavar='DIR', "--docker-context-dir",
metavar="DIR",
default=default_context_dir, default=default_context_dir,
help='Write the Dockerfile and its context into this directory. ' help="Write the Dockerfile and its context into this directory. "
'If empty, make a temporary directory. Default: %(default)s.', "If empty, make a temporary directory. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--user', metavar='NAME', default=opts.get('user', 'nobody'), "--user",
help='Build and install as this user. Default: %(default)s.', metavar="NAME",
default=opts.get("user", "nobody"),
help="Build and install as this user. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--prefix', metavar='DIR', "--prefix",
default=opts.get('prefix', '/home/install'), metavar="DIR",
help='Install all libraries in this prefix. Default: %(default)s.', default=opts.get("prefix", "/home/install"),
help="Install all libraries in this prefix. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--projects-dir', metavar='DIR', "--projects-dir",
default=opts.get('projects_dir', '/home'), metavar="DIR",
help='Place project code directories here. Default: %(default)s.', default=opts.get("projects_dir", "/home"),
help="Place project code directories here. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--os-image', metavar='IMG', choices=zip(*valid_versions)[0], "--os-image",
default=opts.get('os_image', valid_versions[0][0]), metavar="IMG",
help='Docker OS image -- be sure to use only ones you trust (See ' choices=zip(*valid_versions)[0],
'README.docker). Choices: %(choices)s. Default: %(default)s.', default=opts.get("os_image", valid_versions[0][0]),
help="Docker OS image -- be sure to use only ones you trust (See "
"README.docker). Choices: %(choices)s. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--gcc-version', metavar='VER', "--gcc-version",
metavar="VER",
choices=set(zip(*valid_versions)[1]), choices=set(zip(*valid_versions)[1]),
default=opts.get('gcc_version', valid_versions[0][1]), default=opts.get("gcc_version", valid_versions[0][1]),
help='Choices: %(choices)s. Default: %(default)s.', help="Choices: %(choices)s. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--make-parallelism', metavar='NUM', type=int, "--make-parallelism",
default=opts.get('make_parallelism', 1), metavar="NUM",
help='Use `make -j` on multi-CPU systems with lots of RAM. ' type=int,
'Default: %(default)s.', default=opts.get("make_parallelism", 1),
help="Use `make -j` on multi-CPU systems with lots of RAM. "
"Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--local-repo-dir', metavar='DIR', "--local-repo-dir",
help='If set, build {0} from a local directory instead of Github.' metavar="DIR",
.format(github_project), help="If set, build {0} from a local directory instead of Github.".format(
github_project
),
) )
parser.add_argument( parser.add_argument(
'--ccache-tgz', metavar='PATH', "--ccache-tgz",
help='If set, enable ccache for the build. To initialize the ' metavar="PATH",
'cache, first try to hardlink, then to copy --cache-tgz ' help="If set, enable ccache for the build. To initialize the "
'as ccache.tgz into the --docker-context-dir.' "cache, first try to hardlink, then to copy --cache-tgz "
"as ccache.tgz into the --docker-context-dir.",
) )
opts = parse_args_to_fbcode_builder_opts( opts = parse_args_to_fbcode_builder_opts(
add_args, add_args,
# These have add_argument() calls, others are set via --option. # These have add_argument() calls, others are set via --option.
( (
'docker_context_dir', "docker_context_dir",
'user', "user",
'prefix', "prefix",
'projects_dir', "projects_dir",
'os_image', "os_image",
'gcc_version', "gcc_version",
'make_parallelism', "make_parallelism",
'local_repo_dir', "local_repo_dir",
'ccache_tgz', "ccache_tgz",
), ),
opts, opts,
help=textwrap.dedent(''' help=textwrap.dedent(
"""
Reads `fbcode_builder_config.py` from the current directory, and Reads `fbcode_builder_config.py` from the current directory, and
prepares a Docker context directory to build {github_project} and prepares a Docker context directory to build {github_project} and
...@@ -130,47 +145,55 @@ def make_docker_context( ...@@ -130,47 +145,55 @@ def make_docker_context(
Usage: Usage:
(cd $(./make_docker_context.py) && docker build . 2>&1 | tee log) (cd $(./make_docker_context.py) && docker build . 2>&1 | tee log)
'''.format(github_project=github_project)), """.format(
github_project=github_project
)
),
) )
# This allows travis_docker_build.sh not to know the main Github project. # This allows travis_docker_build.sh not to know the main Github project.
local_repo_dir = opts.pop('local_repo_dir', None) local_repo_dir = opts.pop("local_repo_dir", None)
if local_repo_dir is not None: if local_repo_dir is not None:
opts['{0}:local_repo_dir'.format(github_project)] = local_repo_dir opts["{0}:local_repo_dir".format(github_project)] = local_repo_dir
if (opts.get('os_image'), opts.get('gcc_version')) not in valid_versions: if (opts.get("os_image"), opts.get("gcc_version")) not in valid_versions:
raise Exception( raise Exception(
'Due to 4/5 ABI changes (std::string), we can only use {0}'.format( "Due to 4/5 ABI changes (std::string), we can only use {0}".format(
' / '.join('GCC {1} on {0}'.format(*p) for p in valid_versions) " / ".join("GCC {1} on {0}".format(*p) for p in valid_versions)
) )
) )
if opts.get('docker_context_dir') is None: if opts.get("docker_context_dir") is None:
opts['docker_context_dir'] = tempfile.mkdtemp(prefix='docker-context-') opts["docker_context_dir"] = tempfile.mkdtemp(prefix="docker-context-")
elif not os.path.exists(opts.get('docker_context_dir')): elif not os.path.exists(opts.get("docker_context_dir")):
os.makedirs(opts.get('docker_context_dir')) os.makedirs(opts.get("docker_context_dir"))
builder = DockerFBCodeBuilder(**opts) builder = DockerFBCodeBuilder(**opts)
context_dir = builder.option('docker_context_dir') # Mark option "in-use" context_dir = builder.option("docker_context_dir") # Mark option "in-use"
# The renderer may also populate some files into the context_dir. # The renderer may also populate some files into the context_dir.
dockerfile = builder.render(get_steps_fn(builder)) dockerfile = builder.render(get_steps_fn(builder))
with os.fdopen(os.open( with os.fdopen(
os.path.join(context_dir, 'Dockerfile'), os.open(
os.path.join(context_dir, "Dockerfile"),
os.O_RDWR | os.O_CREAT | os.O_EXCL, # Do not overwrite existing files os.O_RDWR | os.O_CREAT | os.O_EXCL, # Do not overwrite existing files
0o644, 0o644,
), 'w') as f: ),
"w",
) as f:
f.write(dockerfile) f.write(dockerfile)
return context_dir return context_dir
if __name__ == '__main__': if __name__ == "__main__":
from utils import read_fbcode_builder_config, build_fbcode_builder_config from utils import read_fbcode_builder_config, build_fbcode_builder_config
# Load a spec from the current directory # Load a spec from the current directory
config = read_fbcode_builder_config('fbcode_builder_config.py') config = read_fbcode_builder_config("fbcode_builder_config.py")
print(make_docker_context( print(
make_docker_context(
build_fbcode_builder_config(config), build_fbcode_builder_config(config),
config['github_project'], config["github_project"],
)) )
)
...@@ -4,7 +4,8 @@ from __future__ import absolute_import ...@@ -4,7 +4,8 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
'Argument parsing logic shared by all fbcode_builder CLI tools.'
"Argument parsing logic shared by all fbcode_builder CLI tools."
import argparse import argparse
import logging import logging
...@@ -13,7 +14,7 @@ from shell_quoting import raw_shell, ShellQuoted ...@@ -13,7 +14,7 @@ from shell_quoting import raw_shell, ShellQuoted
def parse_args_to_fbcode_builder_opts(add_args_fn, top_level_opts, opts, help): def parse_args_to_fbcode_builder_opts(add_args_fn, top_level_opts, opts, help):
''' """
Provides some standard arguments: --debug, --option, --shell-quoted-option Provides some standard arguments: --debug, --option, --shell-quoted-option
...@@ -26,46 +27,52 @@ def parse_args_to_fbcode_builder_opts(add_args_fn, top_level_opts, opts, help): ...@@ -26,46 +27,52 @@ def parse_args_to_fbcode_builder_opts(add_args_fn, top_level_opts, opts, help):
`help` is printed in response to the `--help` argument. `help` is printed in response to the `--help` argument.
''' """
top_level_opts = set(top_level_opts) top_level_opts = set(top_level_opts)
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=help, description=help, formatter_class=argparse.RawDescriptionHelpFormatter
formatter_class=argparse.RawDescriptionHelpFormatter
) )
add_args_fn(parser) add_args_fn(parser)
parser.add_argument( parser.add_argument(
'--option', nargs=2, metavar=('KEY', 'VALUE'), action='append', "--option",
nargs=2,
metavar=("KEY", "VALUE"),
action="append",
default=[ default=[
(k, v) for k, v in opts.items() (k, v)
for k, v in opts.items()
if k not in top_level_opts and not isinstance(v, ShellQuoted) if k not in top_level_opts and not isinstance(v, ShellQuoted)
], ],
help='Set project-specific options. These are assumed to be raw ' help="Set project-specific options. These are assumed to be raw "
'strings, to be shell-escaped as needed. Default: %(default)s.', "strings, to be shell-escaped as needed. Default: %(default)s.",
) )
parser.add_argument( parser.add_argument(
'--shell-quoted-option', nargs=2, metavar=('KEY', 'VALUE'), "--shell-quoted-option",
action='append', nargs=2,
metavar=("KEY", "VALUE"),
action="append",
default=[ default=[
(k, raw_shell(v)) for k, v in opts.items() (k, raw_shell(v))
for k, v in opts.items()
if k not in top_level_opts and isinstance(v, ShellQuoted) if k not in top_level_opts and isinstance(v, ShellQuoted)
], ],
help='Set project-specific options. These are assumed to be shell-' help="Set project-specific options. These are assumed to be shell-"
'quoted, and may be used in commands as-is. Default: %(default)s.', "quoted, and may be used in commands as-is. Default: %(default)s.",
) )
parser.add_argument('--debug', action='store_true', help='Log more') parser.add_argument("--debug", action="store_true", help="Log more")
args = parser.parse_args() args = parser.parse_args()
logging.basicConfig( logging.basicConfig(
level=logging.DEBUG if args.debug else logging.INFO, level=logging.DEBUG if args.debug else logging.INFO,
format='%(levelname)s: %(message)s' format="%(levelname)s: %(message)s",
) )
# Map command-line args back into opts. # Map command-line args back into opts.
logging.debug('opts before command-line arguments: {0}'.format(opts)) logging.debug("opts before command-line arguments: {0}".format(opts))
new_opts = {} new_opts = {}
for key in top_level_opts: for key in top_level_opts:
...@@ -78,6 +85,6 @@ def parse_args_to_fbcode_builder_opts(add_args_fn, top_level_opts, opts, help): ...@@ -78,6 +85,6 @@ def parse_args_to_fbcode_builder_opts(add_args_fn, top_level_opts, opts, help):
for key, val in args.shell_quoted_option: for key, val in args.shell_quoted_option:
new_opts[key] = ShellQuoted(val) new_opts[key] = ShellQuoted(val)
logging.debug('opts after command-line arguments: {0}'.format(new_opts)) logging.debug("opts after command-line arguments: {0}".format(new_opts))
return new_opts return new_opts
...@@ -5,7 +5,7 @@ from __future__ import division ...@@ -5,7 +5,7 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
''' """
shell_builder.py allows running the fbcode_builder logic shell_builder.py allows running the fbcode_builder logic
on the host rather than in a container. on the host rather than in a container.
...@@ -17,50 +17,50 @@ any failing step will cause the script to exit with failure. ...@@ -17,50 +17,50 @@ any failing step will cause the script to exit with failure.
cd build cd build
python fbcode_builder/shell_builder.py > ~/run.sh python fbcode_builder/shell_builder.py > ~/run.sh
bash ~/run.sh bash ~/run.sh
''' """
import os
import distutils.spawn import distutils.spawn
import os
from fbcode_builder import FBCodeBuilder from fbcode_builder import FBCodeBuilder
from shell_quoting import ( from shell_quoting import raw_shell, shell_comment, shell_join, ShellQuoted
raw_shell, shell_comment, shell_join, ShellQuoted
)
from utils import recursively_flatten_list from utils import recursively_flatten_list
class ShellFBCodeBuilder(FBCodeBuilder): class ShellFBCodeBuilder(FBCodeBuilder):
def _render_impl(self, steps): def _render_impl(self, steps):
return raw_shell(shell_join('\n', recursively_flatten_list(steps))) return raw_shell(shell_join("\n", recursively_flatten_list(steps)))
def set_env(self, key, value): def set_env(self, key, value):
return ShellQuoted("export {key}={val}").format(key=key, val=value) return ShellQuoted("export {key}={val}").format(key=key, val=value)
def workdir(self, dir): def workdir(self, dir):
return [ return [
ShellQuoted('mkdir -p {d} && cd {d}').format( ShellQuoted("mkdir -p {d} && cd {d}").format(d=dir),
d=dir
),
] ]
def run(self, shell_cmd): def run(self, shell_cmd):
return ShellQuoted('{cmd}').format(cmd=shell_cmd) return ShellQuoted("{cmd}").format(cmd=shell_cmd)
def step(self, name, actions): def step(self, name, actions):
assert '\n' not in name, 'Name {0} would span > 1 line'.format(name) assert "\n" not in name, "Name {0} would span > 1 line".format(name)
b = ShellQuoted('') b = ShellQuoted("")
return [ShellQuoted('### {0} ###'.format(name)), b] + actions + [b] return [ShellQuoted("### {0} ###".format(name)), b] + actions + [b]
def setup(self): def setup(self):
steps = [ steps = (
ShellQuoted('set -exo pipefail'), [
] + self.create_python_venv() + self.python_venv() ShellQuoted("set -exo pipefail"),
if self.has_option('ccache_dir'): ]
ccache_dir = self.option('ccache_dir') + self.create_python_venv()
+ self.python_venv()
)
if self.has_option("ccache_dir"):
ccache_dir = self.option("ccache_dir")
steps += [ steps += [
ShellQuoted( ShellQuoted(
# Set CCACHE_DIR before the `ccache` invocations below. # Set CCACHE_DIR before the `ccache` invocations below.
'export CCACHE_DIR={ccache_dir} ' "export CCACHE_DIR={ccache_dir} "
'CC="ccache ${{CC:-gcc}}" CXX="ccache ${{CXX:-g++}}"' 'CC="ccache ${{CC:-gcc}}" CXX="ccache ${{CXX:-g++}}"'
).format(ccache_dir=ccache_dir) ).format(ccache_dir=ccache_dir)
] ]
...@@ -71,44 +71,44 @@ class ShellFBCodeBuilder(FBCodeBuilder): ...@@ -71,44 +71,44 @@ class ShellFBCodeBuilder(FBCodeBuilder):
def copy_local_repo(self, dir, dest_name): def copy_local_repo(self, dir, dest_name):
return [ return [
ShellQuoted('cp -r {dir} {dest_name}').format( ShellQuoted("cp -r {dir} {dest_name}").format(dir=dir, dest_name=dest_name),
dir=dir,
dest_name=dest_name
),
] ]
def find_project_root(): def find_project_root():
here = os.path.dirname(os.path.realpath(__file__)) here = os.path.dirname(os.path.realpath(__file__))
maybe_root = os.path.dirname(os.path.dirname(here)) maybe_root = os.path.dirname(os.path.dirname(here))
if os.path.isdir(os.path.join(maybe_root, '.git')): if os.path.isdir(os.path.join(maybe_root, ".git")):
return maybe_root return maybe_root
raise RuntimeError( raise RuntimeError(
"I expected shell_builder.py to be in the " "I expected shell_builder.py to be in the "
"build/fbcode_builder subdir of a git repo") "build/fbcode_builder subdir of a git repo"
)
def persistent_temp_dir(repo_root): def persistent_temp_dir(repo_root):
escaped = repo_root.replace('/', 'sZs').replace('\\', 'sZs').replace(':', '') escaped = repo_root.replace("/", "sZs").replace("\\", "sZs").replace(":", "")
return os.path.join(os.path.expandvars("$HOME"), '.fbcode_builder-' + escaped) return os.path.join(os.path.expandvars("$HOME"), ".fbcode_builder-" + escaped)
if __name__ == '__main__': if __name__ == "__main__":
from utils import read_fbcode_builder_config, build_fbcode_builder_config from utils import read_fbcode_builder_config, build_fbcode_builder_config
repo_root = find_project_root() repo_root = find_project_root()
temp = persistent_temp_dir(repo_root) temp = persistent_temp_dir(repo_root)
config = read_fbcode_builder_config('fbcode_builder_config.py') config = read_fbcode_builder_config("fbcode_builder_config.py")
builder = ShellFBCodeBuilder(projects_dir=temp) builder = ShellFBCodeBuilder(projects_dir=temp)
if distutils.spawn.find_executable('ccache'): if distutils.spawn.find_executable("ccache"):
builder.add_option('ccache_dir', builder.add_option(
os.environ.get('CCACHE_DIR', os.path.join(temp, '.ccache'))) "ccache_dir", os.environ.get("CCACHE_DIR", os.path.join(temp, ".ccache"))
builder.add_option('prefix', os.path.join(temp, 'installed')) )
builder.add_option('make_parallelism', 4) builder.add_option("prefix", os.path.join(temp, "installed"))
builder.add_option("make_parallelism", 4)
builder.add_option( builder.add_option(
'{project}:local_repo_dir'.format(project=config['github_project']), "{project}:local_repo_dir".format(project=config["github_project"]), repo_root
repo_root) )
make_steps = build_fbcode_builder_config(config) make_steps = build_fbcode_builder_config(config)
steps = make_steps(builder) steps = make_steps(builder)
print(builder.render(steps)) print(builder.render(steps))
...@@ -4,7 +4,8 @@ from __future__ import absolute_import ...@@ -4,7 +4,8 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
'''
"""
Almost every FBCodeBuilder string is ultimately passed to a shell. Escaping Almost every FBCodeBuilder string is ultimately passed to a shell. Escaping
too little or too much tends to be the most common error. The utilities in too little or too much tends to be the most common error. The utilities in
...@@ -16,15 +17,14 @@ this file give a systematic way of avoiding such bugs: ...@@ -16,15 +17,14 @@ this file give a systematic way of avoiding such bugs:
- Use `path_join` to join path components. - Use `path_join` to join path components.
- Use `shell_join` to join already-quoted command arguments or shell lines. - Use `shell_join` to join already-quoted command arguments or shell lines.
''' """
import os import os
from collections import namedtuple from collections import namedtuple
class ShellQuoted(namedtuple('ShellQuoted', ('do_not_use_raw_str',))): class ShellQuoted(namedtuple("ShellQuoted", ("do_not_use_raw_str",))):
''' """
Wrap a string with this to make it transparent to shell_quote(). It Wrap a string with this to make it transparent to shell_quote(). It
will almost always suffice to use ShellQuoted.format(), path_join(), will almost always suffice to use ShellQuoted.format(), path_join(),
...@@ -32,27 +32,25 @@ class ShellQuoted(namedtuple('ShellQuoted', ('do_not_use_raw_str',))): ...@@ -32,27 +32,25 @@ class ShellQuoted(namedtuple('ShellQuoted', ('do_not_use_raw_str',))):
If you really must, use raw_shell() to access the raw string. If you really must, use raw_shell() to access the raw string.
''' """
def __new__(cls, s): def __new__(cls, s):
'No need to nest ShellQuoted.' "No need to nest ShellQuoted."
return super(ShellQuoted, cls).__new__( return super(ShellQuoted, cls).__new__(
cls, s.do_not_use_raw_str if isinstance(s, ShellQuoted) else s cls, s.do_not_use_raw_str if isinstance(s, ShellQuoted) else s
) )
def __str__(self): def __str__(self):
raise RuntimeError( raise RuntimeError(
'One does not simply convert {0} to a string -- use path_join() ' "One does not simply convert {0} to a string -- use path_join() "
'or ShellQuoted.format() instead'.format(repr(self)) "or ShellQuoted.format() instead".format(repr(self))
) )
def __repr__(self): def __repr__(self):
return '{0}({1})'.format( return "{0}({1})".format(self.__class__.__name__, repr(self.do_not_use_raw_str))
self.__class__.__name__, repr(self.do_not_use_raw_str)
)
def format(self, **kwargs): def format(self, **kwargs):
''' """
Use instead of str.format() when the arguments are either Use instead of str.format() when the arguments are either
`ShellQuoted()` or raw strings needing to be `shell_quote()`d. `ShellQuoted()` or raw strings needing to be `shell_quote()`d.
...@@ -60,40 +58,46 @@ class ShellQuoted(namedtuple('ShellQuoted', ('do_not_use_raw_str',))): ...@@ -60,40 +58,46 @@ class ShellQuoted(namedtuple('ShellQuoted', ('do_not_use_raw_str',))):
Positional args are deliberately not supported since they are more Positional args are deliberately not supported since they are more
error-prone. error-prone.
''' """
return ShellQuoted(self.do_not_use_raw_str.format(**dict( return ShellQuoted(
self.do_not_use_raw_str.format(
**dict(
(k, shell_quote(v).do_not_use_raw_str) for k, v in kwargs.items() (k, shell_quote(v).do_not_use_raw_str) for k, v in kwargs.items()
))) )
)
)
def shell_quote(s): def shell_quote(s):
'Quotes a string if it is not already quoted' "Quotes a string if it is not already quoted"
return s if isinstance(s, ShellQuoted) \ return (
s
if isinstance(s, ShellQuoted)
else ShellQuoted("'" + str(s).replace("'", "'\\''") + "'") else ShellQuoted("'" + str(s).replace("'", "'\\''") + "'")
)
def raw_shell(s): def raw_shell(s):
'Not a member of ShellQuoted so we get a useful error for raw strings' "Not a member of ShellQuoted so we get a useful error for raw strings"
if isinstance(s, ShellQuoted): if isinstance(s, ShellQuoted):
return s.do_not_use_raw_str return s.do_not_use_raw_str
raise RuntimeError('{0} should have been ShellQuoted'.format(s)) raise RuntimeError("{0} should have been ShellQuoted".format(s))
def shell_join(delim, it): def shell_join(delim, it):
'Joins an iterable of ShellQuoted with a delimiter between each two' "Joins an iterable of ShellQuoted with a delimiter between each two"
return ShellQuoted(delim.join(raw_shell(s) for s in it)) return ShellQuoted(delim.join(raw_shell(s) for s in it))
def path_join(*args): def path_join(*args):
'Joins ShellQuoted and raw pieces of paths to make a shell-quoted path' "Joins ShellQuoted and raw pieces of paths to make a shell-quoted path"
return ShellQuoted(os.path.join(*[ return ShellQuoted(os.path.join(*[raw_shell(shell_quote(s)) for s in args]))
raw_shell(shell_quote(s)) for s in args
]))
def shell_comment(c): def shell_comment(c):
'Do not shell-escape raw strings in comments, but do handle line breaks.' "Do not shell-escape raw strings in comments, but do handle line breaks."
return ShellQuoted('# {c}').format(c=ShellQuoted( return ShellQuoted("# {c}").format(
(raw_shell(c) if isinstance(c, ShellQuoted) else c) c=ShellQuoted(
.replace('\n', '\n# ') (raw_shell(c) if isinstance(c, ShellQuoted) else c).replace("\n", "\n# ")
)) )
)
...@@ -15,8 +15,8 @@ import specs.zstd as zstd ...@@ -15,8 +15,8 @@ import specs.zstd as zstd
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
return { return {
'depends_on': [fmt, folly, fizz, sodium, wangle, zstd], "depends_on": [fmt, folly, fizz, sodium, wangle, zstd],
'steps': [ "steps": [
builder.fb_github_cmake_install('fbthrift/thrift'), builder.fb_github_cmake_install("fbthrift/thrift"),
], ],
} }
...@@ -10,31 +10,40 @@ import specs.fmt as fmt ...@@ -10,31 +10,40 @@ import specs.fmt as fmt
import specs.folly as folly import specs.folly as folly
import specs.gmock as gmock import specs.gmock as gmock
import specs.sodium as sodium import specs.sodium as sodium
from shell_quoting import ShellQuoted from shell_quoting import ShellQuoted
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
builder.add_option('zeromq/libzmq:git_hash', 'v4.2.2') builder.add_option("zeromq/libzmq:git_hash", "v4.2.2")
return { return {
'depends_on': [fmt, folly, fbthrift, gmock, sodium], "depends_on": [fmt, folly, fbthrift, gmock, sodium],
'steps': [ "steps": [
builder.github_project_workdir('zeromq/libzmq', '.'), builder.github_project_workdir("zeromq/libzmq", "."),
builder.step('Build and install zeromq/libzmq', [ builder.step(
builder.run(ShellQuoted('./autogen.sh')), "Build and install zeromq/libzmq",
[
builder.run(ShellQuoted("./autogen.sh")),
builder.configure(), builder.configure(),
builder.make_and_install(), builder.make_and_install(),
]), ],
),
builder.fb_github_project_workdir('fbzmq/_build', 'facebook'), builder.fb_github_project_workdir("fbzmq/_build", "facebook"),
builder.step('Build and install fbzmq/', [ builder.step(
builder.cmake_configure('fbzmq/_build'), "Build and install fbzmq/",
[
builder.cmake_configure("fbzmq/_build"),
# we need the pythonpath to find the thrift compiler # we need the pythonpath to find the thrift compiler
builder.run(ShellQuoted( builder.run(
ShellQuoted(
'PYTHONPATH="$PYTHONPATH:"{p}/lib/python2.7/site-packages ' 'PYTHONPATH="$PYTHONPATH:"{p}/lib/python2.7/site-packages '
'make -j {n}' "make -j {n}"
).format(p=builder.option('prefix'), n=builder.option('make_parallelism'))), ).format(
builder.run(ShellQuoted('make install')), p=builder.option("prefix"),
]), n=builder.option("make_parallelism"),
)
),
builder.run(ShellQuoted("make install")),
],
),
], ],
} }
...@@ -5,9 +5,9 @@ from __future__ import division ...@@ -5,9 +5,9 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import specs.gmock as gmock
import specs.fmt as fmt import specs.fmt as fmt
import specs.folly as folly import specs.folly as folly
import specs.gmock as gmock
import specs.sodium as sodium import specs.sodium as sodium
......
...@@ -7,20 +7,20 @@ from __future__ import unicode_literals ...@@ -7,20 +7,20 @@ from __future__ import unicode_literals
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
builder.add_option('fmtlib/fmt:git_hash', '6.2.1') builder.add_option("fmtlib/fmt:git_hash", "6.2.1")
builder.add_option( builder.add_option(
'fmtlib/fmt:cmake_defines', "fmtlib/fmt:cmake_defines",
{ {
# Avoids a bizarred failure to run tests in Bistro: # Avoids a bizarred failure to run tests in Bistro:
# test_crontab_selector: error while loading shared libraries: # test_crontab_selector: error while loading shared libraries:
# libfmt.so.6: cannot open shared object file: # libfmt.so.6: cannot open shared object file:
# No such file or directory # No such file or directory
'BUILD_SHARED_LIBS': 'OFF', "BUILD_SHARED_LIBS": "OFF",
} },
) )
return { return {
'steps': [ "steps": [
builder.github_project_workdir('fmtlib/fmt', 'build'), builder.github_project_workdir("fmtlib/fmt", "build"),
builder.cmake_install('fmtlib/fmt'), builder.cmake_install("fmtlib/fmt"),
], ],
} }
...@@ -11,13 +11,13 @@ import specs.fmt as fmt ...@@ -11,13 +11,13 @@ import specs.fmt as fmt
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
return { return {
"depends_on": [fmt], "depends_on": [fmt],
'steps': [ "steps": [
# on macOS the filesystem is typically case insensitive. # on macOS the filesystem is typically case insensitive.
# We need to ensure that the CWD is not the folly source # We need to ensure that the CWD is not the folly source
# dir when we build, otherwise the system will decide # dir when we build, otherwise the system will decide
# that `folly/String.h` is the file it wants when including # that `folly/String.h` is the file it wants when including
# `string.h` and the build will fail. # `string.h` and the build will fail.
builder.fb_github_project_workdir('folly/_build'), builder.fb_github_project_workdir("folly/_build"),
builder.cmake_install('facebook/folly'), builder.cmake_install("facebook/folly"),
], ],
} }
...@@ -7,18 +7,18 @@ from __future__ import unicode_literals ...@@ -7,18 +7,18 @@ from __future__ import unicode_literals
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
builder.add_option('google/googletest:git_hash', 'release-1.8.1') builder.add_option("google/googletest:git_hash", "release-1.8.1")
builder.add_option( builder.add_option(
'google/googletest:cmake_defines', "google/googletest:cmake_defines",
{ {
'BUILD_GTEST': 'ON', "BUILD_GTEST": "ON",
# Avoid problems with MACOSX_RPATH # Avoid problems with MACOSX_RPATH
'BUILD_SHARED_LIBS': 'OFF', "BUILD_SHARED_LIBS": "OFF",
} },
) )
return { return {
'steps': [ "steps": [
builder.github_project_workdir('google/googletest', 'build'), builder.github_project_workdir("google/googletest", "build"),
builder.cmake_install('google/googletest'), builder.cmake_install("google/googletest"),
], ],
} }
...@@ -5,9 +5,9 @@ from __future__ import division ...@@ -5,9 +5,9 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import specs.gmock as gmock
import specs.folly as folly
import specs.fizz as fizz import specs.fizz as fizz
import specs.folly as folly
import specs.gmock as gmock
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
......
...@@ -5,10 +5,10 @@ from __future__ import division ...@@ -5,10 +5,10 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import specs.gmock as gmock import specs.fizz as fizz
import specs.fmt as fmt import specs.fmt as fmt
import specs.folly as folly import specs.folly as folly
import specs.fizz as fizz import specs.gmock as gmock
import specs.mvfst as mvfst import specs.mvfst as mvfst
import specs.sodium as sodium import specs.sodium as sodium
import specs.wangle as wangle import specs.wangle as wangle
......
...@@ -5,10 +5,10 @@ from __future__ import division ...@@ -5,10 +5,10 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import specs.gmock as gmock import specs.fizz as fizz
import specs.fmt as fmt import specs.fmt as fmt
import specs.folly as folly import specs.folly as folly
import specs.fizz as fizz import specs.gmock as gmock
import specs.mvfst as mvfst import specs.mvfst as mvfst
import specs.sodium as sodium import specs.sodium as sodium
import specs.wangle as wangle import specs.wangle as wangle
......
...@@ -8,8 +8,8 @@ from __future__ import unicode_literals ...@@ -8,8 +8,8 @@ from __future__ import unicode_literals
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
return { return {
'steps': [ "steps": [
builder.github_project_workdir('google/re2', 'build'), builder.github_project_workdir("google/re2", "build"),
builder.cmake_install('google/re2'), builder.cmake_install("google/re2"),
], ],
} }
...@@ -7,10 +7,13 @@ from __future__ import unicode_literals ...@@ -7,10 +7,13 @@ from __future__ import unicode_literals
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
builder.add_option("rocksdb/_build:cmake_defines", { builder.add_option(
"rocksdb/_build:cmake_defines",
{
"USE_RTTI": "1", "USE_RTTI": "1",
"PORTABLE": "ON", "PORTABLE": "ON",
}) },
)
return { return {
"steps": [ "steps": [
builder.fb_github_cmake_install("rocksdb/_build"), builder.fb_github_cmake_install("rocksdb/_build"),
......
...@@ -9,14 +9,17 @@ from shell_quoting import ShellQuoted ...@@ -9,14 +9,17 @@ from shell_quoting import ShellQuoted
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
builder.add_option('jedisct1/libsodium:git_hash', 'stable') builder.add_option("jedisct1/libsodium:git_hash", "stable")
return { return {
'steps': [ "steps": [
builder.github_project_workdir('jedisct1/libsodium', '.'), builder.github_project_workdir("jedisct1/libsodium", "."),
builder.step('Build and install jedisct1/libsodium', [ builder.step(
builder.run(ShellQuoted('./autogen.sh')), "Build and install jedisct1/libsodium",
[
builder.run(ShellQuoted("./autogen.sh")),
builder.configure(), builder.configure(),
builder.make_and_install(), builder.make_and_install(),
]), ],
),
], ],
} }
...@@ -5,10 +5,10 @@ from __future__ import division ...@@ -5,10 +5,10 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import specs.gmock as gmock import specs.fizz as fizz
import specs.fmt as fmt import specs.fmt as fmt
import specs.folly as folly import specs.folly as folly
import specs.fizz as fizz import specs.gmock as gmock
import specs.sodium as sodium import specs.sodium as sodium
......
...@@ -11,16 +11,21 @@ from shell_quoting import ShellQuoted ...@@ -11,16 +11,21 @@ from shell_quoting import ShellQuoted
def fbcode_builder_spec(builder): def fbcode_builder_spec(builder):
# This API should change rarely, so build the latest tag instead of master. # This API should change rarely, so build the latest tag instead of master.
builder.add_option( builder.add_option(
'facebook/zstd:git_hash', "facebook/zstd:git_hash",
ShellQuoted('$(git describe --abbrev=0 --tags origin/master)') ShellQuoted("$(git describe --abbrev=0 --tags origin/master)"),
) )
return { return {
'steps': [ "steps": [
builder.github_project_workdir('facebook/zstd', '.'), builder.github_project_workdir("facebook/zstd", "."),
builder.step('Build and install zstd', [ builder.step(
builder.make_and_install(make_vars={ "Build and install zstd",
'PREFIX': builder.option('prefix'), [
}) builder.make_and_install(
]), make_vars={
"PREFIX": builder.option("prefix"),
}
)
],
),
], ],
} }
...@@ -4,7 +4,8 @@ from __future__ import absolute_import ...@@ -4,7 +4,8 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
'Miscellaneous utility functions.'
"Miscellaneous utility functions."
import itertools import itertools
import logging import logging
...@@ -12,21 +13,19 @@ import os ...@@ -12,21 +13,19 @@ import os
import shutil import shutil
import subprocess import subprocess
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
def recursively_flatten_list(l): def recursively_flatten_list(l):
return itertools.chain.from_iterable( return itertools.chain.from_iterable(
(recursively_flatten_list(i) if type(i) is list else (i,)) (recursively_flatten_list(i) if type(i) is list else (i,)) for i in l
for i in l
) )
def run_command(*cmd, **kwargs): def run_command(*cmd, **kwargs):
'The stdout of most fbcode_builder utilities is meant to be parsed.' "The stdout of most fbcode_builder utilities is meant to be parsed."
logging.debug('Running: {0} with {1}'.format(cmd, kwargs)) logging.debug("Running: {0} with {1}".format(cmd, kwargs))
kwargs['stdout'] = sys.stderr kwargs["stdout"] = sys.stderr
subprocess.check_call(cmd, **kwargs) subprocess.check_call(cmd, **kwargs)
...@@ -40,13 +39,13 @@ def make_temp_dir(d): ...@@ -40,13 +39,13 @@ def make_temp_dir(d):
def _inner_read_config(path): def _inner_read_config(path):
''' """
Helper to read a named config file. Helper to read a named config file.
The grossness with the global is a workaround for this python bug: The grossness with the global is a workaround for this python bug:
https://bugs.python.org/issue21591 https://bugs.python.org/issue21591
The bug prevents us from defining either a local function or a lambda The bug prevents us from defining either a local function or a lambda
in the scope of read_fbcode_builder_config below. in the scope of read_fbcode_builder_config below.
''' """
global _project_dir global _project_dir
full_path = os.path.join(_project_dir, path) full_path = os.path.join(_project_dir, path)
return read_fbcode_builder_config(full_path) return read_fbcode_builder_config(full_path)
...@@ -60,37 +59,37 @@ def read_fbcode_builder_config(filename): ...@@ -60,37 +59,37 @@ def read_fbcode_builder_config(filename):
global _project_dir global _project_dir
_project_dir = os.path.dirname(filename) _project_dir = os.path.dirname(filename)
scope = {'read_fbcode_builder_config': _inner_read_config} scope = {"read_fbcode_builder_config": _inner_read_config}
with open(filename) as config_file: with open(filename) as config_file:
code = compile(config_file.read(), filename, mode='exec') code = compile(config_file.read(), filename, mode="exec")
exec(code, scope) exec(code, scope)
return scope['config'] return scope["config"]
def steps_for_spec(builder, spec, processed_modules=None): def steps_for_spec(builder, spec, processed_modules=None):
''' """
Sets `builder` configuration, and returns all the builder steps Sets `builder` configuration, and returns all the builder steps
necessary to build `spec` and its dependencies. necessary to build `spec` and its dependencies.
Traverses the dependencies in depth-first order, honoring the sequencing Traverses the dependencies in depth-first order, honoring the sequencing
in each 'depends_on' list. in each 'depends_on' list.
''' """
if processed_modules is None: if processed_modules is None:
processed_modules = set() processed_modules = set()
steps = [] steps = []
for module in spec.get('depends_on', []): for module in spec.get("depends_on", []):
if module not in processed_modules: if module not in processed_modules:
processed_modules.add(module) processed_modules.add(module)
steps.extend(steps_for_spec( steps.extend(
builder, steps_for_spec(
module.fbcode_builder_spec(builder), builder, module.fbcode_builder_spec(builder), processed_modules
processed_modules )
)) )
steps.extend(spec.get('steps', [])) steps.extend(spec.get("steps", []))
return steps return steps
def build_fbcode_builder_config(config): def build_fbcode_builder_config(config):
return lambda builder: builder.build( return lambda builder: builder.build(
steps_for_spec(builder, config['fbcode_builder_spec'](builder)) steps_for_spec(builder, config["fbcode_builder_spec"](builder))
) )
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment