mirror of
https://github.com/facebook/proxygen.git
synced 2025-08-10 05:22:59 +03:00
Summary: From the outset, we wanted to be sure that getdeps was able to source and build the dependencies so that we knew that we'd have a repeatable build. This came at the cost of build times: having to build boost on each CI run is a bit of a chore. This commit adds three new elements to the manifest files: * `rpms` - a list of RPM names that are all required to be present in order to consider the dependency satisfied * `debs` - like `rpms` above, but scoped to debian package names * `preinstalled.env` - a list of environment variables that if they are all set and non-empty will satisfy the dependency. A new `--allow-system-packages` option to getdeps enables the new logic that looks for system packages; it is off by default, but enabled in the generated GitHub Actions workflows. A new `install-system-deps` subcommand is provided that will attempt to install the system packages needed to satisfy the build. This typically needs to be run via sudo and is thus broken out separately from the main getdeps build flow. I made a pass over the manifest files and added package names that satisfy the build on ubuntu-18 and fedora-31. shri-khare: I renamed the `Python3.7.6` manifest to just `python` as part of this change; the version of python that it pulls in through the normal build is the same and I believe that an equal or newer version of python3 is available in the GH actions builder. The `preinstalled.env` is used only by the boost manifest: it references the name of an environment variable that is set by the github windows hosts and that points to a pre-built and pre-installed copy of boost. Since there is no package manager that we can easily query for this sort of thing, probing from the environment seems like a reasonable and fast way to check for this. We may need to evolve this over time to become more feature rich, but this seems like a good starting point. This commit has the potential to save 20 minutes of build time from each public CI build just due to the boost dependency alone! Refs: https://github.com/facebook/watchman/pull/797 Reviewed By: yfeldblum Differential Revision: D20740410 fbshipit-source-id: 6c38019449c54465127656c3d18a6ff1f30adaea
159 lines
4.9 KiB
Python
159 lines
4.9 KiB
Python
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
|
|
import os
|
|
import select
|
|
import subprocess
|
|
import sys
|
|
|
|
from .envfuncs import Env
|
|
from .platform import is_windows
|
|
|
|
|
|
try:
|
|
from shlex import quote as shellquote
|
|
except ImportError:
|
|
from pipes import quote as shellquote
|
|
|
|
|
|
class RunCommandError(Exception):
|
|
pass
|
|
|
|
|
|
def _print_env_diff(env):
|
|
current_keys = set(os.environ.keys())
|
|
wanted_env = set(env.keys())
|
|
|
|
unset_keys = current_keys.difference(wanted_env)
|
|
for k in sorted(unset_keys):
|
|
print("+ unset %s" % k)
|
|
|
|
added_keys = wanted_env.difference(current_keys)
|
|
for k in wanted_env.intersection(current_keys):
|
|
if os.environ[k] != env[k]:
|
|
added_keys.add(k)
|
|
|
|
for k in sorted(added_keys):
|
|
if ("PATH" in k) and (os.pathsep in env[k]):
|
|
print("+ %s=\\" % k)
|
|
for elem in env[k].split(os.pathsep):
|
|
print("+ %s%s\\" % (shellquote(elem), os.pathsep))
|
|
else:
|
|
print("+ %s=%s \\" % (k, shellquote(env[k])))
|
|
|
|
|
|
def run_cmd(cmd, env=None, cwd=None, allow_fail=False, log_file=None):
|
|
if log_file is not None:
|
|
with open(log_file, "a") as log:
|
|
|
|
def log_function(msg):
|
|
log.write(msg)
|
|
sys.stdout.write(msg)
|
|
|
|
return _run_cmd(
|
|
cmd, env=env, cwd=cwd, allow_fail=allow_fail, log_fn=log_function
|
|
)
|
|
else:
|
|
return _run_cmd(
|
|
cmd, env=env, cwd=cwd, allow_fail=allow_fail, log_fn=sys.stdout.write
|
|
)
|
|
|
|
|
|
def _run_cmd(cmd, env, cwd, allow_fail, log_fn):
|
|
log_fn("---\n")
|
|
try:
|
|
cmd_str = " \\\n+ ".join(shellquote(arg) for arg in cmd)
|
|
except TypeError:
|
|
# eg: one of the elements is None
|
|
raise RunCommandError("problem quoting cmd: %r" % cmd)
|
|
|
|
if env:
|
|
assert isinstance(env, Env)
|
|
_print_env_diff(env)
|
|
|
|
# Convert from our Env type to a regular dict.
|
|
# This is needed because python3 looks up b'PATH' and 'PATH'
|
|
# and emits an error if both are present. In our Env type
|
|
# we'll return the same value for both requests, but we don't
|
|
# have duplicate potentially conflicting values which is the
|
|
# spirit of the check.
|
|
env = dict(env.items())
|
|
|
|
if cwd:
|
|
log_fn("+ cd %s && \\\n" % shellquote(cwd))
|
|
# Our long path escape sequence may confuse cmd.exe, so if the cwd
|
|
# is short enough, strip that off.
|
|
if is_windows() and (len(cwd) < 250) and cwd.startswith("\\\\?\\"):
|
|
cwd = cwd[4:]
|
|
|
|
log_fn("+ %s\n" % cmd_str)
|
|
|
|
try:
|
|
p = subprocess.Popen(
|
|
cmd, env=env, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
|
)
|
|
except (TypeError, ValueError, OSError) as exc:
|
|
log_fn("error running `%s`: %s" % (cmd_str, exc))
|
|
raise RunCommandError(
|
|
"%s while running `%s` with env=%r\nos.environ=%r"
|
|
% (str(exc), cmd_str, env, os.environ)
|
|
)
|
|
|
|
_pipe_output(p, log_fn)
|
|
|
|
p.wait()
|
|
if p.returncode != 0 and not allow_fail:
|
|
raise subprocess.CalledProcessError(p.returncode, cmd)
|
|
|
|
return p.returncode
|
|
|
|
|
|
if hasattr(select, "poll"):
|
|
|
|
def _pipe_output(p, log_fn):
|
|
"""Read output from p.stdout and call log_fn() with each chunk of data as it
|
|
becomes available."""
|
|
# Perform non-blocking reads
|
|
import fcntl
|
|
|
|
fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
|
|
poll = select.poll()
|
|
poll.register(p.stdout.fileno(), select.POLLIN)
|
|
|
|
buffer_size = 4096
|
|
while True:
|
|
poll.poll()
|
|
data = p.stdout.read(buffer_size)
|
|
if not data:
|
|
break
|
|
# log_fn() accepts arguments as str (binary in Python 2, unicode in
|
|
# Python 3). In Python 3 the subprocess output will be plain bytes,
|
|
# and need to be decoded.
|
|
if not isinstance(data, str):
|
|
data = data.decode("utf-8", errors="surrogateescape")
|
|
log_fn(data)
|
|
|
|
|
|
else:
|
|
|
|
def _pipe_output(p, log_fn):
|
|
"""Read output from p.stdout and call log_fn() with each chunk of data as it
|
|
becomes available."""
|
|
# Perform blocking reads. Use a smaller buffer size to avoid blocking
|
|
# for very long when data is available.
|
|
buffer_size = 64
|
|
while True:
|
|
data = p.stdout.read(buffer_size)
|
|
if not data:
|
|
break
|
|
# log_fn() accepts arguments as str (binary in Python 2, unicode in
|
|
# Python 3). In Python 3 the subprocess output will be plain bytes,
|
|
# and need to be decoded.
|
|
if not isinstance(data, str):
|
|
data = data.decode("utf-8", errors="surrogateescape")
|
|
log_fn(data)
|