2 # Copyright (c) Meta Platforms, Inc. and affiliates.
4 # This source code is licensed under the MIT license found in the
5 # LICENSE file in the root directory of this source tree.
16 from typing
import Optional
18 from .dyndeps
import create_dyn_dep_munger
19 from .envfuncs
import add_path_entry
, Env
, path_search
20 from .fetcher
import copy_if_different
21 from .runcmd
import run_cmd
23 if typing
.TYPE_CHECKING
:
24 from .buildopts
import BuildOptions
27 class BuilderBase(object):
30 build_opts
: "BuildOptions",
37 final_install_prefix
=None,
43 subdir
= manifest
.get("build", "subdir", ctx
=ctx
)
45 src_dir
= os
.path
.join(src_dir
, subdir
)
47 self
.patchfile
= manifest
.get("build", "patchfile", ctx
=ctx
)
48 self
.patchfile_opts
= manifest
.get("build", "patchfile_opts", ctx
=ctx
) or ""
50 self
.src_dir
= src_dir
51 self
.build_dir
= build_dir
or src_dir
52 self
.inst_dir
= inst_dir
53 self
.build_opts
= build_opts
54 self
.manifest
= manifest
55 self
.final_install_prefix
= final_install_prefix
57 def _get_cmd_prefix(self
):
58 if self
.build_opts
.is_windows():
59 vcvarsall
= self
.build_opts
.get_vcvars_path()
60 if vcvarsall
is not None:
61 # Since it sets rather a large number of variables we mildly abuse
62 # the cmd quoting rules to assemble a command that calls the script
63 # to prep the environment and then triggers the actual command that
65 return [vcvarsall
, "amd64", "&&"]
73 use_cmd_prefix
: bool = True,
74 allow_fail
: bool = False,
84 cmd_prefix
= self
._get
_cmd
_prefix
()
86 cmd
= cmd_prefix
+ cmd
88 log_file
= os
.path
.join(self
.build_dir
, "getdeps_build.log")
92 cwd
=cwd
or self
.build_dir
,
94 allow_fail
=allow_fail
,
97 def _reconfigure(self
, reconfigure
: bool) -> bool:
98 if self
.build_dir
is not None:
99 if not os
.path
.isdir(self
.build_dir
):
100 os
.makedirs(self
.build_dir
)
104 def _apply_patchfile(self
) -> None:
105 if self
.patchfile
is None:
107 patched_sentinel_file
= pathlib
.Path(self
.src_dir
+ "/.getdeps_patched")
108 if patched_sentinel_file
.exists():
111 os
.chdir(self
.src_dir
)
112 print(f
"Patching {self.manifest.name} with {self.patchfile} in {self.src_dir}")
113 patchfile
= os
.path
.join(
114 self
.build_opts
.fbcode_builder_dir
, "patches", self
.patchfile
116 patchcmd
= ["git", "apply"]
117 if self
.patchfile_opts
:
118 patchcmd
.append(self
.patchfile_opts
)
120 subprocess
.check_call(patchcmd
+ [patchfile
])
121 except subprocess
.CalledProcessError
:
122 raise ValueError(f
"Failed to apply patch to {self.manifest.name}")
124 patched_sentinel_file
.touch()
126 def prepare(self
, install_dirs
, reconfigure
: bool) -> None:
127 print("Preparing %s..." % self
.manifest
.name
)
128 reconfigure
= self
._reconfigure
(reconfigure
)
129 self
._apply
_patchfile
()
130 self
._prepare
(install_dirs
=install_dirs
, reconfigure
=reconfigure
)
132 def build(self
, install_dirs
, reconfigure
: bool) -> None:
133 print("Building %s..." % self
.manifest
.name
)
134 reconfigure
= self
._reconfigure
(reconfigure
)
135 self
._apply
_patchfile
()
136 self
._prepare
(install_dirs
=install_dirs
, reconfigure
=reconfigure
)
137 self
._build
(install_dirs
=install_dirs
, reconfigure
=reconfigure
)
139 # On Windows, emit a wrapper script that can be used to run build artifacts
140 # directly from the build directory, without installing them. On Windows $PATH
141 # needs to be updated to include all of the directories containing the runtime
142 # library dependencies in order to run the binaries.
143 if self
.build_opts
.is_windows():
144 script_path
= self
.get_dev_run_script_path()
145 dep_munger
= create_dyn_dep_munger(self
.build_opts
, install_dirs
)
146 dep_dirs
= self
.get_dev_run_extra_path_dirs(install_dirs
, dep_munger
)
147 # pyre-fixme[16]: Optional type has no attribute `emit_dev_run_script`.
148 dep_munger
.emit_dev_run_script(script_path
, dep_dirs
)
151 def num_jobs(self
) -> int:
152 # This is a hack, but we don't have a "defaults manifest" that we can
153 # customize per platform.
154 # TODO: Introduce some sort of defaults config that can select by
155 # platform, just like manifest contexts.
156 if sys
.platform
.startswith("freebsd"):
157 # clang on FreeBSD is quite memory-efficient.
158 default_job_weight
= 512
160 # 1.5 GiB is a lot to assume, but it's typical of Facebook-style C++.
161 # Some manifests are even heavier and should override.
162 default_job_weight
= 1536
163 return self
.build_opts
.get_num_jobs(
166 "build", "job_weight_mib", default_job_weight
, ctx
=self
.ctx
172 self
, install_dirs
, schedule_type
, owner
, test_filter
, retry
, no_testpilot
174 """Execute any tests that we know how to run. If they fail,
175 raise an exception."""
178 def _prepare(self
, install_dirs
, reconfigure
) -> None:
179 """Prepare the build. Useful when need to generate config,
180 but builder is not the primary build system.
181 e.g. cargo when called from cmake"""
184 def _build(self
, install_dirs
, reconfigure
) -> None:
185 """Perform the build.
186 install_dirs contains the list of installation directories for
187 the dependencies of this project.
188 reconfigure will be set to true if the fetcher determined
189 that the sources have changed in such a way that the build
190 system needs to regenerate its rules."""
193 def _compute_env(self
, install_dirs
):
194 # CMAKE_PREFIX_PATH is only respected when passed through the
195 # environment, so we construct an appropriate path to pass down
196 return self
.build_opts
.compute_env_for_install_dirs(
197 install_dirs
, env
=self
.env
, manifest
=self
.manifest
200 def get_dev_run_script_path(self
):
201 assert self
.build_opts
.is_windows()
202 return os
.path
.join(self
.build_dir
, "run.ps1")
204 def get_dev_run_extra_path_dirs(self
, install_dirs
, dep_munger
=None):
205 assert self
.build_opts
.is_windows()
206 if dep_munger
is None:
207 dep_munger
= create_dyn_dep_munger(self
.build_opts
, install_dirs
)
208 return dep_munger
.compute_dependency_paths(self
.build_dir
)
211 class MakeBuilder(BuilderBase
):
224 super(MakeBuilder
, self
).__init
__(
225 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
227 self
.build_args
= build_args
or []
228 self
.install_args
= install_args
or []
229 self
.test_args
= test_args
232 def _make_binary(self
):
233 return self
.manifest
.get("build", "make_binary", "make", ctx
=self
.ctx
)
235 def _get_prefix(self
):
236 return ["PREFIX=" + self
.inst_dir
, "prefix=" + self
.inst_dir
]
238 def _build(self
, install_dirs
, reconfigure
) -> None:
240 env
= self
._compute
_env
(install_dirs
)
242 # Need to ensure that PREFIX is set prior to install because
243 # libbpf uses it when generating its pkg-config file.
244 # The lowercase prefix is used by some projects.
246 [self
._make
_binary
, "-j%s" % self
.num_jobs
]
250 self
._run
_cmd
(cmd
, env
=env
)
252 install_cmd
= [self
._make
_binary
] + self
.install_args
+ self
._get
_prefix
()
253 self
._run
_cmd
(install_cmd
, env
=env
)
255 # bz2's Makefile doesn't install its .so properly
256 if self
.manifest
and self
.manifest
.name
== "bz2":
257 libdir
= os
.path
.join(self
.inst_dir
, "lib")
258 srcpattern
= os
.path
.join(self
.src_dir
, "lib*.so.*")
259 print(f
"copying to {libdir} from {srcpattern}")
260 for file in glob
.glob(srcpattern
):
261 shutil
.copy(file, libdir
)
264 self
, install_dirs
, schedule_type
, owner
, test_filter
, retry
, no_testpilot
266 if not self
.test_args
:
269 env
= self
._compute
_env
(install_dirs
)
271 cmd
= [self
._make
_binary
] + self
.test_args
+ self
._get
_prefix
()
272 self
._run
_cmd
(cmd
, env
=env
)
275 class CMakeBootStrapBuilder(MakeBuilder
):
276 def _build(self
, install_dirs
, reconfigure
) -> None:
280 "--prefix=" + self
.inst_dir
,
281 f
"--parallel={self.num_jobs}",
284 super(CMakeBootStrapBuilder
, self
)._build
(install_dirs
, reconfigure
)
287 class AutoconfBuilder(BuilderBase
):
299 super(AutoconfBuilder
, self
).__init
__(
300 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
302 self
.args
= args
or []
303 self
.conf_env_args
= conf_env_args
or {}
306 def _make_binary(self
):
307 return self
.manifest
.get("build", "make_binary", "make", ctx
=self
.ctx
)
309 def _build(self
, install_dirs
, reconfigure
) -> None:
310 configure_path
= os
.path
.join(self
.src_dir
, "configure")
311 autogen_path
= os
.path
.join(self
.src_dir
, "autogen.sh")
313 env
= self
._compute
_env
(install_dirs
)
315 # Some configure scripts need additional env values passed derived from cmds
316 for (k
, cmd_args
) in self
.conf_env_args
.items():
318 subprocess
.check_output(cmd_args
, env
=dict(env
.items()))
325 if not os
.path
.exists(configure_path
):
326 print("%s doesn't exist, so reconfiguring" % configure_path
)
327 # This libtoolize call is a bit gross; the issue is that
328 # `autoreconf` as invoked by libsodium's `autogen.sh` doesn't
329 # seem to realize that it should invoke libtoolize and then
330 # error out when the configure script references a libtool
332 self
._run
_cmd
(["libtoolize"], cwd
=self
.src_dir
, env
=env
)
334 # We generally prefer to call the `autogen.sh` script provided
335 # by the project on the basis that it may know more than plain
337 if os
.path
.exists(autogen_path
):
338 self
._run
_cmd
(["bash", autogen_path
], cwd
=self
.src_dir
, env
=env
)
340 self
._run
_cmd
(["autoreconf", "-ivf"], cwd
=self
.src_dir
, env
=env
)
341 configure_cmd
= [configure_path
, "--prefix=" + self
.inst_dir
] + self
.args
342 self
._run
_cmd
(configure_cmd
, env
=env
)
343 self
._run
_cmd
([self
._make
_binary
, "-j%s" % self
.num_jobs
], env
=env
)
344 self
._run
_cmd
([self
._make
_binary
, "install"], env
=env
)
347 class Iproute2Builder(BuilderBase
):
348 # ./configure --prefix does not work for iproute2.
349 # Thus, explicitly copy sources from src_dir to build_dir, bulid,
350 # and then install to inst_dir using DESTDIR
351 # lastly, also copy include from build_dir to inst_dir
352 def __init__(self
, build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
) -> None:
353 super(Iproute2Builder
, self
).__init
__(
354 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
357 def _patch(self
) -> None:
358 # FBOSS build currently depends on an old version of iproute2 (commit
359 # 7ca63aef7d1b0c808da0040c6b366ef7a61f38c1). This is missing a commit
360 # (ae717baf15fb4d30749ada3948d9445892bac239) needed to build iproute2
361 # successfully. Apply it viz.: include stdint.h
362 # Reference: https://fburl.com/ilx9g5xm
363 with
open(self
.build_dir
+ "/tc/tc_core.c", "r") as f
:
366 with
open(self
.build_dir
+ "/tc/tc_core.c", "w") as f
:
367 f
.write("#include <stdint.h>\n")
370 def _build(self
, install_dirs
, reconfigure
) -> None:
371 configure_path
= os
.path
.join(self
.src_dir
, "configure")
373 env
= self
.env
.copy()
374 self
._run
_cmd
([configure_path
], env
=env
)
375 shutil
.rmtree(self
.build_dir
)
376 shutil
.copytree(self
.src_dir
, self
.build_dir
)
378 self
._run
_cmd
(["make", "-j%s" % self
.num_jobs
], env
=env
)
379 install_cmd
= ["make", "install", "DESTDIR=" + self
.inst_dir
]
381 for d
in ["include", "lib"]:
382 if not os
.path
.isdir(os
.path
.join(self
.inst_dir
, d
)):
384 os
.path
.join(self
.build_dir
, d
), os
.path
.join(self
.inst_dir
, d
)
387 self
._run
_cmd
(install_cmd
, env
=env
)
390 class CMakeBuilder(BuilderBase
):
391 MANUAL_BUILD_SCRIPT
= """\
401 SRC_DIR = {src_dir!r}
402 BUILD_DIR = {build_dir!r}
403 INSTALL_DIR = {install_dir!r}
404 CMD_PREFIX = {cmd_prefix!r}
405 CMAKE_ENV = {env_str}
406 CMAKE_DEFINE_ARGS = {define_args_str}
409 def get_jobs_argument(num_jobs_arg: int) -> str:
411 return "-j" + str(num_jobs_arg)
413 import multiprocessing
414 num_jobs = multiprocessing.cpu_count() // 2
415 return "-j" + str(num_jobs)
419 ap = argparse.ArgumentParser()
422 nargs=argparse.REMAINDER,
423 help='Any extra arguments after an "--" argument will be passed '
428 choices=["configure", "build", "install", "test"],
430 help="The mode to run: configure, build, or install. "
431 "Defaults to configure",
435 action="store_const",
438 help="An alias for --mode=build",
446 help="Run the build or tests with the specified number of parallel jobs",
450 action="store_const",
453 help="An alias for --mode=install",
457 action="store_const",
460 help="An alias for --mode=test",
462 args = ap.parse_args()
464 # Strip off a leading "--" from the additional CMake arguments
465 if args.cmake_args and args.cmake_args[0] == "--":
466 args.cmake_args = args.cmake_args[1:]
470 if args.mode == "configure":
471 full_cmd = CMD_PREFIX + [CMAKE, SRC_DIR] + CMAKE_DEFINE_ARGS + args.cmake_args
472 elif args.mode in ("build", "install"):
473 target = "all" if args.mode == "build" else "install"
474 full_cmd = CMD_PREFIX + [
482 get_jobs_argument(args.num_jobs),
484 elif args.mode == "test":
485 full_cmd = CMD_PREFIX + [
486 {dev_run_script}CTEST,
487 "--output-on-failure",
488 get_jobs_argument(args.num_jobs),
491 ap.error("unknown invocation mode: %s" % (args.mode,))
493 cmd_str = " ".join(full_cmd)
494 print("Running: %r" % (cmd_str,))
495 proc = subprocess.run(full_cmd, env=env, cwd=BUILD_DIR)
496 sys.exit(proc.returncode)
499 if __name__ == "__main__":
513 final_install_prefix
=None,
514 extra_cmake_defines
=None,
516 super(CMakeBuilder
, self
).__init
__(
523 final_install_prefix
=final_install_prefix
,
525 self
.defines
= defines
or {}
526 if extra_cmake_defines
:
527 self
.defines
.update(extra_cmake_defines
)
530 from .facebook
.vcvarsall
import extra_vc_cmake_defines
534 self
.defines
.update(extra_vc_cmake_defines
)
537 if build_opts
.shared_libs
:
538 self
.defines
["BUILD_SHARED_LIBS"] = "ON"
540 def _invalidate_cache(self
) -> None:
543 "CMakeFiles/CMakeError.log",
544 "CMakeFiles/CMakeOutput.log",
546 name
= os
.path
.join(self
.build_dir
, name
)
547 if os
.path
.isdir(name
):
549 elif os
.path
.exists(name
):
552 def _needs_reconfigure(self
) -> bool:
553 for name
in ["CMakeCache.txt", "build.ninja"]:
554 name
= os
.path
.join(self
.build_dir
, name
)
555 if not os
.path
.exists(name
):
559 def _write_build_script(self
, **kwargs
) -> None:
560 env_lines
= [" {!r}: {!r},".format(k
, v
) for k
, v
in kwargs
["env"].items()]
561 kwargs
["env_str"] = "\n".join(["{"] + env_lines
+ ["}"])
563 if self
.build_opts
.is_windows():
564 kwargs
["dev_run_script"] = '"powershell.exe", {!r}, '.format(
565 self
.get_dev_run_script_path()
568 kwargs
["dev_run_script"] = ""
570 define_arg_lines
= ["["]
571 for arg
in kwargs
["define_args"]:
572 # Replace the CMAKE_INSTALL_PREFIX argument to use the INSTALL_DIR
573 # variable that we define in the MANUAL_BUILD_SCRIPT code.
574 if arg
.startswith("-DCMAKE_INSTALL_PREFIX="):
575 value
= " {!r}.format(INSTALL_DIR),".format(
576 "-DCMAKE_INSTALL_PREFIX={}"
579 value
= " {!r},".format(arg
)
580 define_arg_lines
.append(value
)
581 define_arg_lines
.append("]")
582 kwargs
["define_args_str"] = "\n".join(define_arg_lines
)
584 # In order to make it easier for developers to manually run builds for
585 # CMake-based projects, write out some build scripts that can be used to invoke
587 build_script_path
= os
.path
.join(self
.build_dir
, "run_cmake.py")
588 script_contents
= self
.MANUAL_BUILD_SCRIPT
.format(**kwargs
)
589 with
open(build_script_path
, "wb") as f
:
590 f
.write(script_contents
.encode())
591 os
.chmod(build_script_path
, 0o755)
593 def _compute_cmake_define_args(self
, env
):
595 "CMAKE_INSTALL_PREFIX": self
.final_install_prefix
or self
.inst_dir
,
596 "BUILD_SHARED_LIBS": "OFF",
597 # Some of the deps (rsocket) default to UBSAN enabled if left
598 # unspecified. Some of the deps fail to compile in release mode
599 # due to warning->error promotion. RelWithDebInfo is the happy
601 "CMAKE_BUILD_TYPE": "RelWithDebInfo",
603 if "SANDCASTLE" not in os
.environ
:
604 # We sometimes see intermittent ccache related breakages on some
605 # of the FB internal CI hosts, so we prefer to disable ccache
606 # when running in that environment.
607 ccache
= path_search(env
, "ccache")
609 defines
["CMAKE_CXX_COMPILER_LAUNCHER"] = ccache
611 # rocksdb does its own probing for ccache.
612 # Ensure that it is disabled on sandcastle
613 env
["CCACHE_DISABLE"] = "1"
614 # Some sandcastle hosts have broken ccache related dirs, and
615 # even though we've asked for it to be disabled ccache is
616 # still invoked by rocksdb's cmake.
617 # Redirect its config directory to somewhere that is guaranteed
618 # fresh to us, and that won't have any ccache data inside.
619 env
["CCACHE_DIR"] = f
"{self.build_opts.scratch_dir}/ccache"
621 if "GITHUB_ACTIONS" in os
.environ
and self
.build_opts
.is_windows():
622 # GitHub actions: the host has both gcc and msvc installed, and
623 # the default behavior of cmake is to prefer gcc.
624 # Instruct cmake that we want it to use cl.exe; this is important
625 # because Boost prefers cl.exe and the mismatch results in cmake
626 # with gcc not being able to find boost built with cl.exe.
627 defines
["CMAKE_C_COMPILER"] = "cl.exe"
628 defines
["CMAKE_CXX_COMPILER"] = "cl.exe"
630 if self
.build_opts
.is_darwin():
631 # Try to persuade cmake to set the rpath to match the lib
632 # dirs of the dependencies. This isn't automatic, and to
633 # make things more interesting, cmake uses `;` as the path
634 # separator, so translate the runtime path to something
635 # that cmake will parse
636 defines
["CMAKE_INSTALL_RPATH"] = ";".join(
637 env
.get("DYLD_LIBRARY_PATH", "").split(":")
639 # Tell cmake that we want to set the rpath in the tree
640 # at build time. Without this the rpath is only set
641 # at the moment that the binaries are installed. That
642 # default is problematic for example when using the
643 # gtest integration in cmake which runs the built test
644 # executables during the build to discover the set of
646 defines
["CMAKE_BUILD_WITH_INSTALL_RPATH"] = "ON"
648 boost_169_is_required
= False
650 for m
in self
.loader
.manifests_in_dependency_order():
651 preinstalled
= m
.get_section_as_dict("preinstalled.env", self
.ctx
)
652 boost_169_is_required
= "BOOST_ROOT_1_69_0" in preinstalled
.keys()
653 if boost_169_is_required
:
657 boost_169_is_required
658 and self
.build_opts
.allow_system_packages
659 and self
.build_opts
.host_type
.get_package_manager()
660 and self
.build_opts
.host_type
.get_package_manager() == "rpm"
662 # Boost 1.69 rpms don't install cmake config to the system, so to point to them explicitly
663 defines
["BOOST_INCLUDEDIR"] = "/usr/include/boost169"
664 defines
["BOOST_LIBRARYDIR"] = "/usr/lib64/boost169"
666 defines
.update(self
.defines
)
667 define_args
= ["-D%s=%s" % (k
, v
) for (k
, v
) in defines
.items()]
669 # if self.build_opts.is_windows():
670 # define_args += ["-G", "Visual Studio 15 2017 Win64"]
671 define_args
+= ["-G", "Ninja"]
675 def _build(self
, install_dirs
, reconfigure
: bool) -> None:
676 reconfigure
= reconfigure
or self
._needs
_reconfigure
()
678 env
= self
._compute
_env
(install_dirs
)
679 if not self
.build_opts
.is_windows() and self
.final_install_prefix
:
680 env
["DESTDIR"] = self
.inst_dir
682 # Resolve the cmake that we installed
683 cmake
= path_search(env
, "cmake")
685 raise Exception("Failed to find CMake")
688 define_args
= self
._compute
_cmake
_define
_args
(env
)
689 self
._write
_build
_script
(
690 cmd_prefix
=self
._get
_cmd
_prefix
(),
692 ctest
=path_search(env
, "ctest"),
694 define_args
=define_args
,
695 src_dir
=self
.src_dir
,
696 build_dir
=self
.build_dir
,
697 install_dir
=self
.inst_dir
,
701 self
._invalidate
_cache
()
702 self
._run
_cmd
([cmake
, self
.src_dir
] + define_args
, env
=env
)
720 self
, install_dirs
, schedule_type
, owner
, test_filter
, retry
: int, no_testpilot
722 env
= self
._compute
_env
(install_dirs
)
723 ctest
= path_search(env
, "ctest")
724 cmake
= path_search(env
, "cmake")
726 def require_command(path
: Optional
[str], name
: str) -> str:
728 raise RuntimeError("unable to find command `{}`".format(name
))
731 # On Windows, we also need to update $PATH to include the directories that
732 # contain runtime library dependencies. This is not needed on other platforms
733 # since CMake will emit RPATH properly in the binary so they can find these
735 if self
.build_opts
.is_windows():
736 path_entries
= self
.get_dev_run_extra_path_dirs(install_dirs
)
737 path
= env
.get("PATH")
739 path_entries
.insert(0, path
)
740 env
["PATH"] = ";".join(path_entries
)
742 # Don't use the cmd_prefix when running tests. This is vcvarsall.bat on
743 # Windows. vcvarsall.bat is only needed for the build, not tests. It
744 # unfortunately fails if invoked with a long PATH environment variable when
746 use_cmd_prefix
= False
748 def get_property(test
, propname
, defval
=None):
749 """extracts a named property from a cmake test info json blob.
750 The properties look like:
751 [{"name": "WORKING_DIRECTORY"},
752 {"value": "something"}]
753 We assume that it is invalid for the same named property to be
754 listed more than once.
756 props
= test
.get("properties", [])
758 if p
.get("name", None) == propname
:
759 return p
.get("value", defval
)
763 output
= subprocess
.check_output(
764 [require_command(ctest
, "ctest"), "--show-only=json-v1"],
769 data
= json
.loads(output
.decode("utf-8"))
770 except ValueError as exc
:
772 "Failed to decode cmake test info using %s: %s. Output was: %r"
773 % (ctest
, str(exc
), output
)
777 machine_suffix
= self
.build_opts
.host_type
.as_tuple_string()
778 for test
in data
["tests"]:
779 working_dir
= get_property(test
, "WORKING_DIRECTORY")
781 machine_suffix
= self
.build_opts
.host_type
.as_tuple_string()
782 labels
.append("tpx-fb-test-type=3")
783 labels
.append("tpx_test_config::buildsystem=getdeps")
784 labels
.append("tpx_test_config::platform={}".format(machine_suffix
))
786 if get_property(test
, "DISABLED"):
787 labels
.append("disabled")
788 command
= test
["command"]
791 require_command(cmake
, "cmake"),
802 "target": "%s-%s-getdeps-%s"
803 % (self
.manifest
.name
, test
["name"], machine_suffix
),
807 "required_paths": [],
814 if schedule_type
== "continuous" or schedule_type
== "testwarden":
815 # for continuous and testwarden runs, disabling retry can give up
816 # better signals for flaky tests.
819 tpx
= path_search(env
, "tpx")
820 if tpx
and not no_testpilot
:
821 buck_test_info
= list_tests()
824 from .facebook
.testinfra
import start_run
826 buck_test_info_name
= os
.path
.join(self
.build_dir
, ".buck-test-info.json")
827 with
open(buck_test_info_name
, "w") as f
:
828 json
.dump(buck_test_info
, f
)
830 env
.set("http_proxy", "")
831 env
.set("https_proxy", "")
833 from sys
import platform
835 with
start_run(env
["FBSOURCE_HASH"]) as run_id
:
838 "--force-local-execution",
841 "--retry=%d" % retry
,
842 "-j=%s" % str(self
.num_jobs
),
843 "--print-long-results",
847 testpilot_args
+= ["--contacts", owner
]
850 testpilot_args
.append("--env")
851 testpilot_args
.extend(f
"{key}={val}" for key
, val
in env
.items())
853 if run_id
is not None:
854 testpilot_args
+= ["--run-id", run_id
]
857 testpilot_args
+= ["--", test_filter
]
859 if schedule_type
== "diff":
860 runs
.append(["--collection", "oss-diff", "--purpose", "diff"])
861 elif schedule_type
== "continuous":
871 elif schedule_type
== "testwarden":
872 # One run to assess new tests
877 "oss-new-test-stress",
881 "stress-run-new-test",
884 # And another for existing tests
889 "oss-existing-test-stress",
901 testpilot_args
+ run
,
902 cwd
=self
.build_opts
.fbcode_builder_dir
,
904 use_cmd_prefix
=use_cmd_prefix
,
908 require_command(ctest
, "ctest"),
909 "--output-on-failure",
914 args
+= ["-R", test_filter
]
917 while count
<= retry
:
918 retcode
= self
._run
_cmd
(
919 args
, env
=env
, use_cmd_prefix
=use_cmd_prefix
, allow_fail
=True
925 # Only add this option in the second run.
926 args
+= ["--rerun-failed"]
928 # pyre-fixme[61]: `retcode` is undefined, or not always defined.
930 # Allow except clause in getdeps.main to catch and exit gracefully
931 # This allows non-testpilot runs to fail through the same logic as failed testpilot runs, which may become handy in case if post test processing is needed in the future
932 # pyre-fixme[61]: `retcode` is undefined, or not always defined.
933 raise subprocess
.CalledProcessError(retcode
, args
)
936 class NinjaBootstrap(BuilderBase
):
937 def __init__(self
, build_opts
, ctx
, manifest
, build_dir
, src_dir
, inst_dir
) -> None:
938 super(NinjaBootstrap
, self
).__init
__(
939 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
942 def _build(self
, install_dirs
, reconfigure
) -> None:
943 self
._run
_cmd
([sys
.executable
, "configure.py", "--bootstrap"], cwd
=self
.src_dir
)
944 src_ninja
= os
.path
.join(self
.src_dir
, "ninja")
945 dest_ninja
= os
.path
.join(self
.inst_dir
, "bin/ninja")
946 bin_dir
= os
.path
.dirname(dest_ninja
)
947 if not os
.path
.exists(bin_dir
):
949 shutil
.copyfile(src_ninja
, dest_ninja
)
950 shutil
.copymode(src_ninja
, dest_ninja
)
953 class OpenSSLBuilder(BuilderBase
):
954 def __init__(self
, build_opts
, ctx
, manifest
, build_dir
, src_dir
, inst_dir
) -> None:
955 super(OpenSSLBuilder
, self
).__init
__(
956 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
959 def _build(self
, install_dirs
, reconfigure
) -> None:
960 configure
= os
.path
.join(self
.src_dir
, "Configure")
962 # prefer to resolve the perl that we installed from
963 # our manifest on windows, but fall back to the system
965 env
= self
.env
.copy()
966 for d
in install_dirs
:
967 bindir
= os
.path
.join(d
, "bin")
968 add_path_entry(env
, "PATH", bindir
, append
=False)
970 perl
= typing
.cast(str, path_search(env
, "perl", "perl"))
973 if self
.build_opts
.is_windows():
975 args
= ["VC-WIN64A-masm", "-utf-8"]
976 elif self
.build_opts
.is_darwin():
978 make_j_args
= ["-j%s" % self
.num_jobs
]
980 ["darwin64-x86_64-cc"]
981 if not self
.build_opts
.is_arm()
982 else ["darwin64-arm64-cc"]
984 elif self
.build_opts
.is_linux():
986 make_j_args
= ["-j%s" % self
.num_jobs
]
988 ["linux-x86_64"] if not self
.build_opts
.is_arm() else ["linux-aarch64"]
991 raise Exception("don't know how to build openssl for %r" % self
.ctx
)
997 "--prefix=%s" % self
.inst_dir
,
998 "--openssldir=%s" % self
.inst_dir
,
1002 "enable-static-engine",
1009 make_build
= [make
] + make_j_args
1010 self
._run
_cmd
(make_build
)
1011 make_install
= [make
, "install_sw", "install_ssldirs"]
1012 self
._run
_cmd
(make_install
)
1015 class Boost(BuilderBase
):
1017 self
, build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
, b2_args
1019 children
= os
.listdir(src_dir
)
1020 assert len(children
) == 1, "expected a single directory entry: %r" % (children
,)
1021 boost_src
= children
[0]
1022 assert boost_src
.startswith("boost")
1023 src_dir
= os
.path
.join(src_dir
, children
[0])
1024 super(Boost
, self
).__init
__(
1025 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
1027 self
.b2_args
= b2_args
1029 def _build(self
, install_dirs
, reconfigure
) -> None:
1030 env
= self
._compute
_env
(install_dirs
)
1031 linkage
= ["static"]
1032 if self
.build_opts
.is_windows() or self
.build_opts
.shared_libs
:
1033 linkage
.append("shared")
1036 if self
.build_opts
.is_darwin():
1037 clang
= subprocess
.check_output(["xcrun", "--find", "clang"])
1038 user_config
= os
.path
.join(self
.build_dir
, "project-config.jam")
1039 with
open(user_config
, "w") as jamfile
:
1040 jamfile
.write("using clang : : %s ;\n" % clang
.decode().strip())
1041 args
.append("--user-config=%s" % user_config
)
1043 for link
in linkage
:
1044 bootstrap_args
= self
.manifest
.get_section_as_args(
1045 "bootstrap.args", self
.ctx
1047 if self
.build_opts
.is_windows():
1048 bootstrap
= os
.path
.join(self
.src_dir
, "bootstrap.bat")
1049 self
._run
_cmd
([bootstrap
] + bootstrap_args
, cwd
=self
.src_dir
, env
=env
)
1050 args
+= ["address-model=64"]
1052 bootstrap
= os
.path
.join(self
.src_dir
, "bootstrap.sh")
1054 [bootstrap
, "--prefix=%s" % self
.inst_dir
] + bootstrap_args
,
1059 b2
= os
.path
.join(self
.src_dir
, "b2")
1063 "-j%s" % self
.num_jobs
,
1064 "--prefix=%s" % self
.inst_dir
,
1065 "--builddir=%s" % self
.build_dir
,
1071 "runtime-link=shared",
1075 "visibility=global",
1084 class NopBuilder(BuilderBase
):
1085 def __init__(self
, build_opts
, ctx
, manifest
, src_dir
, inst_dir
) -> None:
1086 super(NopBuilder
, self
).__init
__(
1087 build_opts
, ctx
, manifest
, src_dir
, None, inst_dir
1090 def build(self
, install_dirs
, reconfigure
: bool) -> None:
1091 print("Installing %s -> %s" % (self
.src_dir
, self
.inst_dir
))
1092 parent
= os
.path
.dirname(self
.inst_dir
)
1093 if not os
.path
.exists(parent
):
1096 install_files
= self
.manifest
.get_section_as_ordered_pairs(
1097 "install.files", self
.ctx
1100 for src_name
, dest_name
in self
.manifest
.get_section_as_ordered_pairs(
1101 "install.files", self
.ctx
1103 full_dest
= os
.path
.join(self
.inst_dir
, dest_name
)
1104 full_src
= os
.path
.join(self
.src_dir
, src_name
)
1106 dest_parent
= os
.path
.dirname(full_dest
)
1107 if not os
.path
.exists(dest_parent
):
1108 os
.makedirs(dest_parent
)
1109 if os
.path
.isdir(full_src
):
1110 if not os
.path
.exists(full_dest
):
1111 shutil
.copytree(full_src
, full_dest
)
1113 shutil
.copyfile(full_src
, full_dest
)
1114 shutil
.copymode(full_src
, full_dest
)
1115 # This is a bit gross, but the mac ninja.zip doesn't
1116 # give ninja execute permissions, so force them on
1117 # for things that look like they live in a bin dir
1118 if os
.path
.dirname(dest_name
) == "bin":
1119 st
= os
.lstat(full_dest
)
1120 os
.chmod(full_dest
, st
.st_mode | stat
.S_IXUSR
)
1122 if not os
.path
.exists(self
.inst_dir
):
1123 shutil
.copytree(self
.src_dir
, self
.inst_dir
)
1126 class OpenNSABuilder(NopBuilder
):
1127 # OpenNSA libraries are stored with git LFS. As a result, fetcher fetches
1128 # LFS pointers and not the contents. Use git-lfs to pull the real contents
1129 # before copying to install dir using NoopBuilder.
1130 # In future, if more builders require git-lfs, we would consider installing
1131 # git-lfs as part of the sandcastle infra as against repeating similar
1132 # logic for each builder that requires git-lfs.
1133 def __init__(self
, build_opts
, ctx
, manifest
, src_dir
, inst_dir
) -> None:
1134 super(OpenNSABuilder
, self
).__init
__(
1135 build_opts
, ctx
, manifest
, src_dir
, inst_dir
1138 def build(self
, install_dirs
, reconfigure
: bool) -> None:
1139 env
= self
._compute
_env
(install_dirs
)
1140 self
._run
_cmd
(["git", "lfs", "install", "--local"], cwd
=self
.src_dir
, env
=env
)
1141 self
._run
_cmd
(["git", "lfs", "pull"], cwd
=self
.src_dir
, env
=env
)
1143 super(OpenNSABuilder
, self
).build(install_dirs
, reconfigure
)
1146 class SqliteBuilder(BuilderBase
):
1147 def __init__(self
, build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
) -> None:
1148 super(SqliteBuilder
, self
).__init
__(
1149 build_opts
, ctx
, manifest
, src_dir
, build_dir
, inst_dir
1152 def _build(self
, install_dirs
, reconfigure
) -> None:
1153 for f
in ["sqlite3.c", "sqlite3.h", "sqlite3ext.h"]:
1154 src
= os
.path
.join(self
.src_dir
, f
)
1155 dest
= os
.path
.join(self
.build_dir
, f
)
1156 copy_if_different(src
, dest
)
1159 cmake_minimum_required(VERSION 3.1.3 FATAL_ERROR)
1161 add_library(sqlite3 STATIC sqlite3.c)
1162 # These options are taken from the defaults in Makefile.msc in
1163 # the sqlite distribution
1164 target_compile_definitions(sqlite3 PRIVATE
1165 -DSQLITE_ENABLE_COLUMN_METADATA=1
1166 -DSQLITE_ENABLE_FTS3=1
1167 -DSQLITE_ENABLE_RTREE=1
1168 -DSQLITE_ENABLE_GEOPOLY=1
1169 -DSQLITE_ENABLE_JSON1=1
1170 -DSQLITE_ENABLE_STMTVTAB=1
1171 -DSQLITE_ENABLE_DBPAGE_VTAB=1
1172 -DSQLITE_ENABLE_DBSTAT_VTAB=1
1173 -DSQLITE_INTROSPECTION_PRAGMAS=1
1174 -DSQLITE_ENABLE_DESERIALIZE=1
1176 install(TARGETS sqlite3)
1177 install(FILES sqlite3.h sqlite3ext.h DESTINATION include)
1180 with
open(os
.path
.join(self
.build_dir
, "CMakeLists.txt"), "w") as f
:
1181 f
.write(cmake_lists
)
1184 "CMAKE_INSTALL_PREFIX": self
.inst_dir
,
1185 "BUILD_SHARED_LIBS": "ON" if self
.build_opts
.shared_libs
else "OFF",
1186 "CMAKE_BUILD_TYPE": "RelWithDebInfo",
1188 define_args
= ["-D%s=%s" % (k
, v
) for (k
, v
) in defines
.items()]
1189 define_args
+= ["-G", "Ninja"]
1191 env
= self
._compute
_env
(install_dirs
)
1193 # Resolve the cmake that we installed
1194 cmake
= path_search(env
, "cmake")
1196 self
._run
_cmd
([cmake
, self
.build_dir
] + define_args
, env
=env
)