From 3d0d9e6d53e2e34b2247d3e36ef40a381c305b93 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Fri, 3 May 2019 15:52:39 -0700 Subject: [PATCH] fbcode_builder: getdeps: add run_cmd() function Summary: This runs a command, raising an exception if it exits with a non-zero error status. It prints out the arguments in a mostly copy-and-pasteable form, with PATH-like env vars pretty printed to make it easier to see what is being invoked; here's an example of how cmake is being invoked later in this stack: ``` --- + CMAKE_PREFIX_PATH=\ + /data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/installed/ninja-5d7ec7:\ + /data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/installed/cmake-91dc9a:\ + PKG_CONFIG_PATH=\ + /data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/installed/ninja-5d7ec7/lib/pkgconfig:\ + /data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/installed/cmake-91dc9a/lib/pkgconfig:\ + cd /data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/build/zstd-470344 && \ + cmake configure /data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/repos/github.com-facebook-zstd.git/build/cmake -DCMAKE_INST ALL_PREFIX=/data/users/wez/scratch/dataZusersZwezZfbsource/fbcode_builder_getdeps/installed/zstd-470344 -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=R elWithDebInfo -G Ninja ``` Reviewed By: simpkins Differential Revision: D14690999 fbshipit-source-id: cdb0c681c7dfdfdc6e8c96bf4830bfbcf666411b --- build/fbcode_builder/getdeps/runcmd.py | 87 ++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 build/fbcode_builder/getdeps/runcmd.py diff --git a/build/fbcode_builder/getdeps/runcmd.py b/build/fbcode_builder/getdeps/runcmd.py new file mode 100644 index 0000000000..476457253b --- /dev/null +++ b/build/fbcode_builder/getdeps/runcmd.py @@ -0,0 +1,87 @@ +# Copyright (c) 2019-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. An additional grant +# of patent rights can be found in the PATENTS file in the same directory. + +from __future__ import absolute_import, division, print_function, unicode_literals + +import os +import subprocess + +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): + print("---") + 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: + print("+ cd %s && \\" % 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:] + + print("+ %s" % cmd_str) + + if allow_fail: + return subprocess.call(cmd, env=env, cwd=cwd) + + try: + return subprocess.check_call(cmd, env=env, cwd=cwd) + except (TypeError, ValueError, OSError) as exc: + raise RunCommandError( + "%s while running `%s` with env=%r\nos.environ=%r" + % (str(exc), cmd_str, env, os.environ) + )