diff --git a/build/fbcode_builder/getdeps.py b/build/fbcode_builder/getdeps.py index cb013f1ba..506c402ce 100755 --- a/build/fbcode_builder/getdeps.py +++ b/build/fbcode_builder/getdeps.py @@ -153,10 +153,9 @@ class BuildCmd(SubCmd): reconfigure = change_status.build_changed() sources_changed = change_status.sources_changed() - hash = fetcher.hash() - directory = "%s-%s" % (m.name, hash) - build_dir = os.path.join(opts.scratch_dir, "build", directory) - inst_dir = os.path.join(opts.scratch_dir, "installed", directory) + dirs = opts.compute_dirs(m, fetcher) + build_dir = dirs["build_dir"] + inst_dir = dirs["inst_dir"] built_marker = os.path.join(inst_dir, ".built-by-getdeps") if os.path.exists(built_marker): @@ -175,7 +174,7 @@ class BuildCmd(SubCmd): builder.build(install_dirs, reconfigure=reconfigure) with open(built_marker, "w") as f: - f.write(hash) + f.write(dirs["hash"]) install_dirs.append(inst_dir) @@ -198,6 +197,56 @@ class BuildCmd(SubCmd): ) +@cmd("test", "test a given project") +class TestCmd(SubCmd): + def run(self, args): + opts = setup_build_options(args) + manifest = load_project(opts, args.project) + + ctx = context_from_host_tuple() + projects = manifests_in_dependency_order(opts, manifest, ctx) + + # Accumulate the install directories so that the test steps + # can find their dep installation + install_dirs = [] + + for m in projects: + fetcher = m.create_fetcher(opts, ctx) + + dirs = opts.compute_dirs(m, fetcher) + build_dir = dirs["build_dir"] + inst_dir = dirs["inst_dir"] + + if m == manifest or args.test_all: + built_marker = os.path.join(inst_dir, ".built-by-getdeps") + if not os.path.exists(built_marker): + print("project %s has not been built" % m.name) + # TODO: we could just go ahead and build it here, but I + # want to tackle that as part of adding build-for-test + # support. + return 1 + src_dir = fetcher.get_src_dir() + builder = m.create_builder(opts, src_dir, build_dir, inst_dir, ctx) + builder.run_tests(install_dirs) + + install_dirs.append(inst_dir) + + def setup_parser(self, parser): + parser.add_argument( + "project", + help=( + "name of the project or path to a manifest " + "file describing the project" + ), + ) + parser.add_argument( + "--test-all", + action="store_true", + default=False, + help="Enable running tests for the named project and all of its deps", + ) + + def build_argparser(): common_args = argparse.ArgumentParser(add_help=False) common_args.add_argument( diff --git a/build/fbcode_builder/getdeps/builder.py b/build/fbcode_builder/getdeps/builder.py index bf7c18cfc..8270c159d 100644 --- a/build/fbcode_builder/getdeps/builder.py +++ b/build/fbcode_builder/getdeps/builder.py @@ -83,6 +83,11 @@ class BuilderBase(object): self._build(install_dirs=install_dirs, reconfigure=reconfigure) + def run_tests(self, install_dirs): + """ Execute any tests that we know how to run. If they fail, + raise an exception. """ + pass + def _build(self, install_dirs, reconfigure): """ Perform the build. install_dirs contains the list of installation directories for @@ -171,6 +176,43 @@ class CMakeBuilder(BuilderBase): return True return False + def _compute_env(self, install_dirs): + # CMAKE_PREFIX_PATH is only respected when passed through the + # environment, so we construct an appropriate path to pass down + env = self.env.copy() + + lib_path = None + if self.build_opts.is_darwin(): + lib_path = "DYLD_LIBRARY_PATH" + elif self.build_opts.is_linux(): + lib_path = "LD_LIBRARY_PATH" + else: + lib_path = None + + for d in install_dirs: + add_path_entry(env, "CMAKE_PREFIX_PATH", d) + + pkgconfig = os.path.join(d, "lib/pkgconfig") + if os.path.exists(pkgconfig): + add_path_entry(env, "PKG_CONFIG_PATH", pkgconfig) + + # Allow resolving shared objects built earlier (eg: zstd + # doesn't include the full path to the dylib in its linkage + # so we need to give it an assist) + if lib_path: + for lib in ["lib", "lib64"]: + libdir = os.path.join(d, lib) + if os.path.exists(libdir): + add_path_entry(env, lib_path, libdir) + + # Allow resolving binaries (eg: cmake, ninja) and dlls + # built by earlier steps + bindir = os.path.join(d, "bin") + if os.path.exists(bindir): + add_path_entry(env, "PATH", bindir, append=False) + + return env + def _build(self, install_dirs, reconfigure): reconfigure = reconfigure or self._needs_reconfigure() @@ -183,6 +225,24 @@ class CMakeBuilder(BuilderBase): # medium. "CMAKE_BUILD_TYPE": "RelWithDebInfo", } + env = self._compute_env(install_dirs) + if self.build_opts.is_darwin(): + # Try to persuade cmake to set the rpath to match the lib + # dirs of the dependencies. This isn't automatic, and to + # make things more interesting, cmake uses `;` as the path + # separator, so translate the runtime path to something + # that cmake will parse + defines["CMAKE_INSTALL_RPATH"] = ";".join( + env.get("DYLD_LIBRARY_PATH", "").split(":") + ) + # Tell cmake that we want to set the rpath in the tree + # at build time. Without this the rpath is only set + # at the moment that the binaries are installed. That + # default is problematic for example when using the + # gtest integration in cmake which runs the built test + # executables during the build to discover the set of + # tests. + defines["CMAKE_BUILD_WITH_INSTALL_RPATH"] = "ON" defines.update(self.defines) define_args = ["-D%s=%s" % (k, v) for (k, v) in defines.items()] @@ -191,16 +251,6 @@ class CMakeBuilder(BuilderBase): # define_args += ["-G", "Visual Studio 15 2017 Win64"] define_args += ["-G", "Ninja"] - # CMAKE_PREFIX_PATH is only respected when passed through the - # environment, so we construct an appropriate path to pass down - env = self.env.copy() - for d in install_dirs: - add_path_entry(env, "CMAKE_PREFIX_PATH", d) - add_path_entry(env, "PKG_CONFIG_PATH", "%s/lib/pkgconfig" % d) - - bindir = os.path.join(d, "bin") - add_path_entry(env, "PATH", bindir, append=False) - # Resolve the cmake that we installed cmake = path_search(env, "cmake") @@ -223,6 +273,13 @@ class CMakeBuilder(BuilderBase): env=env, ) + def run_tests(self, install_dirs): + env = self._compute_env(install_dirs) + ctest = path_search(env, "ctest") + self._run_cmd( + [ctest, "--output-on-failure", "-j", str(self.build_opts.num_jobs)], env=env + ) + class NinjaBootstrap(BuilderBase): def __init__(self, build_opts, ctx, manifest, build_dir, src_dir, inst_dir): diff --git a/build/fbcode_builder/getdeps/buildopts.py b/build/fbcode_builder/getdeps/buildopts.py index da6cdca08..021cbb491 100644 --- a/build/fbcode_builder/getdeps/buildopts.py +++ b/build/fbcode_builder/getdeps/buildopts.py @@ -89,6 +89,14 @@ class BuildOptions(object): def is_linux(self): return self.host_type.is_linux() + def compute_dirs(self, manifest, fetcher): + hash = fetcher.hash() + directory = "%s-%s" % (manifest.name, hash) + build_dir = os.path.join(self.scratch_dir, "build", directory) + inst_dir = os.path.join(self.scratch_dir, "installed", directory) + + return {"build_dir": build_dir, "inst_dir": inst_dir, "hash": hash} + def list_win32_subst_letters(): output = subprocess.check_output(["subst"]).decode("utf-8") diff --git a/build/fbcode_builder/manifests/folly b/build/fbcode_builder/manifests/folly index f28cf2eba..1ac3e77c6 100644 --- a/build/fbcode_builder/manifests/folly +++ b/build/fbcode_builder/manifests/folly @@ -49,3 +49,4 @@ fbcode/folly = folly [cmake.defines] BUILD_SHARED_LIBS=OFF +BUILD_TESTS=ON diff --git a/build/fbcode_builder/manifests/wangle b/build/fbcode_builder/manifests/wangle index 394291cbb..876c2dbf3 100644 --- a/build/fbcode_builder/manifests/wangle +++ b/build/fbcode_builder/manifests/wangle @@ -12,7 +12,7 @@ builder = cmake subdir = wangle [cmake.defines] -BUILD_TESTS = OFF +BUILD_TESTS = ON [dependencies] folly