mirror of
https://github.com/facebook/proxygen.git
synced 2025-08-07 07:02:53 +03:00
emit a script to use for running commands from the build directory
Summary: On Windows the build artifacts cannot be easily run directly from the build output directory without installing them. The `$PATH` environment variable needs to be set correctly so that the runtime library dependencies can be found. This updates the builder code to emit a `run.ps1` wrapper script in the build output directory that sets `$PATH` to support running build artifacts directly from the build directory. Additionally, this updates the CMake-specific builder to set properly when running the tests with `ctest`. Reviewed By: wez Differential Revision: D20688290 fbshipit-source-id: 5d0f4d685692bca7e37370bd88309cf7634d87f0
This commit is contained in:
committed by
Facebook GitHub Bot
parent
2c456d68ca
commit
8c46dddb17
@@ -12,6 +12,7 @@ import stat
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from .dyndeps import create_dyn_dep_munger
|
||||
from .envfuncs import Env, add_path_entry, path_search
|
||||
from .fetcher import copy_if_different
|
||||
from .runcmd import run_cmd
|
||||
@@ -56,7 +57,7 @@ class BuilderBase(object):
|
||||
return [vcvarsall, "amd64", "&&"]
|
||||
return []
|
||||
|
||||
def _run_cmd(self, cmd, cwd=None, env=None):
|
||||
def _run_cmd(self, cmd, cwd=None, env=None, use_cmd_prefix=True):
|
||||
if env:
|
||||
e = self.env.copy()
|
||||
e.update(env)
|
||||
@@ -64,9 +65,10 @@ class BuilderBase(object):
|
||||
else:
|
||||
env = self.env
|
||||
|
||||
cmd_prefix = self._get_cmd_prefix()
|
||||
if cmd_prefix:
|
||||
cmd = cmd_prefix + cmd
|
||||
if use_cmd_prefix:
|
||||
cmd_prefix = self._get_cmd_prefix()
|
||||
if cmd_prefix:
|
||||
cmd = cmd_prefix + cmd
|
||||
|
||||
log_file = os.path.join(self.build_dir, "getdeps_build.log")
|
||||
run_cmd(cmd=cmd, env=env, cwd=cwd or self.build_dir, log_file=log_file)
|
||||
@@ -81,6 +83,16 @@ class BuilderBase(object):
|
||||
|
||||
self._build(install_dirs=install_dirs, reconfigure=reconfigure)
|
||||
|
||||
# On Windows, emit a wrapper script that can be used to run build artifacts
|
||||
# directly from the build directory, without installing them. On Windows $PATH
|
||||
# needs to be updated to include all of the directories containing the runtime
|
||||
# library dependencies in order to run the binaries.
|
||||
if self.build_opts.is_windows():
|
||||
script_path = self.get_dev_run_script_path()
|
||||
dep_munger = create_dyn_dep_munger(self.build_opts, install_dirs)
|
||||
dep_dirs = self.get_dev_run_extra_path_dirs(install_dirs, dep_munger)
|
||||
dep_munger.emit_dev_run_script(script_path, dep_dirs)
|
||||
|
||||
def run_tests(self, install_dirs, schedule_type, owner):
|
||||
""" Execute any tests that we know how to run. If they fail,
|
||||
raise an exception. """
|
||||
@@ -100,6 +112,16 @@ class BuilderBase(object):
|
||||
# environment, so we construct an appropriate path to pass down
|
||||
return self.build_opts.compute_env_for_install_dirs(install_dirs, env=self.env)
|
||||
|
||||
def get_dev_run_script_path(self):
|
||||
assert self.build_opts.is_windows()
|
||||
return os.path.join(self.build_dir, "run.ps1")
|
||||
|
||||
def get_dev_run_extra_path_dirs(self, install_dirs, dep_munger=None):
|
||||
assert self.build_opts.is_windows()
|
||||
if dep_munger is None:
|
||||
dep_munger = create_dyn_dep_munger(self.build_opts, install_dirs)
|
||||
return dep_munger.compute_dependency_paths(self.build_dir)
|
||||
|
||||
|
||||
class MakeBuilder(BuilderBase):
|
||||
def __init__(self, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, args):
|
||||
@@ -280,7 +302,7 @@ def main():
|
||||
"Release",
|
||||
] + args.cmake_args
|
||||
elif args.mode == "test":
|
||||
full_cmd = CMD_PREFIX + [CTEST] + args.cmake_args
|
||||
full_cmd = CMD_PREFIX + [{dev_run_script}CTEST] + args.cmake_args
|
||||
else:
|
||||
ap.error("unknown invocation mode: %s" % (args.mode,))
|
||||
|
||||
@@ -335,6 +357,13 @@ if __name__ == "__main__":
|
||||
env_lines = [" {!r}: {!r},".format(k, v) for k, v in kwargs["env"].items()]
|
||||
kwargs["env_str"] = "\n".join(["{"] + env_lines + ["}"])
|
||||
|
||||
if self.build_opts.is_windows():
|
||||
kwargs["dev_run_script"] = '"powershell.exe", {!r}, '.format(
|
||||
self.get_dev_run_script_path()
|
||||
)
|
||||
else:
|
||||
kwargs["dev_run_script"] = ""
|
||||
|
||||
define_arg_lines = ["["]
|
||||
for arg in kwargs["define_args"]:
|
||||
# Replace the CMAKE_INSTALL_PREFIX argument to use the INSTALL_DIR
|
||||
@@ -461,6 +490,23 @@ if __name__ == "__main__":
|
||||
ctest = path_search(env, "ctest")
|
||||
cmake = path_search(env, "cmake")
|
||||
|
||||
# On Windows, we also need to update $PATH to include the directories that
|
||||
# contain runtime library dependencies. This is not needed on other platforms
|
||||
# since CMake will emit RPATH properly in the binary so they can find these
|
||||
# dependencies.
|
||||
if self.build_opts.is_windows():
|
||||
path_entries = self.get_dev_run_extra_path_dirs(install_dirs)
|
||||
path = env.get("PATH")
|
||||
if path:
|
||||
path_entries.insert(0, path)
|
||||
env["PATH"] = ";".join(path_entries)
|
||||
|
||||
# Don't use the cmd_prefix when running tests. This is vcvarsall.bat on
|
||||
# Windows. vcvarsall.bat is only needed for the build, not tests. It
|
||||
# unfortunately fails if invoked with a long PATH environment variable when
|
||||
# running the tests.
|
||||
use_cmd_prefix = False
|
||||
|
||||
def get_property(test, propname, defval=None):
|
||||
""" extracts a named property from a cmake test info json blob.
|
||||
The properties look like:
|
||||
@@ -581,11 +627,13 @@ if __name__ == "__main__":
|
||||
testpilot_args + run,
|
||||
cwd=self.build_opts.fbcode_builder_dir,
|
||||
env=env,
|
||||
use_cmd_prefix=use_cmd_prefix,
|
||||
)
|
||||
else:
|
||||
self._run_cmd(
|
||||
[ctest, "--output-on-failure", "-j", str(self.build_opts.num_jobs)],
|
||||
env=env,
|
||||
use_cmd_prefix=use_cmd_prefix,
|
||||
)
|
||||
|
||||
|
||||
|
@@ -5,10 +5,12 @@
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import errno
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from struct import unpack
|
||||
@@ -16,6 +18,9 @@ from struct import unpack
|
||||
from .envfuncs import path_search
|
||||
|
||||
|
||||
OBJECT_SUBDIRS = ("bin", "lib", "lib64")
|
||||
|
||||
|
||||
def copyfile(src, dest):
|
||||
shutil.copyfile(src, dest)
|
||||
shutil.copymode(src, dest)
|
||||
@@ -56,7 +61,7 @@ class DepBase(object):
|
||||
inst_dir = self.install_dirs[-1]
|
||||
print("Process deps under %s" % inst_dir, file=sys.stderr)
|
||||
|
||||
for dir in ["bin", "lib", "lib64"]:
|
||||
for dir in OBJECT_SUBDIRS:
|
||||
src_dir = os.path.join(inst_dir, dir)
|
||||
if not os.path.isdir(src_dir):
|
||||
continue
|
||||
@@ -70,6 +75,23 @@ class DepBase(object):
|
||||
copyfile(os.path.join(src_dir, objfile), dest_obj)
|
||||
self.munge_in_place(dest_obj, final_lib_dir)
|
||||
|
||||
def find_all_dependencies(self, build_dir):
|
||||
all_deps = set()
|
||||
for objfile in self.list_objs_in_dir(
|
||||
build_dir, recurse=True, output_prefix=build_dir
|
||||
):
|
||||
for d in self.list_dynamic_deps(objfile):
|
||||
all_deps.add(d)
|
||||
|
||||
interesting_deps = {d for d in all_deps if self.interesting_dep(d)}
|
||||
dep_paths = []
|
||||
for dep in interesting_deps:
|
||||
dep_path = self.resolve_loader_path(dep)
|
||||
if dep_path:
|
||||
dep_paths.append(dep_path)
|
||||
|
||||
return dep_paths
|
||||
|
||||
def munge_in_place(self, objfile, final_lib_dir):
|
||||
print("Munging %s" % objfile)
|
||||
for d in self.list_dynamic_deps(objfile):
|
||||
@@ -97,19 +119,26 @@ class DepBase(object):
|
||||
return dep
|
||||
d = os.path.basename(dep)
|
||||
for inst_dir in self.install_dirs:
|
||||
for libdir in ["bin", "lib", "lib64"]:
|
||||
for libdir in OBJECT_SUBDIRS:
|
||||
candidate = os.path.join(inst_dir, libdir, d)
|
||||
if os.path.exists(candidate):
|
||||
return candidate
|
||||
return None
|
||||
|
||||
def list_objs_in_dir(self, dir):
|
||||
objs = []
|
||||
for d in os.listdir(dir):
|
||||
if self.is_objfile(os.path.join(dir, d)):
|
||||
objs.append(os.path.normcase(d))
|
||||
|
||||
return objs
|
||||
def list_objs_in_dir(self, dir, recurse=False, output_prefix=""):
|
||||
for entry in os.listdir(dir):
|
||||
entry_path = os.path.join(dir, entry)
|
||||
st = os.lstat(entry_path)
|
||||
if stat.S_ISREG(st.st_mode):
|
||||
if self.is_objfile(entry_path):
|
||||
relative_result = os.path.join(output_prefix, entry)
|
||||
yield os.path.normcase(relative_result)
|
||||
elif recurse and stat.S_ISDIR(st.st_mode):
|
||||
child_prefix = os.path.join(output_prefix, entry)
|
||||
for result in self.list_objs_in_dir(
|
||||
entry_path, recurse=recurse, output_prefix=child_prefix
|
||||
):
|
||||
yield result
|
||||
|
||||
def is_objfile(self, objfile):
|
||||
return True
|
||||
@@ -194,6 +223,89 @@ class WinDeps(DepBase):
|
||||
return True
|
||||
return False
|
||||
|
||||
def emit_dev_run_script(self, script_path, dep_dirs):
|
||||
"""Emit a script that can be used to run build artifacts directly from the
|
||||
build directory, without installing them.
|
||||
|
||||
The dep_dirs parameter should be a list of paths that need to be added to $PATH.
|
||||
This can be computed by calling compute_dependency_paths() or
|
||||
compute_dependency_paths_fast().
|
||||
|
||||
This is only necessary on Windows, which does not have RPATH, and instead
|
||||
requires the $PATH environment variable be updated in order to find the proper
|
||||
library dependencies.
|
||||
"""
|
||||
contents = self._get_dev_run_script_contents(dep_dirs)
|
||||
with open(script_path, "w") as f:
|
||||
f.write(contents)
|
||||
|
||||
def compute_dependency_paths(self, build_dir):
|
||||
"""Return a list of all directories that need to be added to $PATH to ensure
|
||||
that library dependencies can be found correctly. This is computed by scanning
|
||||
binaries to determine exactly the right list of dependencies.
|
||||
|
||||
The compute_dependency_paths_fast() is a alternative function that runs faster
|
||||
but may return additional extraneous paths.
|
||||
"""
|
||||
dep_dirs = set()
|
||||
# Find paths by scanning the binaries.
|
||||
for dep in self.find_all_dependencies(build_dir):
|
||||
dep_dirs.add(os.path.dirname(dep))
|
||||
|
||||
dep_dirs.update(self.read_custom_dep_dirs(build_dir))
|
||||
return sorted(dep_dirs)
|
||||
|
||||
def compute_dependency_paths_fast(self, build_dir):
|
||||
"""Similar to compute_dependency_paths(), but rather than actually scanning
|
||||
binaries, just add all library paths from the specified installation
|
||||
directories. This is much faster than scanning the binaries, but may result in
|
||||
more paths being returned than actually necessary.
|
||||
"""
|
||||
dep_dirs = set()
|
||||
for inst_dir in self.install_dirs:
|
||||
for subdir in OBJECT_SUBDIRS:
|
||||
path = os.path.join(inst_dir, subdir)
|
||||
if os.path.exists(path):
|
||||
dep_dirs.add(path)
|
||||
|
||||
dep_dirs.update(self.read_custom_dep_dirs(build_dir))
|
||||
return sorted(dep_dirs)
|
||||
|
||||
def read_custom_dep_dirs(self, build_dir):
|
||||
# The build system may also have included libraries from other locations that
|
||||
# we might not be able to find normally in find_all_dependencies().
|
||||
# To handle this situation we support reading additional library paths
|
||||
# from a LIBRARY_DEP_DIRS.txt file that may have been generated in the build
|
||||
# output directory.
|
||||
dep_dirs = set()
|
||||
try:
|
||||
explicit_dep_dirs_path = os.path.join(build_dir, "LIBRARY_DEP_DIRS.txt")
|
||||
with open(explicit_dep_dirs_path, "r") as f:
|
||||
for line in f.read().splitlines():
|
||||
dep_dirs.add(line)
|
||||
except OSError as ex:
|
||||
if ex.errno != errno.ENOENT:
|
||||
raise
|
||||
|
||||
return dep_dirs
|
||||
|
||||
def _get_dev_run_script_contents(self, path_dirs):
|
||||
path_entries = ["$env:PATH"] + path_dirs
|
||||
path_str = ";".join(path_entries)
|
||||
return """\
|
||||
$orig_env = $env:PATH
|
||||
$env:PATH = "{path_str}"
|
||||
|
||||
try {{
|
||||
$cmd_args = $args[1..$args.length]
|
||||
& $args[0] @cmd_args
|
||||
}} finally {{
|
||||
$env:PATH = $orig_env
|
||||
}}
|
||||
""".format(
|
||||
path_str=path_str
|
||||
)
|
||||
|
||||
|
||||
class ElfDeps(DepBase):
|
||||
def __init__(self, buildopts, install_dirs):
|
||||
|
Reference in New Issue
Block a user