scripts/build_static_binary.py (368 lines of code) (raw):

#!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import argparse import logging import multiprocessing import os import shutil import subprocess import sys import tempfile from pathlib import Path from typing import Optional, List, Dict LOG: logging.Logger = logging.getLogger(__name__) ZLIB_VERSION: str = "1.2.11" ZLIB_URL: str = f"https://downloads.sourceforge.net/project/libpng/zlib/{ZLIB_VERSION}/zlib-{ZLIB_VERSION}.tar.gz" JSONCPP_VERSION: str = "1.9.4" JSONCPP_URL: str = ( f"https://github.com/open-source-parsers/jsoncpp/archive/{JSONCPP_VERSION}.tar.gz" ) GTEST_VERSION: str = "1.10.0" GTEST_URL: str = ( f"https://github.com/google/googletest/archive/release-{GTEST_VERSION}.tar.gz" ) FMT_VERSION: str = "7.1.3" FMT_URL: str = f"https://github.com/fmtlib/fmt/archive/{FMT_VERSION}.tar.gz" RE2_VERSION: str = "2021-04-01" RE2_URL: str = f"https://github.com/google/re2/archive/{RE2_VERSION}.tar.gz" BOOST_VERSION: str = "1.76.0" BOOST_URL: str = f"https://boostorg.jfrog.io/artifactory/main/release/{BOOST_VERSION}/source/boost_{BOOST_VERSION.replace('.', '_')}.tar.bz2" REDEX_URL: str = "https://github.com/facebook/redex.git" def _directory_exists(path: str) -> Path: path = os.path.expanduser(path) if not os.path.isdir(path): raise argparse.ArgumentTypeError(f"Path `{path}` is not a directory.") return Path(path) def _run( command: List[str], cwd: Optional[Path] = None, env: Optional[Dict[str, str]] = None, ) -> "subprocess.CompletedProcess[str]": LOG.info(f"Running {' '.join(command)}") return subprocess.run(command, check=True, text=True, cwd=cwd, env=env) def _download_and_extract( url: str, filename: str, work_directory: Path, extract_directory: Path ) -> None: download_directory = work_directory / "download" download_directory.mkdir(exist_ok=True) _run( ["curl", "-fL", "-o", filename, "--retry", "3", url], cwd=download_directory, ) extract_directory.mkdir() _run( ["tar", "-x", "-C", str(extract_directory), "-f", filename], cwd=download_directory, ) def _flatten_directories(root: Path) -> None: """ Given a path, remove any directory indirection. For instance, if we have a directory hierarchy: /a/b/c/foo /a/b/c/bar /a/b/c/bar/baz Given the path `/a`, we flatten it to: /a/foo /a/bar /a/bar/baz """ subdirectory = root first_subdirectory = None while len(list(subdirectory.iterdir())) == 1: subdirectory = next(subdirectory.iterdir()) if first_subdirectory is None: first_subdirectory = subdirectory if first_subdirectory is None: return for path in subdirectory.iterdir(): path.replace(root / path.name) shutil.rmtree(first_subdirectory) def _parse_arguments() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Build a static binary for Mariana Trench." ) parser.add_argument( "--repository", metavar="PATH", type=_directory_exists, default=".", help="Path to the root of the repository.", ) parser.add_argument( "--jobs", type=int, default=multiprocessing.cpu_count(), help="Number of jobs.", ) parser.add_argument( "--output", metavar="PATH", type=str, default="mariana-trench-binary", help="Path to the output binary.", ) arguments = parser.parse_args() if ( not (arguments.repository / "README.md").exists() and (arguments.repository / "../README.md").exists() ): arguments.repository = (arguments.repository / "..").resolve(strict=True) return arguments def _build_zlib(arguments: argparse.Namespace, work_directory: Path) -> Path: LOG.info(f"Building zib {ZLIB_VERSION}") zlib_build_directory = work_directory / f"build/zlib-{ZLIB_VERSION}" zlib_install_directory = work_directory / f"install/zlib-{ZLIB_VERSION}" _download_and_extract( ZLIB_URL, f"zlip-{ZLIB_VERSION}.tar.gz", work_directory, zlib_build_directory, ) zlib_build_directory /= f"zlib-{ZLIB_VERSION}" _run( ["./configure", f"--prefix={zlib_install_directory}"], cwd=zlib_build_directory, ) _run( ["make", "-j", str(arguments.jobs), "install"], cwd=zlib_build_directory, ) return zlib_install_directory def _build_jsoncpp( arguments: argparse.Namespace, work_directory: Path, ) -> Path: LOG.info(f"Building jsoncpp {JSONCPP_VERSION}") jsoncpp_build_directory = work_directory / f"build/jsoncpp-{JSONCPP_VERSION}" jsoncpp_install_directory = work_directory / f"install/jsoncpp-{JSONCPP_VERSION}" _download_and_extract( JSONCPP_URL, f"jsoncpp-{JSONCPP_VERSION}.tar.gz", work_directory, jsoncpp_build_directory, ) jsoncpp_build_directory /= f"jsoncpp-{JSONCPP_VERSION}" _run( [ "meson", "--buildtype", "release", "--default-library", "static", "--libdir", "lib", ".", "build-static", ], cwd=jsoncpp_build_directory, ) _run( ["ninja", "-v", "-j", str(arguments.jobs)], cwd=jsoncpp_build_directory / "build-static", ) jsoncpp_install_directory.mkdir() _run( ["ninja", "-v", "install"], cwd=jsoncpp_build_directory / "build-static", env={**os.environ, "DESTDIR": str(jsoncpp_install_directory)}, ) _flatten_directories(jsoncpp_install_directory) return jsoncpp_install_directory def _build_boost( arguments: argparse.Namespace, work_directory: Path, ) -> Path: LOG.info(f"Building boost {BOOST_VERSION}") boost_build_directory = work_directory / f"build/boost-{BOOST_VERSION}" boost_install_directory = work_directory / f"install/boost-{BOOST_VERSION}" _download_and_extract( BOOST_URL, f"boost-{BOOST_VERSION}.tar.bz2", work_directory, boost_build_directory, ) boost_build_directory /= f"boost_{BOOST_VERSION.replace('.', '_')}" _run( [ "./bootstrap.sh", f"--prefix={boost_install_directory}", "--without-icu", "--with-libraries=system", "--with-libraries=filesystem", "--with-libraries=regex", "--with-libraries=program_options", "--with-libraries=iostreams", "--with-libraries=thread", ], cwd=boost_build_directory, ) _run( [ "./b2", "-d0", f"-j{arguments.jobs}", "install", "threading=multi", "link=static", "runtime-link=static" if sys.platform != "darwin" else "", "-sNO_LZMA=1", "-sNO_ZSTD=1", "-sNO_BZIP2=1", "-sNO_ZLIB=1", ], cwd=boost_build_directory, ) return boost_install_directory def _build_gtest(arguments: argparse.Namespace, work_directory: Path) -> Path: LOG.info(f"Building gtest {GTEST_VERSION}") gtest_build_directory = work_directory / f"build/gtest-{GTEST_VERSION}" gtest_install_directory = work_directory / f"install/gtest-{GTEST_VERSION}" _download_and_extract( GTEST_URL, f"gtest-{GTEST_VERSION}.tar.gz", work_directory, gtest_build_directory, ) gtest_build_directory /= f"googletest-release-{GTEST_VERSION}" _run( ["cmake", f"-DCMAKE_INSTALL_PREFIX={gtest_install_directory}", "."], cwd=gtest_build_directory, ) _run(["make", f"-j{arguments.jobs}"], cwd=gtest_build_directory) _run(["make", "install"], cwd=gtest_build_directory) return gtest_install_directory def _build_fmt(arguments: argparse.Namespace, work_directory: Path) -> Path: LOG.info(f"Building fmt {FMT_VERSION}") fmt_build_directory = work_directory / f"build/fmt-{FMT_VERSION}" fmt_install_directory = work_directory / f"install/fmt-{FMT_VERSION}" _download_and_extract( FMT_URL, f"fmt-{FMT_VERSION}.tar.gz", work_directory, fmt_build_directory, ) fmt_build_directory /= f"fmt-{FMT_VERSION}" _run( [ "cmake", "-DBUILD_SHARED_LIBS=FALSE", f"-DCMAKE_INSTALL_PREFIX={fmt_install_directory}", ".", ], cwd=fmt_build_directory, ) _run(["make", f"-j{arguments.jobs}"], cwd=fmt_build_directory) _run(["make", "install"], cwd=fmt_build_directory) return fmt_install_directory def _build_re2(arguments: argparse.Namespace, work_directory: Path) -> Path: LOG.info(f"Building re2 {RE2_VERSION}") re2_build_directory = work_directory / f"build/re2-{RE2_VERSION}" re2_install_directory = work_directory / f"install/re2-{RE2_VERSION}" _download_and_extract( RE2_URL, f"re2-{RE2_VERSION}.tar.gz", work_directory, re2_build_directory, ) re2_build_directory /= f"re2-{RE2_VERSION}" _run( [ "cmake", "-DBUILD_SHARED_LIBS=FALSE", "-DRE2_BUILD_TESTING=OFF", f"-DCMAKE_INSTALL_PREFIX={re2_install_directory}", ".", ], cwd=re2_build_directory, ) _run(["make", f"-j{arguments.jobs}"], cwd=re2_build_directory) _run(["make", "install"], cwd=re2_build_directory) return re2_install_directory def _build_redex( arguments: argparse.Namespace, work_directory: Path, zlib: Path, jsoncpp: Path, boost: Path, ) -> Path: LOG.info("Building redex") redex_build_directory = work_directory / "build/redex-master" redex_install_directory = work_directory / "install/redex-master" _run( ["git", "clone", "--depth=1", REDEX_URL, redex_build_directory.name], cwd=redex_build_directory.parent, ) redex_build_directory /= "build" redex_build_directory.mkdir() _run( [ "cmake", "-DBUILD_TYPE=Static", f"-DCMAKE_INSTALL_PREFIX={redex_install_directory}", f"-DZLIB_HOME={zlib}", f"-DJSONCPP_DIR={jsoncpp}", f"-DBOOST_ROOT={boost}", "..", ], cwd=redex_build_directory, ) _run(["make", f"-j{arguments.jobs}"], cwd=redex_build_directory) _run(["make", "install"], cwd=redex_build_directory) return redex_install_directory def _build_mariana_trench( arguments: argparse.Namespace, work_directory: Path, zlib: Path, jsoncpp: Path, gtest: Path, fmt: Path, re2: Path, boost: Path, redex: Path, ) -> Path: LOG.info("Building mariana trench") build_directory = work_directory / "build/mariana-trench" build_directory.mkdir() install_directory = work_directory / "install/mariana-trench" _run( [ "cmake", "-DLINK_TYPE=Static", f"-DCMAKE_INSTALL_PREFIX={install_directory}", f"-DZLIB_ROOT={zlib}", f"-DJSONCPP_DIR={jsoncpp}", f"-DGTest_DIR={gtest}/lib/cmake/GTest", f"-Dfmt_DIR={fmt}/lib/cmake/fmt", f"-Dre2_DIR={re2}/lib/cmake/re2", f"-DBOOST_ROOT={boost}", f"-DREDEX_ROOT={redex}", str(arguments.repository.resolve()), ], cwd=build_directory, ) _run(["make", f"-j{arguments.jobs}"], cwd=build_directory) _run(["make", "install"], cwd=build_directory) return install_directory def main() -> None: logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") arguments = _parse_arguments() if sys.platform not in ("darwin", "linux"): raise AssertionError("This operating system is not currently supported.") with tempfile.TemporaryDirectory(prefix="mt-static-") as directory: work_directory = Path(directory) LOG.info(f"Repository root is `{arguments.repository}`.") LOG.info(f"Using {arguments.jobs} concurrent jobs.") LOG.info(f"Work directory is `{work_directory}`.") build_directory = work_directory / "build" build_directory.mkdir() install_directory = work_directory / "install" install_directory.mkdir() zlib = _build_zlib(arguments, work_directory) jsoncpp = _build_jsoncpp(arguments, work_directory) gtest = _build_gtest(arguments, work_directory) fmt = _build_fmt(arguments, work_directory) re2 = _build_re2(arguments, work_directory) boost = _build_boost(arguments, work_directory) redex = _build_redex(arguments, work_directory, zlib, jsoncpp, boost) mariana_trench = _build_mariana_trench( arguments, work_directory, zlib, jsoncpp, gtest, fmt, re2, boost, redex, ) LOG.info(f"Moving binary to `{arguments.output}`") (mariana_trench / "bin/mariana-trench-binary").rename(Path(arguments.output)) if __name__ == "__main__": main()