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()