# -*- mode: python; -*- import atexit import copy import errno import functools import json import os import pathlib import platform import re import shlex import shutil import subprocess import sys import textwrap import uuid from datetime import datetime from glob import glob import SCons import SCons.Script from mongo_tooling_metrics.lib.top_level_metrics import SConsToolingMetrics from pkg_resources import parse_version from site_scons.mongo import build_profiles # This must be first, even before EnsureSConsVersion, if # we are to avoid bulk loading all tools in the DefaultEnvironment. DefaultEnvironment(tools=[]) # These come from site_scons/mongo. Import these things # after calling DefaultEnvironment, for the sake of paranoia. import mongo import mongo.generators as mongo_generators import mongo.install_actions as install_actions import mongo.platform as mongo_platform import mongo.toolchain as mongo_toolchain EnsurePythonVersion(3, 10) EnsureSConsVersion(3, 1, 1) utc_starttime = datetime.utcnow() # Monkey patch SCons.FS.File.release_target_info to be a no-op. # See https://github.com/SCons/scons/issues/3454 def release_target_info_noop(self): pass SCons.Node.FS.File.release_target_info = release_target_info_noop import psutil from buildscripts import moduleconfig, utils scons_invocation = "{} {}".format(sys.executable, " ".join(sys.argv)) print("scons: running with args {}".format(scons_invocation)) atexit.register(mongo.print_build_failures) # An extra instance of the SCons parser is used to manually validate options # flags. We use it detect some common misspellings/unknown options and # communicate with the user more effectively than just allowing Configure to # fail. # This is to work around issue #4187 # (https://github.com/SCons/scons/issues/4187). Upon a future upgrade to SCons # that incorporates #4187, we should replace this solution with that. _parser = SCons.Script.SConsOptions.Parser("") def add_option(name, **kwargs): _parser.add_option("--" + name, **{"default": None, **kwargs}) if "dest" not in kwargs: kwargs["dest"] = name if "metavar" not in kwargs and kwargs.get("type", None) == "choice": kwargs["metavar"] = "[" + "|".join(kwargs["choices"]) + "]" AddOption("--" + name, **kwargs) def get_option(name): return GetOption(name) def has_option(name): optval = GetOption(name) # Options with nargs=0 are true when their value is the empty tuple. Otherwise, # if the value is falsish (empty string, None, etc.), coerce to False. return True if optval == () else bool(optval) def use_system_version_of_library(name): # Disabled during Bazel migration return False def using_system_version_of_cxx_libraries(): # Disabled during Bazel migration return False def make_variant_dir_generator(): memoized_variant_dir = [False] def generate_variant_dir(target, source, env, for_signature): if not memoized_variant_dir[0]: memoized_variant_dir[0] = env.subst("$BUILD_ROOT/$VARIANT_DIR") return memoized_variant_dir[0] return generate_variant_dir # Always randomize the build order to shake out missing edges, and to help the cache: # http://scons.org/doc/production/HTML/scons-user/ch24s06.html SetOption("random", 1) # Options TODOs: # # - We should either alphabetize the entire list of options, or split them into logical groups # with clear boundaries, and then alphabetize the groups. There is no way in SCons though to # inform it of options groups. # # - Many of these options are currently only either present or absent. This is not good for # scripting the build invocation because it means you need to interpolate in the presence of # the whole option. It is better to make all options take an optional on/off or true/false # using the nargs='const' mechanism. # add_option( "build-profile", choices=[type for type in build_profiles.BuildProfileType], default=build_profiles.BuildProfileType.DEFAULT, type="choice", help="""Short hand for common build configurations. These profiles are well supported by the build and are kept up to date. The 'default' profile should be used unless you have the required prerequisites in place to use the other profiles, i.e. having the mongodbtoolchain installed and being connected to an icecream cluster. For mongodb developers, it is recommended to use the 'san' (sanitizer) profile to identify bugs as soon as possible. Check out site_scons/mongo/build_profiles.py to see each profile.""", ) add_option( "evergreen-tmp-dir", help="Configures the path to the evergreen configured tmp directory.", default=None, ) # Preload to perform early fetch fo repositories tool = Tool("integrate_bazel") tool.exists(DefaultEnvironment()) mongo_toolchain_execroot = DefaultEnvironment().PrefetchToolchain() build_profile = build_profiles.get_build_profile(get_option("build-profile")) add_option( "ninja", choices=["enabled", "disabled"], default=build_profile.ninja, nargs="?", const="enabled", type="choice", help="Enable the build.ninja generator tool stable or canary version", ) add_option( "force-jobs", help="Allow more jobs than available cpu's when icecream is not enabled.", nargs=0, ) add_option( "build-tools", choices=["stable", "next"], default="stable", type="choice", help="Enable experimental build tools", ) add_option( "legacy-tarball", choices=["true", "false"], default="false", const="true", nargs="?", type="choice", help="Build a tarball matching the old MongoDB dist targets", ) add_option( "lint-scope", choices=["all", "changed"], default="all", type="choice", help="Lint files in the current git diff instead of all files", ) add_option( "bazel-includes-info", action="append", help="write included headers in bazel label format to put files ([library].bazel_includes)", default=[], ) add_option( "install-mode", choices=["hygienic"], default="hygienic", help="select type of installation", nargs=1, type="choice", ) add_option( "install-action", choices=([*install_actions.available_actions] + ["default"]), default="hardlink", help="select mechanism to use to install files (advanced option to reduce disk IO and utilization)", nargs=1, type="choice", ) add_option( "build-dir", default="#build", help="build output directory", ) add_option( "release", choices=["on", "off"], const="on", default=build_profile.release, help="release build", nargs="?", type="choice", ) add_option( "remote-exec-release", choices=["on", "off"], const="off", default=build_profile.remote_exec_release, help="Turn on bazel remote execution for release", nargs="?", type="choice", ) add_option( "lto", help="enable full link time optimizations (experimental, except with MSVC)", nargs=0, ) add_option( "thin-lto", help="enable thin link time optimizations (experimental)", nargs=0, ) add_option( "endian", choices=["big", "little", "auto"], default="auto", help="endianness of target platform", nargs=1, type="choice", ) add_option( "disable-minimum-compiler-version-enforcement", help="allow use of unsupported older compilers (NEVER for production builds)", nargs=0, ) add_option( "ssl", help="Enable or Disable SSL", choices=["on", "off"], default="on", const="on", nargs="?", type="choice", ) add_option( "wiredtiger", choices=["on", "off"], const="on", default="on", help="Enable wiredtiger", nargs="?", type="choice", ) add_option( "ocsp-stapling", choices=["on", "off"], default="on", help="Enable OCSP Stapling on servers", nargs="?", type="choice", ) js_engine_choices = ["mozjs", "none"] add_option( "js-engine", choices=js_engine_choices, default=js_engine_choices[0], help="JavaScript scripting engine implementation", type="choice", ) add_option( "server-js", choices=["on", "off"], default="on", help="Build mongod without JavaScript support", type="choice", ) add_option( "libc++", help="use libc++ (experimental, requires clang)", nargs=0, ) add_option( "use-glibcxx-debug", help="Enable the glibc++ debug implementations of the C++ standard libary", nargs=0, ) add_option( "noshell", help="don't build shell", nargs=0, ) add_option( "dbg", choices=["on", "off"], const="on", default=build_profile.dbg, help="Enable runtime debugging checks", nargs="?", type="choice", ) add_option( "debug-symbols", choices=["on", "off"], const="on", default=build_profile.debug_symbols, help="Enable producing debug symbols", nargs="?", type="choice", ) add_option( "disable-ref-track", help="Disables runtime tracking of REF state changes for pages within wiredtiger. " "Tracking the REF state changes is useful for debugging but there is a small performance cost.", nargs=0, ) add_option( "separate-debug", choices=["on", "off"], const="on", default="off", help="Produce separate debug files", nargs="?", type="choice", ) add_option( "spider-monkey-dbg", choices=["on", "off"], const="on", default="off", help="Enable SpiderMonkey debug mode", nargs="?", type="choice", ) add_option( "opt", choices=["on", "debug", "size", "off", "auto"], const="on", default=build_profile.opt, help="Enable compile-time optimization", nargs="?", type="choice", ) experimental_optimizations = [ "O3", "builtin-memcmp", "fnsi", "nofp", "nordyn", "sandybridge", "tbaa", "treevec", "vishidden", ] experimental_optimization_choices = ["*"] experimental_optimization_choices.extend("+" + opt for opt in experimental_optimizations) experimental_optimization_choices.extend("-" + opt for opt in experimental_optimizations) add_option( "experimental-optimization", action="append", choices=experimental_optimization_choices, const=experimental_optimization_choices[0], default=["+sandybridge"], help="Enable experimental optimizations", nargs="?", type="choice", ) add_option( "debug-compress", action="append", choices=["off", "as", "ld"], default=["auto"], help="Compress debug sections", ) add_option( "coverity-build", action="store_true", default=False, help=( "Enable coverity build mode, which only means the bazel build will not run. " "The bazel build is expected to be run in a prior separate coverity enabled bazel build." ), ) add_option( "sanitize", help="enable selected sanitizers", metavar="san1,san2,...sanN", default=build_profile.sanitize, ) add_option( "sanitize-coverage", help="enable selected coverage sanitizers", metavar="cov1,cov2,...covN", ) add_option( "shared-libsan", choices=["on", "off"], default="off", nargs="?", const="on", help="dynamically link to sanitizer runtime(s)", type="choice", ) add_option( "allocator", choices=["auto", "system", "tcmalloc-google", "tcmalloc-gperf"], default=build_profile.allocator, help='allocator to use (use "auto" for best choice for current platform)', type="choice", ) add_option( "gdbserver", help="build in gdb server support", nargs=0, ) add_option( "lldb-server", help="build in lldb server support", nargs=0, ) add_option( "wait-for-debugger", help="Wait for debugger attach on process startup", nargs=0, ) add_option( "gcov", help="compile with flags for gcov", nargs=0, ) add_option( "pgo-profile", help="compile with pgo profiling", nargs=0, ) add_option( "pgo", help="compile with pgo. Assumes profile file default.profdata at root of repository", nargs=0, ) add_option( "enable-http-client", choices=["auto", "on", "off"], default="auto", help="Enable support for HTTP client requests (required WinHTTP or cURL)", type="choice", ) add_option( "consolidated-test-bins", choices=["on", "off"], default="off", help="Test binaries should build consolidated versions of themselves as defined by CONSOLIDATED_TARGET", type="choice", ) add_option( "use-sasl-client", help="Support SASL authentication in the client library", nargs=0, ) add_option( "use-tracing-profiler", choices=["on", "off"], default="off", help="Enable tracing profiler statistic collection", type="choice", ) add_option( "build-fast-and-loose", choices=["on", "off", "auto"], const="on", default="auto", help="looser dependency checking", nargs="?", type="choice", ) add_option( "disable-warnings-as-errors", action="append", choices=["configure", "source"], const="source", default=build_profile.disable_warnings_as_errors, help="Don't add a warnings-as-errors flag to compiler command lines in selected contexts; defaults to 'source' if no argument is provided", nargs="?", type="choice", ) add_option( "detect-odr-violations", help="Have the linker try to detect ODR violations, if supported", nargs=0, ) add_option( "variables-help", help="Print the help text for SCons variables", nargs=0, ) add_option( "osx-version-min", help="minimum OS X version to support", ) # https://docs.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=vs-2017 # https://docs.microsoft.com/en-us/windows-server/get-started/windows-server-release-info win_version_min_choices = { "win10": ("0A00", "0000"), "ws2016": ("0A00", "1607"), "ws2019": ("0A00", "1809"), } add_option( "win-version-min", choices=list(win_version_min_choices.keys()), default=None, help="minimum Windows version to support", type="choice", ) add_option( "cache", choices=["all", "nolinked"], const="all", help="Use an object cache rather than a per-build variant directory (experimental)", nargs="?", ) add_option( "cache-dir", default="$BUILD_ROOT/scons/cache", help="Specify the directory to use for caching objects if --cache is in use", ) add_option( "cache-signature-mode", choices=["none", "validate"], default="none", help="Extra check to validate integrity of cache files after pulling from cache", ) add_option( "cxx-std", choices=["20"], default="20", help="Select the C++ language standard to build with", ) def find_mongo_custom_variables(): files = [] paths = [path for path in sys.path if "site_scons" in path] for path in paths: probe = os.path.join(path, "mongo_custom_variables.py") if os.path.isfile(probe): files.append(probe) return files add_option( "variables-files", default=build_profile.variables_files, action="append", help="Specify variables files to load.", ) add_option( "streams-release-build", default=False, action="store_true", help="If set, will include the enterprise streams module in a release build.", ) link_model_choices = ["auto", "object", "static", "dynamic", "dynamic-strict", "dynamic-sdk"] add_option( "link-model", choices=link_model_choices, default=build_profile.link_model, help="Select the linking model for the project", type="choice", ) add_option( "linker", choices=["auto", "gold", "lld", "bfd"], default="auto", help="Specify the type of linker to use.", type="choice", ) variable_parse_mode_choices = ["auto", "posix", "other"] add_option( "variable-parse-mode", choices=variable_parse_mode_choices, default=variable_parse_mode_choices[0], help="Select which parsing mode is used to interpret command line variables", type="choice", ) add_option( "modules", help="Comma-separated list of modules to build. Empty means none. Default is all.", ) add_option( "runtime-hardening", choices=["on", "off"], default="on", help="Enable runtime hardening features (e.g. stack smash protection)", type="choice", ) experimental_runtime_hardenings = [ "cfex", "controlflow", "stackclash", ] experimental_runtime_hardening_choices = ["*"] experimental_runtime_hardening_choices.extend("+" + opt for opt in experimental_runtime_hardenings) experimental_runtime_hardening_choices.extend("-" + opt for opt in experimental_runtime_hardenings) add_option( "experimental-runtime-hardening", action="append", choices=experimental_runtime_hardening_choices, const=experimental_runtime_hardening_choices[0], default=[], help="Enable experimental runtime hardenings", nargs="?", type="choice", ) add_option( "use-hardware-crc32", choices=["on", "off"], default="on", help="Enable CRC32 hardware acceleration", type="choice", ) add_option( "xray", choices=["on", "off"], default="off", help="Build with LLVM XRay support", type="choice", ) add_option( "xray-instruction-threshold", help="XRay instrumentation instruction threshold", default=1, nargs="?", type=int, ) add_option( "git-decider", choices=["on", "off"], const="on", default="off", help="Use git metadata for out-of-date detection for source files", nargs="?", type="choice", ) add_option( "toolchain-root", default=mongo_toolchain_execroot if mongo_toolchain_execroot else "", help="Name a toolchain root for use with toolchain selection Variables files in etc/scons", ) if mongo_toolchain_execroot: bin_dir = os.path.join(mongo_toolchain_execroot, "external/mongo_toolchain/v4/bin") gcc_path = os.path.dirname( os.path.realpath(os.path.join(bin_dir, os.readlink(os.path.join(bin_dir, "g++")))) ) clang_path = os.path.dirname( os.path.realpath(os.path.join(bin_dir, os.readlink(os.path.join(bin_dir, "clang++")))) ) else: gcc_path = "" clang_path = "" add_option( "bazel-toolchain-clang", default=clang_path, help="used in Variables files to help find the real bazel toolchain location.", ) add_option( "bazel-toolchain-gcc", default=gcc_path, help="used in Variables files to help find the real bazel toolchain location.", ) add_option( "msvc-debugging-format", choices=["codeview", "pdb"], default="codeview", help="Debugging format in debug builds using msvc. Codeview (/Z7) or Program database (/Zi). Default is codeview.", type="choice", ) add_option( "use-libunwind", choices=["on", "off", "auto"], const="on", default=build_profile.libunwind, help="Enable libunwind for backtraces", nargs="?", type="choice", ) add_option( "jlink", help="Limit link concurrency. Takes either an integer to limit to or a" " float between 0 and 1.0 whereby jobs will be multiplied to get the final" " jlink value." "\n\nExample: --jlink=0.75 --jobs 8 will result in a jlink value of 6", const=0.5, default=build_profile.jlink, nargs="?", type=float, ) add_option( "enable-usdt-probes", choices=["on", "off", "auto"], default="auto", help="Enable USDT probes. Default is auto, which is enabled only on Linux with SystemTap headers", type="choice", nargs="?", const="on", ) add_option( "libdeps-debug", choices=["on", "off"], const="off", help="Print way too much debugging information on how libdeps is handling dependencies.", nargs="?", type="choice", ) add_option( "libdeps-linting", choices=["on", "off", "print"], const="on", default="on", help="Enable linting of libdeps. Default is on, optionally 'print' will not stop the build.", nargs="?", type="choice", ) add_option( "build-metrics", metavar="FILE", const="build-metrics.json", default="", help="Enable tracking of build performance and output data as json." ' Use "-" to output json to stdout, or supply a path to the desired' " file to output to. If no argument is supplied, the default log" ' file will be "build-metrics.json".', nargs="?", type=str, ) add_option( "visibility-support", choices=["auto", "on", "off"], const="auto", default="auto", help="Enable visibility annotations", nargs="?", type="choice", ) add_option( "force-macos-dynamic-link", default=False, action="store_true", help="Bypass link-model=dynamic check for macos versions <12.", ) add_option( "bazel-dynamic-execution", default=False, action="store_true", help="use bazel dynamic execution experimental feature", ) # --build-mongot is a compile flag used by the evergreen build variants that run end-to-end search # suites, as it downloads the necessary mongot binary. add_option( "build-mongot", choices=["latest", "release"], default=None, type="choice", help="Installs the appropriate mongot for your architecture", ) add_option( "patch-build-mongot-url", default=None, help="Installs mongot binary from upstream patch on mongot-master for your architecture", ) try: with open("version.json", "r") as version_fp: version_data = json.load(version_fp) if "version" not in version_data: print("version.json does not contain a version string") Exit(1) if "githash" not in version_data: version_data["githash"] = utils.get_git_version() except IOError as e: # If the file error wasn't because the file is missing, error out if e.errno != errno.ENOENT: print(("Error opening version.json: {0}".format(e.strerror))) Exit(1) version_data = { "version": utils.get_git_describe()[1:], "githash": utils.get_git_version(), } except ValueError as e: print(("Error decoding version.json: {0}".format(e))) Exit(1) def to_boolean(s): if isinstance(s, bool): return s elif s.lower() in ("1", "on", "true", "yes"): return True elif s.lower() in ("0", "off", "false", "no"): return False raise ValueError(f"Invalid value {s}, must be a boolean-like string") # Setup the command-line variables def variable_shlex_converter(val): # If the argument is something other than a string, propagate # it literally. if not isinstance(val, str): return val parse_mode = get_option("variable-parse-mode") if parse_mode == "auto": parse_mode = "other" if mongo_platform.is_running_os("windows") else "posix" return shlex.split(val, posix=(parse_mode == "posix")) # Setup the command-line variables def where_is_converter(val): path = WhereIs(val) if path: return os.path.abspath(path) return val def variable_arch_converter(val): arches = { "x86_64": "x86_64", "amd64": "x86_64", "emt64": "x86_64", "x86": "i386", } val = val.lower() if val in arches: return arches[val] # Uname returns a bunch of possible x86's on Linux. # Check whether the value is an i[3456]86 processor. if re.match(r"^i[3-6]86$", val): return "i386" # Return whatever val is passed in - hopefully it's legit return val def bool_var_converter(val, var): try: return to_boolean(val) except ValueError as exc: if val.lower() != "auto": raise ValueError( f'Invalid {var} value {s}, must be a boolean-like string or "auto"' ) from exc return "auto" # The Scons 'default' tool enables a lot of tools that we don't actually need to enable. # On platforms like Solaris, it actually does the wrong thing by enabling the sunstudio # toolchain first. As such it is simpler and more efficient to manually load the precise # set of tools we need for each platform. # If we aren't on a platform where we know the minimal set of tools, we fall back to loading # the 'default' tool. def decide_platform_tools(): if mongo_platform.is_running_os("windows"): # we only support MS toolchain on windows return ["msvc", "mslink", "mslib", "masm", "vcredist"] elif mongo_platform.is_running_os("linux", "solaris"): return ["gcc", "g++", "gnulink", "ar", "gas"] elif mongo_platform.is_running_os("darwin"): return ["gcc", "g++", "applelink", "ar", "libtool", "as", "xcode"] else: return ["default"] def variable_tools_converter(val): tool_list = shlex.split(val) # This list is intentionally not sorted; the order of tool loading # matters as some of the tools have dependencies on other tools. return tool_list + [ "distsrc", "gziptool", "mongo_consolidated_targets", "mongo_test_execution", "mongo_test_list", "mongo_benchmark", "mongo_integrationtest", "mongo_unittest", "mongo_libfuzzer", "mongo_pretty_printer_tests", "textfile", "mongo_workload_simulator", ] def variable_distsrc_converter(val): if not val.endswith("/"): return val + "/" return val def fatal_error(env, msg, *args): print(msg.format(*args)) Exit(1) # Apply the default variables files, and walk the provided # arguments. Interpret any falsy argument (like the empty string) as # resetting any prior state. This makes the argument # --variables-files= destructive of any prior variables files # arguments, including the default. variables_files_args = get_option("variables-files") variables_files = find_mongo_custom_variables() for variables_file in variables_files_args: if variables_file: variables_files.append(variables_file) else: variables_files = [] for vf in variables_files: if not os.path.isfile(vf): fatal_error(None, f"Specified variables file '{vf}' does not exist") print(f"Using variable customization file {vf}") env_vars = Variables( files=variables_files, args=ARGUMENTS, ) sconsflags = os.environ.get("SCONSFLAGS", None) if sconsflags: print(("Using SCONSFLAGS environment variable arguments: %s" % sconsflags)) env_vars.Add( "ABIDW", help="Configures the path to the 'abidw' (a libabigail) utility", ) env_vars.Add( "AR", help="Sets path for the archiver", ) env_vars.Add( "ARFLAGS", help="Sets flags for the archiver", converter=variable_shlex_converter, ) env_vars.Add( "CCACHE", help="Tells SCons where the ccache binary is", default=build_profile.CCACHE, ) env_vars.Add( "CACHE_SIZE", help="Maximum size of the SCons cache (in gigabytes)", default=32, converter=lambda x: int(x), ) env_vars.Add( "CACHE_PRUNE_TARGET", help="Maximum percent in-use in SCons cache after pruning", default=66, converter=lambda x: int(x), ) env_vars.Add( "CC", help="Selects the C compiler to use", ) env_vars.Add( "CCFLAGS", help="Sets flags for the C and C++ compiler", converter=variable_shlex_converter, ) env_vars.Add( "NON_CONF_CCFLAGS", help="Sets flags for the C and C++ compiler that are not used in configure checks", converter=variable_shlex_converter, ) env_vars.Add( "TOOLCHAIN_CCFLAGS", help="Sets flags for the C and C++ compiler specific to the toolchain", converter=variable_shlex_converter, ) env_vars.Add( "NON_CONF_LINKFLAGS", help="Sets flags for the C and C++ linker that are not used in configure checks", converter=variable_shlex_converter, ) env_vars.Add( "TOOLCHAIN_LINKFLAGS", help="Sets flags for the C and C++ linker specific to the toolchain", converter=variable_shlex_converter, ) env_vars.Add( "ASFLAGS", help="Sets assembler specific flags", converter=variable_shlex_converter, ) env_vars.Add( "CFLAGS", help="Sets flags for the C compiler", converter=variable_shlex_converter, ) env_vars.Add( "CPPDEFINES", help="Sets pre-processor definitions for C and C++", converter=variable_shlex_converter, default=[], ) env_vars.Add( "CPPPATH", help="Adds paths to the preprocessor search path", converter=variable_shlex_converter, ) env_vars.Add( "CXX", help="Selects the C++ compiler to use", ) env_vars.Add( "CXXFLAGS", help="Sets flags for the C++ compiler", converter=variable_shlex_converter, ) env_vars.Add( "UNITTESTS_COMPILE_CONCURRENCY", help="Sets the ratio of total jobs for max concurrency when compiling unittests source files. Should be float between 0 and 1.", default="1", converter=lambda val: float(val) if val != "" else "", ) env_vars.Add( "DESTDIR", help="Where builds will install files", default="$BUILD_ROOT/install", ) env_vars.Add( "BAZEL_FLAGS", help="Flags specific to bazel to pass through to the underlying bazel build command.", default="", ) env_vars.Add( "BAZEL_INTEGRATION_DEBUG", help="Enable SCons/Bazel integration debug output", converter=functools.partial(bool_var_converter, var="BAZEL_INTEGRATION_DEBUG"), default="0", ) env_vars.Add( "DSYMUTIL", help="Path to the dsymutil utility", ) def validate_dwarf_version(key, val, env): if val == "4" or val == "5" or val == "": return print(f"Invalid DWARF_VERSION '{val}'. Only valid versions are 4 or 5.") Exit(1) env_vars.Add( "DWARF_VERSION", help="Sets the DWARF version (non-Windows). Incompatible with SPLIT_DWARF=1.", validator=validate_dwarf_version, converter=lambda val: int(val) if val != "" else "", default="", ) def validate_dwarf_width(key, val, env): if val == "32" or val == "64" or val == "": return print(f"Invalid DWARF_WIDTH '{val}'. Only valid versions are 32 or 64.") Exit(1) env_vars.Add( "DWARF_WIDTH", help="Sets the DWARF addressing mode to either 32-bit or 64-bit (non-Windows)", validator=validate_dwarf_width, converter=lambda val: int(val) if val != "" else "", default="", ) env_vars.Add( "READELF", help="Path to readelf", default="readelf", ) env_vars.Add( "GITDIFFFLAGS", help="Sets flags for git diff", default="", ) env_vars.Add( "REVISION", help="Base git revision", default="", ) # Note: This probably is only really meaningful when configured via a variables file. It will # also override whatever the SCons platform defaults would be. env_vars.Add( "ENV", help="Sets the environment for subprocesses", ) env_vars.Add( "FRAMEWORKPATH", help="Adds paths to the linker search path for darwin frameworks", converter=variable_shlex_converter, ) env_vars.Add( "FRAMEWORKS", help="Adds extra darwin frameworks to link against", converter=variable_shlex_converter, ) env_vars.Add( "HOST_ARCH", help="Sets the native architecture of the compiler", converter=variable_arch_converter, default=None, ) env_vars.Add( "ICECC", help="Tells SCons where icecream icecc tool is", default=build_profile.ICECC, ) env_vars.Add( "ICERUN", help="Tells SCons where icecream icerun tool is", ) env_vars.Add( "ICECC_CREATE_ENV", help="Tells SCons where icecc-create-env tool is", default="icecc-create-env", ) env_vars.Add( "ICECC_DEBUG", help="Tell ICECC to create debug logs (auto, on/off true/false 1/0)", default=False, ) env_vars.Add( "ICECC_SCHEDULER", help="Tells ICECC where the scheduler daemon is running", ) env_vars.Add( "ICECC_VERSION", help="Tells ICECC where the compiler package is", ) env_vars.Add( "ICECC_VERSION_ARCH", help="Tells ICECC the target architecture for the compiler package, if non-native", ) env_vars.Add( "LIBPATH", help="Adds paths to the linker search path", converter=variable_shlex_converter, ) env_vars.Add( "LIBS", help="Adds extra libraries to link against", converter=variable_shlex_converter, ) env_vars.Add( "LINKFLAGS", help="Sets flags for the linker", converter=variable_shlex_converter, ) env_vars.Add( "LLVM_SYMBOLIZER", help="Name of or path to the LLVM symbolizer", ) env_vars.Add( "MAXLINELENGTH", help="Maximum line length before using temp files", # This is very small, but appears to be the least upper bound # across our platforms. # # See https://support.microsoft.com/en-us/help/830473/command-prompt-cmd.-exe-command-line-string-limitation default=4095, ) # Note: This is only really meaningful when configured via a variables file. See the # default_buildinfo_environment_data() function for examples of how to use this. env_vars.Add( "MONGO_BUILDINFO_ENVIRONMENT_DATA", help="Sets the info returned from the buildInfo command and --version command-line flag", default=mongo_generators.default_buildinfo_environment_data(), ) env_vars.Add( "MONGO_DIST_SRC_PREFIX", help="Sets the prefix for files in the source distribution archive", converter=variable_distsrc_converter, default="mongodb-src-r${MONGO_VERSION}", ) env_vars.Add( "MONGO_DISTARCH", help="Adds a string representing the target processor architecture to the dist archive", default="$TARGET_ARCH", ) env_vars.Add( "MONGO_DISTMOD", help="Adds a string that will be embedded in the dist archive naming", default="", ) env_vars.Add( "MONGO_DISTNAME", help="Sets the version string to be used in dist archive naming", default="$MONGO_VERSION", ) def validate_mongo_version(key, val, env): valid_version_re = re.compile(r"^(\d+)\.(\d+)\.(\d+)-?((?:(rc)(\d+))?.*)?$", re.MULTILINE) invalid_version_re = re.compile(r"^0\.0\.0(?:-.*)?", re.MULTILINE) if not valid_version_re.match(val) or invalid_version_re.match(val): print( ( "Invalid MONGO_VERSION '{}', or could not derive from version.json or git metadata. Please add a conforming MONGO_VERSION=x.y.z[-extra] as an argument to SCons".format( val ) ) ) Exit(1) env_vars.Add( "MONGO_VERSION", help="Sets the version string for MongoDB", default=version_data["version"], validator=validate_mongo_version, ) env_vars.Add( "MONGO_GIT_HASH", help="Sets the githash to store in the MongoDB version information", default=version_data["githash"], ) env_vars.Add( "MSVC_USE_SCRIPT", help="Sets the script used to setup Visual Studio.", ) env_vars.Add( "MSVC_VERSION", help="Sets the version of Visual C++ to use (e.g. 14.2 for VS2019, 14.3 for VS2022)", default="14.3", ) env_vars.Add( "LINKFLAGS_COMPILER_EXEC_PREFIX", help="Specify the search path to be injected into the LINKFLAGS", default="", ) env_vars.Add( "COMPILER_EXEC_PREFIX_OPT", help="Specify the option sign for compiler exec search paths.", default="-B", ) env_vars.Add( "NINJA_BUILDDIR", help="Location for shared Ninja state", default="$BUILD_ROOT/ninja", ) env_vars.Add( "NINJA_PREFIX", default=build_profile.NINJA_PREFIX, help="""A prefix to add to the beginning of generated ninja files. Useful for when compiling multiple build ninja files for different configurations, for instance: scons --sanitize=asan --ninja NINJA_PREFIX=asan asan.ninja scons --sanitize=tsan --ninja NINJA_PREFIX=tsan tsan.ninja Will generate the files (respectively): asan.ninja tsan.ninja Defaults to build. Best used with the --ninja flag so you don't have to reiterate the prefix in the target name and variable. """, ) env_vars.Add( "NINJA_SUFFIX", help="""A suffix to add to the end of generated build.ninja files. Useful for when compiling multiple build ninja files for different configurations, for instance: scons --sanitize=asan --ninja NINJA_SUFFIX=asan build.ninja scons --sanitize=tsan --ninja NINJA_SUFFIX=tsan build.ninja Will generate the files (respectively): build.ninja.asan build.ninja.tsan """, ) env_vars.Add( "__NINJA_NO", help="Disables the Ninja tool unconditionally. Not intended for human use.", default=0, ) env_vars.Add( "COMPILATIONDB_IGNORE_WRAPPERS", help="Comma separated list of variables which reference wrapper binaries that should be excluded when generating compile_commands.json", default="$ICECC,$ICERUN,$ICECREAM_RUN_ICECC,$CCACHE", ) env_vars.Add( "OBJCOPY", help="Sets the path to objcopy", default=WhereIs("objcopy"), ) env_vars.Add( "PKGDIR", help="Directory in which to build packages and archives", default="$BUILD_DIR/pkgs", ) env_vars.Add( "PREFIX", help="Final installation location of files. Will be made into a sub dir of $DESTDIR", default=".", ) # Exposed to be able to cross compile Android/*nix from Windows without ending up with the .exe suffix. env_vars.Add( "PROGSUFFIX", help="Sets the suffix for built executable files", ) env_vars.Add( "RPATH", help="Set the RPATH for dynamic libraries and executables", converter=variable_shlex_converter, ) env_vars.Add( "SHCCFLAGS", help="Sets flags for the C and C++ compiler when building shared libraries", converter=variable_shlex_converter, ) env_vars.Add( "SHCFLAGS", help="Sets flags for the C compiler when building shared libraries", converter=variable_shlex_converter, ) env_vars.Add( "SHCXXFLAGS", help="Sets flags for the C++ compiler when building shared libraries", converter=variable_shlex_converter, ) env_vars.Add( "SHELL", help="Picks the shell to use when spawning commands", ) env_vars.Add( "SHLINKFLAGS", help="Sets flags for the linker when building shared libraries", converter=variable_shlex_converter, ) env_vars.Add( "SHLINKFLAGS_EXTRA", help="Adds additional flags for shared links without overwriting tool configured SHLINKFLAGS values", converter=variable_shlex_converter, ) env_vars.Add( "STRIP", help="Path to the strip utility (non-darwin platforms probably use OBJCOPY for this)", ) env_vars.Add( "PROTOC", default="$$PROTOC_VAR_GEN", help="Path to protobuf compiler.", converter=where_is_converter, ) env_vars.Add( "PROTOC_GRPC_PLUGIN", default="$$PROTOC_GRPC_PLUGIN_GEN", help="Path to protobuf compiler grpc plugin.", converter=where_is_converter, ) env_vars.Add( "SPLIT_DWARF", help="Set the boolean (auto, on/off true/false 1/0) to enable gsplit-dwarf (non-Windows). Incompatible with DWARF_VERSION=5", converter=functools.partial(bool_var_converter, var="SPLIT_DWARF"), default="auto", ) env_vars.Add( "ENABLE_GRPC_BUILD", help="Set the boolean (auto, on/off true/false 1/0) to enable building grpc and protobuf compiler.", converter=functools.partial(bool_var_converter, var="ENABLE_GRPC_BUILD"), default="0", ) env_vars.Add( "GDB", help="Configures the path to the 'gdb' debugger binary.", ) env_vars.Add( "GDB_INDEX", help="Set the boolean (auto, on/off true/false 1/0) to enable creation of a gdb_index in binaries.", converter=functools.partial(bool_var_converter, var="GDB_INDEX"), default="auto", ) env_vars.Add( "GDB_PPTEST_PYONLY", help="""Set the boolean (on/off true/false 1/0) to enable SCons to only emit the .py files needed for testing GDB pretty printers. Useful for when the executable files for testing GDB pretty printers are more rapidly built using Ninja and the install-dist-test target (separately from SCons).""", converter=functools.partial(bool_var_converter, var="GDB_PPTEST_PYONLY"), default="False", ) env_vars.Add( "ENABLE_BUILD_RETRY", help="Set the boolean (auto, on/off true/false 1/0) to enable retrying a compile or link commands failures.", converter=functools.partial(bool_var_converter, var="ENABLE_BUILD_RETRY"), default="False", ) env_vars.Add( "TAPI", help="Configures the path to the 'tapi' (an Xcode) utility", ) env_vars.Add( "TARGET_ARCH", help="Sets the architecture to build for", converter=variable_arch_converter, default=None, ) env_vars.Add( "TARGET_OS", help="Sets the target OS to build for", default=mongo_platform.get_running_os_name(), ) env_vars.Add( "TOOLS", help="Sets the list of SCons tools to add to the environment", converter=variable_tools_converter, default=decide_platform_tools(), ) env_vars.Add( "VARIANT_DIR", help="Sets the name (or generator function) for the variant directory", default=build_profile.VARIANT_DIR, ) env_vars.Add( "VERBOSE", help="Controls build verbosity (auto, on/off true/false 1/0)", default="auto", ) env_vars.Add( "WINDOWS_OPENSSL_BIN", help="Sets the path to the openssl binaries for packaging", default="c:/openssl/bin", ) env_vars.Add( PathVariable( "LOCAL_TMPDIR", help="Set the TMPDIR when running tests.", default="$BUILD_ROOT/tmp_test_data", validator=PathVariable.PathAccept, ), ) env_vars.AddVariables( ("BUILD_METRICS_EVG_TASK_ID", "Evergreen task ID to add to build metrics data."), ("BUILD_METRICS_EVG_BUILD_VARIANT", "Evergreen build variant to add to build metrics data."), ) for tool in ["build_metrics", "split_dwarf"]: try: Tool(tool).options(env_vars) except ImportError as exc: print(f"WARNING: The {tool} tool might not work as intended due to a failed import:\n{exc}") pass # -- Validate user provided options -- # A dummy environment that should *only* have the variables we have set. In practice it has # some other things because SCons isn't quite perfect about keeping variable initialization # scoped to Tools, but it should be good enough to do validation on any Variable values that # came from the command line or from loaded files. variables_only_env = Environment( # Disable platform specific variable injection platform=(lambda x: ()), # But do *not* load any tools, since those might actually set variables. Note that this # causes the value of our TOOLS variable to have no effect. tools=[], # Use the Variables specified above. variables=env_vars, ) # don't run configure if user calls --help if GetOption("help"): try: Help("\nThe following variables may also be set like scons VARIABLE=value\n", append=True) Help(env_vars.GenerateHelpText(variables_only_env, sort=True), append=True) Help( "\nThe 'list-targets' target can be built to list useful comprehensive build targets\n", append=True, ) except TypeError: # The append=true kwarg is only supported in scons>=2.4. Without it, calls to Help() clobber # the automatically generated options help, which we don't want. Users on older scons # versions will need to use --variables-help to learn about which variables we support. pass Return() if ("CC" in variables_only_env) != ("CXX" in variables_only_env): print("Cannot customize C compiler without customizing C++ compiler, and vice versa") Exit(1) # --- environment setup --- # If the user isn't using the # to indicate top-of-tree or $ to expand a variable, forbid # relative paths. Relative paths don't really work as expected, because they end up relative to # the top level SConstruct, not the invoker's CWD. We could in theory fix this with # GetLaunchDir, but that seems a step too far. buildDir = get_option("build-dir").rstrip("/") if buildDir[0] not in ["$", "#"]: if not os.path.isabs(buildDir): print("Do not use relative paths with --build-dir") Exit(1) cacheDir = get_option("cache-dir").rstrip("/") if cacheDir[0] not in ["$", "#"]: if not os.path.isabs(cacheDir): print("Do not use relative paths with --cache-dir") Exit(1) sconsDataDir = Dir(buildDir).Dir("scons") SConsignFile(str(sconsDataDir.File("sconsign.py3"))) def printLocalInfo(): import sys import SCons print(("scons version: " + SCons.__version__)) print(("python version: " + " ".join([repr(i) for i in sys.version_info]))) printLocalInfo() boostLibs = ["filesystem", "program_options", "system", "iostreams", "thread", "log"] onlyServer = len(COMMAND_LINE_TARGETS) == 0 or ( len(COMMAND_LINE_TARGETS) == 1 and str(COMMAND_LINE_TARGETS[0]) in ["mongod", "mongos", "test"] ) noshell = has_option("noshell") jsEngine = get_option("js-engine") serverJs = get_option("server-js") == "on" if not serverJs and not jsEngine: print("Warning: --server-js=off is not needed with --js-engine=none") # We defer building the env until we have determined whether we want certain values. Some values # in the env actually have semantics for 'None' that differ from being absent, so it is better # to build it up via a dict, and then construct the Environment in one shot with kwargs. # # Yes, BUILD_ROOT vs BUILD_DIR is confusing. Ideally, BUILD_DIR would actually be called # VARIANT_DIR, and at some point we should probably do that renaming. Until we do though, we # also need an Environment variable for the argument to --build-dir, which is the parent of all # variant dirs. For now, we call that BUILD_ROOT. If and when we s/BUILD_DIR/VARIANT_DIR/g, # then also s/BUILD_ROOT/BUILD_DIR/g. envDict = dict( BUILD_ROOT=buildDir, BUILD_DIR=make_variant_dir_generator(), DIST_ARCHIVE_SUFFIX=".tgz", MODULE_BANNERS=[], MODULE_INJECTORS=dict(), PYTHON="$( {} $)".format(sys.executable), SERVER_ARCHIVE="${SERVER_DIST_BASENAME}${DIST_ARCHIVE_SUFFIX}", UNITTEST_ALIAS="install-unittests", # TODO: Move unittests.txt to $BUILD_DIR, but that requires # changes to MCI. UNITTEST_LIST="$BUILD_ROOT/unittests.txt", PRETTY_PRINTER_TEST_ALIAS="install-pretty-printer-tests", PRETTY_PRINTER_TEST_LIST="$BUILD_ROOT/pretty_printer_tests.txt", LIBFUZZER_TEST_ALIAS="install-fuzzertests", LIBFUZZER_TEST_LIST="$BUILD_ROOT/libfuzzer_tests.txt", INTEGRATION_TEST_ALIAS="install-integration-tests", INTEGRATION_TEST_LIST="$BUILD_ROOT/integration_tests.txt", BENCHMARK_ALIAS="install-benchmarks", BENCHMARK_LIST="$BUILD_ROOT/benchmarks.txt", CONFIGUREDIR="$BUILD_ROOT/scons/$VARIANT_DIR/sconf_temp", CONFIGURELOG="$BUILD_ROOT/scons/config.log", LIBDEPS_TAG_EXPANSIONS=[], ) # By default, we will get the normal SCons tool search. But if the # user has opted into the next gen tools, add our experimental tool # directory into the default toolpath, ahead of whatever is already in # there so it overrides it. if get_option("build-tools") == "next": SCons.Tool.DefaultToolpath.insert(0, os.path.abspath("site_scons/site_tools/next")) env = Environment(variables=env_vars, **envDict) del envDict env.AddMethod(lambda env, name, **kwargs: add_option(name, **kwargs), "AddOption") env.Prepend(CCFLAGS="$TOOLCHAIN_CCFLAGS") env.Prepend(LINKFLAGS="$TOOLCHAIN_LINKFLAGS") if ARGUMENTS.get("CC") and ARGUMENTS.get("CXX"): os.environ["CC"] = env.get("CC") os.environ["CXX"] = env.get("CXX") os.environ["USE_NATIVE_TOOLCHAIN"] = "1" # Early load to setup env functions tool = Tool("integrate_bazel") tool.exists(env) # The placement of this is intentional. Here we setup an atexit method to store tooling metrics. # We should only register this function after env, env_vars and the parser have been properly initialized. SConsToolingMetrics.register_metrics( utc_starttime=datetime.utcnow(), artifact_dir=env.Dir("$BUILD_DIR").get_abspath(), env_vars=env_vars, env=env, parser=_parser, ) if get_option("build-metrics"): env["BUILD_METRICS_ARTIFACTS_DIR"] = "$BUILD_ROOT/$VARIANT_DIR" env.Tool("build_metrics") env.AddBuildMetricsMetaData("evg_id", env.get("BUILD_METRICS_EVG_TASK_ID", "UNKNOWN")) env.AddBuildMetricsMetaData("variant", env.get("BUILD_METRICS_EVG_BUILD_VARIANT", "UNKNOWN")) env.Execute(SCons.Defaults.Mkdir(env.Dir("$LOCAL_TMPDIR"))) if get_option("cache-signature-mode") == "validate": validate_cache_dir = Tool("validate_cache_dir") if validate_cache_dir.exists(env): validate_cache_dir(env) else: env.FatalError("Failed to enable validate_cache_dir tool.") # Only print the spinner if stdout is a tty if sys.stdout.isatty(): Progress(["-\r", "\\\r", "|\r", "/\r"], interval=50) # We are going to start running conf tests soon, so setup # --disable-warnings-as-errors as soon as possible. def create_werror_generator(flagname): werror_conftests = "configure" not in get_option("disable-warnings-as-errors") werror_source = "source" not in get_option("disable-warnings-as-errors") def generator(target, source, env, for_signature): if werror_conftests and "conftest" in str(target[0]): return flagname if werror_source: return flagname return str() return generator env.Append( CCFLAGS=["$CCFLAGS_GENERATE_WERROR"], CCFLAGS_GENERATE_WERROR=create_werror_generator("$CCFLAGS_WERROR"), CXXFLAGS=["$CXXFLAGS_GENERATE_WERROR"], CXXFLAGS_GENERATE_WERROR=create_werror_generator("$CXXFLAGS_WERROR"), LINKFLAGS=["$LINKFLAGS_GENERATE_WERROR"], LINKFLAGS_GENERATE_WERROR=create_werror_generator("$LINKFLAGS_WERROR"), ) def non_conf_ccflags_gen(target, source, env, for_signature): if "conftest" in str(target[0]): return "" return "$NON_CONF_CCFLAGS" def non_conf_linkflags_gen(target, source, env, for_signature): if "conftest" in str(target[0]): return "" return "$NON_CONF_LINKFLAGS" env["_NON_CONF_CCFLAGS_GEN"] = non_conf_ccflags_gen env["_NON_CONF_LINKFLAGS_GEN"] = non_conf_linkflags_gen env.Append(CCFLAGS="$_NON_CONF_CCFLAGS_GEN") env.Append(LINKFLAGS="$_NON_CONF_LINKFLAGS_GEN") for var in ["CC", "CXX"]: if var not in env: continue path = env[var] print("{} is {}".format(var, path)) if not os.path.isabs(path): which = shutil.which(path) if which is None: print("{} was not found in $PATH".format(path)) else: print("{} found in $PATH at {}".format(path, which)) path = which realpath = os.path.realpath(path) if realpath != path: print("{} resolves to {}".format(path, realpath)) env.AddMethod(mongo_platform.env_os_is_wrapper, "TargetOSIs") env.AddMethod(mongo_platform.env_get_os_name_wrapper, "GetTargetOSName") def conf_error(env, msg, *args): print(msg.format(*args)) print("See {0} for details".format(env.File("$CONFIGURELOG").abspath)) Exit(1) env.AddMethod(fatal_error, "FatalError") env.AddMethod(conf_error, "ConfError") # Normalize the VERBOSE Option, and make its value available as a # function. if env["VERBOSE"] == "auto": env["VERBOSE"] = not sys.stdout.isatty() and env.get("__NINJA_NO") != "1" else: try: env["VERBOSE"] = to_boolean(env["VERBOSE"]) except ValueError as e: env.FatalError(f"Error setting VERBOSE variable: {e}") env.AddMethod(lambda env: env["VERBOSE"], "Verbose") env.Append( LINKFLAGS=['${_concat(COMPILER_EXEC_PREFIX_OPT, LINKFLAGS_COMPILER_EXEC_PREFIX, "", __env__)}'] ) # Normalize the ICECC_DEBUG option try: env["ICECC_DEBUG"] = to_boolean(env["ICECC_DEBUG"]) except ValueError as e: env.FatalError(f"Error setting ICECC_DEBUG variable: {e}") if has_option("variables-help"): print(env_vars.GenerateHelpText(env)) Exit(0) unknown_vars = env_vars.UnknownVariables() if unknown_vars: env.FatalError("Unknown variables specified: {0}", ", ".join(list(unknown_vars.keys()))) install_actions.setup(env, get_option("install-action")) if env.TargetOSIs("windows") and os.path.exists( env.File("#/src/mongo/db/modules/enterprise/SConscript").abspath ): # the sasl zip can be rebuilt by following the instructions at: # https://github.com/mongodb-forks/cyrus-sasl/blob/mongo-sasl-2-1-28/README.md import mongo.download_windows_sasl mongo.download_windows_sasl.download_sasl(env) detectEnv = env.Clone() # Identify the toolchain in use. We currently support the following: # These macros came from # http://nadeausoftware.com/articles/2012/10/c_c_tip_how_detect_compiler_name_and_version_using_compiler_predefined_macros toolchain_macros = { "GCC": "defined(__GNUC__) && !defined(__clang__)", "clang": "defined(__clang__)", "MSVC": "defined(_MSC_VER)", } def CheckForToolchain(context, toolchain, lang_name, compiler_var, source_suffix): test_body = textwrap.dedent( """ #if {0} /* we are using toolchain {0} */ #else #error #endif """.format(toolchain_macros[toolchain]) ) print_tuple = (lang_name, context.env[compiler_var], toolchain) context.Message('Checking if %s compiler "%s" is %s... ' % print_tuple) # Strip indentation from the test body to ensure that the newline at the end of the # endif is the last character in the file (rather than a line of spaces with no # newline), and that all of the preprocessor directives start at column zero. Both of # these issues can trip up older toolchains. result = context.TryCompile(test_body, source_suffix) context.Result(result) return result endian = get_option("endian") if endian == "auto": endian = sys.byteorder processor_macros = { "aarch64": {"endian": "little", "check": "(defined(__arm64__) || defined(__aarch64__))"}, "emscripten": {"endian": "little", "check": "(defined(__EMSCRIPTEN__))"}, "ppc64le": {"endian": "little", "check": "(defined(__powerpc64__))"}, "riscv64": {"endian": "little", "check": "(defined(__riscv)) && (__riscv_xlen == 64)"}, "s390x": {"endian": "big", "check": "(defined(__s390x__))"}, "x86_64": {"endian": "little", "check": "(defined(__x86_64) || defined(_M_AMD64))"}, } def CheckForProcessor(context, which_arch): def run_compile_check(arch): if not endian == processor_macros[arch]["endian"]: return False test_body = """ #if {0} /* Detected {1} */ #else #error not {1} #endif """.format(processor_macros[arch]["check"], arch) return context.TryCompile(textwrap.dedent(test_body), ".c") if which_arch: ret = run_compile_check(which_arch) context.Message("Checking if target processor is %s " % which_arch) context.Result(ret) return ret for k in list(processor_macros.keys()): ret = run_compile_check(k) if ret: context.Result("Detected a %s processor" % k) return k context.Result("Could not detect processor model/architecture") return False # Taken from http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system os_macros = { "windows": "defined(_WIN32)", "solaris": "defined(__sun)", "freebsd": "defined(__FreeBSD__)", "openbsd": "defined(__OpenBSD__)", "iOS": "defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_SIMULATOR", "iOS-sim": "defined(__APPLE__) && TARGET_OS_IOS && TARGET_OS_SIMULATOR", "tvOS": "defined(__APPLE__) && TARGET_OS_TV && !TARGET_OS_SIMULATOR", "tvOS-sim": "defined(__APPLE__) && TARGET_OS_TV && TARGET_OS_SIMULATOR", "watchOS": "defined(__APPLE__) && TARGET_OS_WATCH && !TARGET_OS_SIMULATOR", "watchOS-sim": "defined(__APPLE__) && TARGET_OS_WATCH && TARGET_OS_SIMULATOR", # NOTE: Once we have XCode 8 required, we can rely on the value of TARGET_OS_OSX. In case # we are on an older XCode, use TARGET_OS_MAC and TARGET_OS_IPHONE. We don't need to correct # the above declarations since we will never target them with anything other than XCode 8. "macOS": "defined(__APPLE__) && (TARGET_OS_OSX || (TARGET_OS_MAC && !TARGET_OS_IPHONE))", "linux": "defined(__linux__)", "android": "defined(__ANDROID__)", "emscripten": "defined(__EMSCRIPTEN__)", } def CheckForOS(context, which_os): test_body = """ #if defined(__APPLE__) #include #endif #if {0} /* detected {1} */ #else #error #endif """.format(os_macros[which_os], which_os) context.Message("Checking if target OS {0} is supported by the toolchain... ".format(which_os)) ret = context.TryCompile(textwrap.dedent(test_body), ".c") context.Result(ret) return ret def CheckForCXXLink(context): test_body = """ #include #include int main() { std::cout << "Hello, World" << std::endl; return EXIT_SUCCESS; } """ context.Message("Checking that the C++ compiler can link a C++ program... ") ret = context.TryLink(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret detectSystem = Configure( detectEnv, help=False, custom_tests={ "CheckForToolchain": CheckForToolchain, "CheckForProcessor": CheckForProcessor, "CheckForOS": CheckForOS, }, ) toolchain_search_sequence = ["GCC", "clang"] if mongo_platform.is_running_os("windows"): toolchain_search_sequence = ["MSVC", "clang", "GCC"] detected_toolchain = None for candidate_toolchain in toolchain_search_sequence: if detectSystem.CheckForToolchain(candidate_toolchain, "C++", "CXX", ".cpp"): detected_toolchain = candidate_toolchain break if not detected_toolchain: env.ConfError("Couldn't identify the C++ compiler") if not detectSystem.CheckForToolchain(detected_toolchain, "C", "CC", ".c"): env.ConfError("C compiler does not match identified C++ compiler") # Now that we've detected the toolchain, we add methods to the env # to get the canonical name of the toolchain and to test whether # scons is using a particular toolchain. def get_toolchain_name(self): return detected_toolchain.lower() def is_toolchain(self, *args): actual_toolchain = self.ToolchainName() for v in args: if v.lower() == actual_toolchain: return True return False env.AddMethod(get_toolchain_name, "ToolchainName") env.AddMethod(is_toolchain, "ToolchainIs") releaseBuild = get_option("release") == "on" debugBuild = get_option("dbg") == "on" debug_symbols = get_option("debug-symbols") == "on" optBuild = mongo_generators.get_opt_options(env) if env.get("ENABLE_BUILD_RETRY"): if get_option("ninja") != "disabled": print("ENABLE_BUILD_RETRY not compatible with ninja, disabling ENABLE_BUILD_RETRY.") else: env["BUILD_RETRY_ATTEMPTS"] = 10 env["BUILD_RETRY_MAX_DELAY_SECONDS"] = 120 env.Tool("build_auto_retry") if env.ToolchainIs("clang"): # LLVM utilizes the stack extensively without optimization enabled, which # causes the built product to easily blow through our 1M stack size whenever # either gcov or sanitizers are enabled. Ref: SERVER-65684 if has_option("gcov") and optBuild not in ("on", "debug"): env.FatalError( "Error: A clang --gcov build must have either --opt=debug or --opt=on to " + "prevent crashes due to excessive stack usage" ) if has_option("sanitize") and get_option("sanitize") != "" and optBuild not in ("on", "debug"): env.FatalError( "Error: A clang --sanitize build must have either --opt=debug or --opt=on " + "to prevent crashes due to excessive stack usage" ) if releaseBuild and (debugBuild or optBuild != "on"): env.FatalError( "Error: A --release build may not have debugging, and must have full optimization" ) if env["TARGET_ARCH"]: if not detectSystem.CheckForProcessor(env["TARGET_ARCH"]): env.ConfError("Could not detect processor specified in TARGET_ARCH variable") else: detected_processor = detectSystem.CheckForProcessor(None) if not detected_processor: env.ConfError("Failed to detect a supported target architecture") env["TARGET_ARCH"] = detected_processor if env["TARGET_OS"] not in os_macros: print("No special config for [{0}] which probably means it won't work".format(env["TARGET_OS"])) elif not detectSystem.CheckForOS(env["TARGET_OS"]): env.ConfError("TARGET_OS ({0}) is not supported by compiler", env["TARGET_OS"]) detectSystem.Finish() if env.TargetOSIs("posix"): if env.ToolchainIs("gcc", "clang"): env.Append( CCFLAGS_WERROR=["-Werror"], CXXFLAGS_WERROR=["-Werror=unused-result"] if env.ToolchainIs("clang") else [], LINKFLAGS_WERROR=["-Wl,--fatal-warnings"] if not env.TargetOSIs("darwin") else [], ) elif env.TargetOSIs("windows"): env.Append(CCFLAGS_WERROR=["/WX"]) if env.ToolchainIs("clang"): def assembler_with_cpp_gen(target, source, env, for_signature): if source[0].get_suffix() == ".sx": return "-x assembler-with-cpp" env["CLANG_ASSEMBLER_WITH_CPP"] = assembler_with_cpp_gen env.Append(ASFLAGS=["$CLANG_ASSEMBLER_WITH_CPP"]) env["CC_VERSION"] = mongo_toolchain.get_toolchain_ver(env, "CC") env["CXX_VERSION"] = mongo_toolchain.get_toolchain_ver(env, "CXX") if not env["HOST_ARCH"]: env["HOST_ARCH"] = env["TARGET_ARCH"] # In some places we have POSIX vs Windows cpp files, and so there's an additional # env variable to interpolate their names in child sconscripts env["TARGET_OS_FAMILY"] = "posix" if env.TargetOSIs("posix") else env.GetTargetOSName() if env.TargetOSIs("linux") or "tcmalloc-google" == get_option("allocator"): # tcmalloc from google has some requirements on the kernel version for rseq support # here we check if it should be available try: kernel_version = platform.release().split(".") kernel_major = int(kernel_version[0]) kernel_minor = int(kernel_version[1]) except (ValueError, IndexError): print( f"Failed to extract kernel major and minor versions, tcmalloc-google will not be available for use: {kernel_version}" ) kernel_major = 0 kernel_minor = 0 # Normalize the allocator option and store it in the Environment. It # would be nicer to use SetOption here, but you can't reset user # options for some strange reason in SCons. Instead, we store this # option as a new variable in the environment. if get_option("allocator") == "auto": if env.TargetOSIs("linux") and env["TARGET_ARCH"] in ("x86_64", "aarch64"): env["MONGO_ALLOCATOR"] = "tcmalloc-google" # googles tcmalloc uses the membarrier() system call which was added in Linux 4.3, # so fall back to gperf implementation for older kernels if kernel_major < 4 or (kernel_major == 4 and kernel_minor < 3): env["MONGO_ALLOCATOR"] = "tcmalloc-gperf" elif env.TargetOSIs("windows") or ( env.TargetOSIs("linux") and env["TARGET_ARCH"] in ("ppc64le", "s390x") ): env["MONGO_ALLOCATOR"] = "tcmalloc-gperf" else: env["MONGO_ALLOCATOR"] = "system" else: env["MONGO_ALLOCATOR"] = get_option("allocator") if env["MONGO_ALLOCATOR"] == "tcmalloc-google": if kernel_major < 4 or (kernel_major == 4 and kernel_minor < 3): env.ConfError( f"tcmalloc-google allocator only supported on linux kernel 4.3 or greater: kenerl verison={platform.release()}" ) if env["MONGO_ALLOCATOR"] == "tcmalloc-google": env.Append(CPPDEFINES=["ABSL_ALLOCATOR_NOTHROW"]) if has_option("cache"): if has_option("gcov"): env.FatalError("Mixing --cache and --gcov doesn't work correctly yet. See SERVER-11084") env.CacheDir(str(env.Dir(cacheDir))) # Normalize the link model. If it is auto, then for now both developer and release builds # use the "static" mode. Someday later, we probably want to make the developer build default # dynamic. link_model = get_option("link-model") if link_model == "auto": link_model = "static" if link_model.startswith("dynamic") and get_option("install-action") == "symlink": env.FatalError( f"Options '--link-model={link_model}' not supported with '--install-action={get_option('install-action')}'." ) if ( link_model == "dynamic" and env.TargetOSIs("darwin") and not get_option("force-macos-dynamic-link") ): macos_version_message = textwrap.dedent("""\ link-model=dynamic us only supported on macos version 12 or higher. This is due to a 512 dylib RUNTIME limit on older macos. See this post for more information: https://developer.apple.com/forums//thread/708366?login=true&page=1#717495022 Use '--force-macos-dynamic-link' to bypass this check. """) try: macos_version_major = int(platform.mac_ver()[0].split(".")[0]) if macos_version_major < 12: env.FatalError( textwrap.dedent(f"""\ Macos version detected: {macos_version_major} """) + macos_version_message ) except (IndexError, TypeError) as exc: env.FatalError( textwrap.dedent(f"""\ Failed to detect macos version: {exc} """) + macos_version_message ) # libunwind configuration. # In which the following globals are set and normalized to bool: # - use_libunwind # - use_system_libunwind # - use_vendored_libunwind use_libunwind = get_option("use-libunwind") use_system_libunwind = use_system_version_of_library("libunwind") # Assume system libunwind works if it's installed and selected. can_use_libunwind = ( use_system_libunwind or env.TargetOSIs("linux") and (env["TARGET_ARCH"] in ("x86_64", "aarch64", "ppc64le", "s390x")) ) if use_libunwind == "off": use_libunwind = False use_system_libunwind = False elif use_libunwind == "on": use_libunwind = True if not can_use_libunwind: env.ConfError("libunwind not supported on target platform") Exit(1) elif use_libunwind == "auto": use_libunwind = can_use_libunwind use_vendored_libunwind = use_libunwind and not use_system_libunwind env["USE_VENDORED_LIBUNWIND"] = use_vendored_libunwind if use_system_libunwind and not use_libunwind: print("Error: --use-system-libunwind requires --use-libunwind") Exit(1) if get_option("visibility-support") == "auto": visibility_annotations_enabled = not env.TargetOSIs("windows") and link_model.startswith( "dynamic" ) else: visibility_annotations_enabled = get_option("visibility-support") == "on" # Windows can't currently support anything other than 'object' or 'static', until # we have annotated functions for export. if env.TargetOSIs("windows") and not visibility_annotations_enabled: if link_model not in ["object", "static", "dynamic-sdk"]: env.FatalError( "Windows builds must use the 'object', 'dynamic-sdk', or 'static' link models" ) # TODO(SERVER-85904): remove check when object mode & LTO are supported in bazel if link_model == "object": env.FatalError( 'Bazel-enabled builds currently do not support the "object" link model. Reffer to SERVER-85904 for more info.' ) # The 'object' mode for libdeps is enabled by setting _LIBDEPS to $_LIBDEPS_OBJS. The other two # modes operate in library mode, enabled by setting _LIBDEPS to $_LIBDEPS_LIBS. env["_LIBDEPS"] = "$_LIBDEPS_OBJS" if link_model == "object" else "$_LIBDEPS_LIBS" env["BUILDERS"]["ProgramObject"] = env["BUILDERS"]["StaticObject"] env["BUILDERS"]["LibraryObject"] = env["BUILDERS"]["StaticObject"] env["SHARPREFIX"] = "$LIBPREFIX" env["SHARSUFFIX"] = "${SHLIBSUFFIX}${LIBSUFFIX}" env["BUILDERS"]["SharedArchive"] = SCons.Builder.Builder( action=env["BUILDERS"]["StaticLibrary"].action, emitter="$SHAREMITTER", prefix="$SHARPREFIX", suffix="$SHARSUFFIX", src_suffix=env["BUILDERS"]["SharedLibrary"].src_suffix, ) # Teach object builders how to build underlying generated types for builder in ["SharedObject", "StaticObject"]: env["BUILDERS"][builder].add_src_builder("Protoc") # These allow delayed evaluation of the AIB values for the default values of # the corresponding command line variables def protoc_var_gen(env, target, source, for_signature): return env.File("$DESTDIR/$PREFIX_BINDIR/protobuf_compiler$PROGSUFFIX") env["PROTOC_VAR_GEN"] = protoc_var_gen def protoc_grpc_plugin_var_gen(env, target, source, for_signature): return env.File("$DESTDIR/$PREFIX_BINDIR/grpc_cpp_plugin$PROGSUFFIX") env["PROTOC_GRPC_PLUGIN_GEN"] = protoc_grpc_plugin_var_gen if link_model.startswith("dynamic"): if link_model == "dynamic" and visibility_annotations_enabled: def visibility_cppdefines_generator(target, source, env, for_signature): if "MONGO_API_NAME" not in env: return None return "MONGO_API_${MONGO_API_NAME}" env["MONGO_VISIBILITY_CPPDEFINES_GENERATOR"] = visibility_cppdefines_generator def visibility_shccflags_generator(target, source, env, for_signature): if env.get("MONGO_API_NAME"): return "-fvisibility=hidden" return None if not env.TargetOSIs("windows"): env["MONGO_VISIBILITY_SHCCFLAGS_GENERATOR"] = visibility_shccflags_generator env.AppendUnique( CPPDEFINES=[ "MONGO_USE_VISIBILITY", "$MONGO_VISIBILITY_CPPDEFINES_GENERATOR", ], SHCCFLAGS=[ "$MONGO_VISIBILITY_SHCCFLAGS_GENERATOR", ], ) def library(env, target, source, *args, **kwargs): sharedLibrary = env.SharedLibrary(target, source, *args, **kwargs) sharedArchive = env.SharedArchive(target, source=sharedLibrary[0].sources, *args, **kwargs) sharedLibrary.extend(sharedArchive) return sharedLibrary env["BUILDERS"]["Library"] = library env["BUILDERS"]["LibraryObject"] = env["BUILDERS"]["SharedObject"] # TODO: Ideally, the conditions below should be based on a # detection of what linker we are using, not the local OS, but I # doubt very much that we will see the mach-o linker on anything # other than Darwin, or a BFD/sun-esque linker elsewhere. # On Darwin, we need to tell the linker that undefined symbols are # resolved via dynamic lookup; otherwise we get build failures. On # other unixes, we need to suppress as-needed behavior so that # initializers are ensured present, even if there is no visible # edge to the library in the symbol graph. # # NOTE: The darwin linker flag is only needed because the library # graph is not a DAG. Once the graph is a DAG, we will require all # edges to be expressed, and we should drop the flag. When that # happens, we should also add -z,defs flag on ELF platforms to # ensure that missing symbols due to unnamed dependency edges # result in link errors. # # NOTE: The `illegal_cyclic_or_unresolved_dependencies_allowlisted` # tag can be applied to a library to indicate that it does not (or # cannot) completely express all of its required link dependencies. # This can occur for four reasons: # # - No unique provider for the symbol: Some symbols do not have a # unique dependency that provides a definition, in which case it # is impossible for the library to express a dependency edge to # resolve the symbol. # # - The library is part of a cycle: If library A depends on B, # which depends on C, which depends on A, then it is impossible # to express all three edges in SCons, since otherwise there is # no way to sequence building the libraries. The cyclic # libraries actually work at runtime, because some parent object # links all of them. # # - The symbol is provided by an executable into which the library # will be linked. The mongo::inShutdown symbol is a good # example. # # - The symbol is provided by a third-party library, outside of our # control. # # All of these are defects in the linking model. In an effort to # eliminate these issues, we have begun tagging those libraries # that are affected, and requiring that all non-tagged libraries # correctly express all dependencies. As we repair each defective # library, we can remove the tag. When all the tags are removed # the graph will be acyclic. Libraries which are incomplete for the # final reason, "libraries outside of our control", may remain for # reasons beyond our control. Such libraries ideally should # have no dependencies (and thus be leaves in our linking DAG). # If that condition is met, then the graph will be acyclic. if env.TargetOSIs("darwin"): if link_model.startswith("dynamic"): print( "WARNING: Building MongoDB server with dynamic linking " + "on macOS is not supported. Static linking is recommended." ) if link_model == "dynamic-strict": # Darwin is strict by default pass else: def libdeps_tags_expand_incomplete(source, target, env, for_signature): # On darwin, since it is strict by default, we need to add a flag # when libraries are tagged incomplete. if "illegal_cyclic_or_unresolved_dependencies_allowlisted" in target[ 0 ].get_env().get("LIBDEPS_TAGS", []): return ["-Wl,-undefined,dynamic_lookup"] return [] env["LIBDEPS_TAG_EXPANSIONS"].append(libdeps_tags_expand_incomplete) elif env.TargetOSIs("windows"): if link_model == "dynamic-strict": # Windows is strict by default pass else: def libdeps_tags_expand_incomplete(source, target, env, for_signature): # On windows, since it is strict by default, we need to add a flag # when libraries are tagged incomplete. if "illegal_cyclic_or_unresolved_dependencies_allowlisted" in target[ 0 ].get_env().get("LIBDEPS_TAGS", []): return ["/FORCE:UNRESOLVED"] return [] env["LIBDEPS_TAG_EXPANSIONS"].append(libdeps_tags_expand_incomplete) else: env.AppendUnique(LINKFLAGS=["-Wl,--no-as-needed"]) # Using zdefs doesn't work at all with the sanitizers if not has_option("sanitize") and get_option("sanitize") != "": if link_model == "dynamic-strict": env.AppendUnique(SHLINKFLAGS=["-Wl,-z,defs"]) else: # On BFD/gold linker environments, which are not strict by # default, we need to add a flag when libraries are not # tagged incomplete. def libdeps_tags_expand_incomplete(source, target, env, for_signature): if "illegal_cyclic_or_unresolved_dependencies_allowlisted" not in target[ 0 ].get_env().get("LIBDEPS_TAGS", []): return ["-Wl,-z,defs"] return [] env["LIBDEPS_TAG_EXPANSIONS"].append(libdeps_tags_expand_incomplete) # Enable the fast decider if explicitly requested or if in 'auto' mode # and not in conflict with other options like the ninja option which # sets its own decider. if ( get_option("ninja") == "disabled" and get_option("build-fast-and-loose") == "on" or (get_option("build-fast-and-loose") == "auto" and not has_option("release")) ): # See http://www.scons.org/wiki/GoFastButton for details env.Decider("MD5-timestamp") env.SetOption("max_drift", 1) # If the user has requested the git decider, enable it if it is available. We want to do this after # we set the basic decider above, so that we 'chain' to that one. if get_option("git-decider") == "on": git_decider = Tool("git_decider") if git_decider.exists(env): git_decider(env) # On non-windows platforms, we may need to differentiate between flags being used to target an # executable (like -fPIE), vs those being used to target a (shared) library (like -fPIC). To do so, # we inject a new family of SCons variables PROG*FLAGS, by reaching into the various COMs. if not env.TargetOSIs("windows"): env["CCCOM"] = env["CCCOM"].replace("$CCFLAGS", "$PROGCCFLAGS") env["CXXCOM"] = env["CXXCOM"].replace("$CCFLAGS", "$PROGCCFLAGS") env["PROGCCFLAGS"] = ["$CCFLAGS"] env["CCCOM"] = env["CCCOM"].replace("$CFLAGS", "$PROGCFLAGS") env["PROGCFLAGS"] = ["$CFLAGS"] env["CXXCOM"] = env["CXXCOM"].replace("$CXXFLAGS", "$PROGCXXFLAGS") env["PROGCXXFLAGS"] = ["$CXXFLAGS"] env["LINKCOM"] = env["LINKCOM"].replace("$LINKFLAGS", "$PROGLINKFLAGS") env["PROGLINKFLAGS"] = ["$LINKFLAGS"] # ASPPFLAGS is used for assembler commands, this condition below assumes assembler files # will be only directly assembled in librarys and not programs if link_model.startswith("dynamic"): env.Append(ASPPFLAGS=["-fPIC"]) else: env.Append(ASPPFLAGS=["-fPIE"]) # When it is necessary to supply additional SHLINKFLAGS without modifying the toolset default, # following appends contents of SHLINKFLAGS_EXTRA variable to the linker command env.AppendUnique(SHLINKFLAGS=["$SHLINKFLAGS_EXTRA"]) class ForceVerboseConftest: """ This class allows for configurable substition calls to enable forcing the conftest to use verbose logs even when verbose mode is not specified. """ def __init__(self, msg): self.msg = msg def __call__(self, target, source, env, for_signature): for t in target: # TODO: SERVER-60915 switch to SCons api conftest check if "conftest" in str(t): return None return self.msg if not env.Verbose(): # Even though we are not in Verbose mode, conftest logs should # always be verbose, because they go to a file and not seen # by the user anyways. env.Append(CCCOMSTR=ForceVerboseConftest("Compiling $TARGET")) env.Append(CXXCOMSTR=ForceVerboseConftest(env["CCCOMSTR"])) env.Append(SHCCCOMSTR=ForceVerboseConftest("Compiling $TARGET")) env.Append(SHCXXCOMSTR=ForceVerboseConftest(env["SHCCCOMSTR"])) env.Append(LINKCOMSTR=ForceVerboseConftest("Linking $TARGET")) env.Append(SHLINKCOMSTR=ForceVerboseConftest(env["LINKCOMSTR"])) env.Append(ARCOMSTR=ForceVerboseConftest("Generating library $TARGET")) # Link tools other than mslink don't setup TEMPFILE in LINKCOM, # disabling SCons automatically falling back to a temp file when # running link commands that are over MAXLINELENGTH. With our object # file linking mode, we frequently hit even the large linux command # line length, so we want it everywhere. If we aren't using mslink, # add TEMPFILE in. For verbose builds when using a tempfile, we need # some trickery so that we print the command we are running, and not # just the invocation of the compiler being fed the command file. if "mslink" not in env["TOOLS"]: if env.Verbose(): env["LINKCOM"] = "${{TEMPFILE('{0}', '')}}".format(env["LINKCOM"]) env["SHLINKCOM"] = "${{TEMPFILE('{0}', '')}}".format(env["SHLINKCOM"]) if "libtool" not in env["TOOLS"]: env["ARCOM"] = "${{TEMPFILE('{0}', '')}}".format(env["ARCOM"]) else: env["LINKCOM"] = "${{TEMPFILE('{0}', 'LINKCOMSTR')}}".format(env["LINKCOM"]) env["SHLINKCOM"] = "${{TEMPFILE('{0}', 'SHLINKCOMSTR')}}".format(env["SHLINKCOM"]) if "libtool" not in env["TOOLS"]: env["ARCOM"] = "${{TEMPFILE('{0}', 'ARCOMSTR')}}".format(env["ARCOM"]) if env["_LIBDEPS"] == "$_LIBDEPS_OBJS": # The libraries we build in LIBDEPS_OBJS mode are just placeholders for tracking dependencies. # This avoids wasting time and disk IO on them. def write_uuid_to_file(env, target, source): with open(env.File(target[0]).abspath, "w") as fake_lib: fake_lib.write(str(uuid.uuid4())) fake_lib.write("\n") # We originally did this by setting ARCOM to write_uuid_to_file. # This worked more or less by accident. It works when SCons is # doing the action execution because when it would subst the # command line subst would execute the function as part of string # resolution which would have the side effect of writing the # file. Since it returned None subst would do some special # handling to make sure it never made it to the command line. This # breaks Ninja however because we are taking that return value and # trying to pass it to the command executor (/bin/sh or # cmd.exe) and end up with the function name as a command. The # resulting command looks something like `/bin/sh -c # 'write_uuid_to_file(env, target, source)`. If we instead # actually do what we want and that is make the StaticLibrary # builder's action a FunctionAction the Ninja generator will # correctly dispatch it and not generate an invalid command # line. This also has the side benefit of being more clear that # we're expecting a Python function to execute here instead of # pretending to be a CommandAction that just happens to not run a # command but instead runs a function. env["BUILDERS"]["StaticLibrary"].action = SCons.Action.Action( write_uuid_to_file, "Generating placeholder library $TARGET" ) import libdeps_tool as libdeps libdeps.setup_environment( env, emitting_shared=link_model, debug=get_option("libdeps-debug"), linting=get_option("libdeps-linting"), ) # The abilink/tapilink tools and the thin archive tool must be loaded # after libdeps, so that the scanners they inject can see the library # dependencies added by libdeps. Neither abilink nor tapilink can work # with the current Ninja generation because they rely on adding # ListActions to builders. if get_option("ninja") == "disabled" and link_model.startswith("dynamic"): # Add in the abi linking tool if the user requested and it is # supported on this platform. # # TODO: Can we unify the `abilink` and `tapilink` tools? if env.get("ABIDW"): abilink = Tool("abilink") if abilink.exists(env): abilink(env) if env.get("TAPI"): # TAPI is less useful when running with Bazel + Remote Execution. Disable since the initial implementation # of the build system with Bazel will not support it. # TODO(SERVER-88612): Remove fatal error we decide to implement TAPI support in Bazel env.FatalError("TAPI is not supported with the hybrid build system.") tapilink = Tool("tapilink") if tapilink.exists(env): tapilink(env) if env["_LIBDEPS"] == "$_LIBDEPS_LIBS": # The following platforms probably aren't using the binutils # toolchain, or may be using it for the archiver but not the # linker, and binutils currently is the only thing that supports # thin archives. Don't even try on those platforms. if not env.TargetOSIs("solaris", "darwin", "windows", "openbsd"): env.Tool("thin_archive") if env.TargetOSIs("linux", "freebsd", "openbsd"): env["LINK_WHOLE_ARCHIVE_LIB_START"] = "-Wl,--whole-archive" env["LINK_WHOLE_ARCHIVE_LIB_END"] = "-Wl,--no-whole-archive" env["LINK_AS_NEEDED_LIB_START"] = "-Wl,--as-needed" env["LINK_AS_NEEDED_LIB_END"] = "-Wl,--no-as-needed" elif env.TargetOSIs("darwin"): env["LINK_WHOLE_ARCHIVE_LIB_START"] = "-Wl,-force_load" env["LINK_WHOLE_ARCHIVE_LIB_END"] = "" env["LINK_AS_NEEDED_LIB_START"] = "-Wl,-mark_dead_strippable_dylib" env["LINK_AS_NEEDED_LIB_END"] = "" elif env.TargetOSIs("solaris"): env["LINK_WHOLE_ARCHIVE_LIB_START"] = "-Wl,-z,allextract" env["LINK_WHOLE_ARCHIVE_LIB_END"] = "-Wl,-z,defaultextract" elif env.TargetOSIs("windows"): env["LINK_WHOLE_ARCHIVE_LIB_START"] = "/WHOLEARCHIVE" env["LINK_WHOLE_ARCHIVE_LIB_END"] = "" env["LIBDEPS_FLAG_SEPARATORS"] = {env["LINK_WHOLE_ARCHIVE_LIB_START"]: {"suffix": ":"}} if env.TargetOSIs("darwin") and link_model.startswith("dynamic"): def init_no_global_libdeps_tag_expansion(source, target, env, for_signature): """ This callable will be expanded by scons and modify the environment by adjusting the prefix and postfix flags to account for linking options related to the use of global static initializers for any given libdep. """ if "init-no-global-side-effects" in env.get(libdeps.Constants.LibdepsTags, []): # macos as-needed flag is used on the library directly when it is built return env.get("LINK_AS_NEEDED_LIB_START", "") env["LIBDEPS_TAG_EXPANSIONS"].append(init_no_global_libdeps_tag_expansion) def init_no_global_add_flags(target, start_flag, end_flag): """Helper function for init_no_global_libdeps_tag_expand""" setattr(target[0].attributes, "libdeps_prefix_flags", [start_flag]) setattr(target[0].attributes, "libdeps_postfix_flags", [end_flag]) if env.TargetOSIs("linux", "freebsd", "openbsd"): setattr( target[0].attributes, "libdeps_switch_flags", [ { "on": start_flag, "off": end_flag, } ], ) def init_no_global_libdeps_tag_emitter(target, source, env): """ This emitter will be attached the correct pre and post fix flags to a given library to cause it to have certain flags before or after on the link line. """ if link_model == "dynamic": start_flag = env.get("LINK_AS_NEEDED_LIB_START", "") end_flag = env.get("LINK_AS_NEEDED_LIB_END", "") # In the dynamic case, any library that is known to not have global static # initializers can supply the flag and be wrapped in --as-needed linking, # allowing the linker to be smart about linking libraries it may not need. if "init-no-global-side-effects" in env.get( libdeps.Constants.LibdepsTags, [] ) and not env.TargetOSIs("darwin"): init_no_global_add_flags(target, start_flag, end_flag) else: init_no_global_add_flags(target, "", "") else: start_flag = env.get("LINK_WHOLE_ARCHIVE_LIB_START", "") end_flag = env.get("LINK_WHOLE_ARCHIVE_LIB_END", "") # In the static case, any library that is unknown to have global static # initializers should supply the flag and be wrapped in --whole-archive linking, # allowing the linker to bring in all those symbols which may not be directly needed # at link time. if "init-no-global-side-effects" not in env.get(libdeps.Constants.LibdepsTags, []): init_no_global_add_flags(target, start_flag, end_flag) else: init_no_global_add_flags(target, "", "") return target, source for target_builder in ["SharedLibrary", "SharedArchive", "StaticLibrary"]: builder = env["BUILDERS"][target_builder] base_emitter = builder.emitter new_emitter = SCons.Builder.ListEmitter([base_emitter, init_no_global_libdeps_tag_emitter]) builder.emitter = new_emitter link_guard_rules = { "test": [ "dist", ], } class LibdepsLinkGuard(SCons.Errors.UserError): pass def checkComponentType(target_comps, comp, target, lib): """ For a libdep and each AIB_COMPONENT its labeled as, check if its violates any of the link gaurd rules. """ for target_comp in target_comps: for link_guard_rule in link_guard_rules: if target_comp in link_guard_rules[link_guard_rule] and link_guard_rule in comp: raise LibdepsLinkGuard( textwrap.dedent(f"""\n LibdepsLinkGuard: \tTarget '{target[0]}' links LIBDEP '{lib}' \tbut is listed as AIB_COMPONENT '{target_comp}' which is not allowed link libraries \twith AIB_COMPONENTS that include the word '{link_guard_rule}'\n""") ) def get_comps(env): """util function for extracting all AIB_COMPONENTS as a list""" comps = env.get("AIB_COMPONENTS_EXTRA", []) comp = env.get("AIB_COMPONENT", None) if comp: comps += [comp] return comps def link_guard_libdeps_tag_expand(source, target, env, for_signature): """ Callback function called on all binaries to check if a certain binary from a given component is linked to another binary of a given component, the goal being to define rules that prevents test components from being linked into production or releaseable components. """ for lib in libdeps.get_libdeps(source, target, env, for_signature): if not lib.env: continue for comp in get_comps(lib.env): checkComponentType(get_comps(env), comp, target, lib) return [] env["LIBDEPS_TAG_EXPANSIONS"].append(link_guard_libdeps_tag_expand) env.Tool("forceincludes") # ---- other build setup ----- if not debugBuild: env.AppendUnique(CPPDEFINES=["NDEBUG"]) # Normalize our experimental optimiation and hardening flags selected_experimental_optimizations = set() for suboption in get_option("experimental-optimization"): if suboption == "*": selected_experimental_optimizations.update(experimental_optimizations) elif suboption.startswith("-"): selected_experimental_optimizations.discard(suboption[1:]) elif suboption.startswith("+"): selected_experimental_optimizations.add(suboption[1:]) selected_experimental_runtime_hardenings = set() for suboption in get_option("experimental-runtime-hardening"): if suboption == "*": selected_experimental_runtime_hardenings.update(experimental_runtime_hardenings) elif suboption.startswith("-"): selected_experimental_runtime_hardenings.discard(suboption[1:]) elif suboption.startswith("+"): selected_experimental_runtime_hardenings.add(suboption[1:]) # Disable floating-point contractions such as forming of fused multiply-add operations. if env.ToolchainIs("clang", "gcc"): env.Append(CCFLAGS=["-ffp-contract=off"]) else: # msvc defaults to /fp:precise. Visual Studio 2022 does not emit floating-point contractions # with /fp:precise, but previous versions can. Disable contractions altogether by using # /fp:strict. env.Append(CCFLAGS=["/fp:strict"]) if env.TargetOSIs("linux"): env.Append(LIBS=["m"]) if not env.TargetOSIs("android"): env.Append(LIBS=["resolv"]) elif env.TargetOSIs("solaris"): env.Append(LIBS=["socket", "resolv", "lgrp"]) elif env.TargetOSIs("freebsd"): env.Append(LIBS=["kvm"]) env.Append(CCFLAGS=["-fno-omit-frame-pointer"]) elif env.TargetOSIs("darwin"): env.Append(LIBS=["resolv"]) elif env.TargetOSIs("openbsd"): env.Append(LIBS=["kvm"]) elif env.TargetOSIs("windows"): env["DIST_ARCHIVE_SUFFIX"] = ".zip" # If tools configuration fails to set up 'cl' in the path, fall back to importing the whole # shell environment and hope for the best. This will work, for instance, if you have loaded # an SDK shell. for pathdir in env["ENV"]["PATH"].split(os.pathsep): if os.path.exists(os.path.join(pathdir, "cl.exe")): break else: print("NOTE: Tool configuration did not find 'cl' compiler, falling back to os environment") env["ENV"] = dict(os.environ) env.Append( CPPDEFINES=[ # This tells the Windows compiler not to link against the .lib files # and to use boost as a bunch of header-only libraries "BOOST_ALL_NO_LIB", ] ) env.Append(CPPDEFINES=["_UNICODE"]) env.Append(CPPDEFINES=["UNICODE"]) # Temporary fixes to allow compilation with VS2017 env.Append( CPPDEFINES=[ "_SILENCE_CXX17_ALLOCATOR_VOID_DEPRECATION_WARNING", "_SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING", "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING", # TODO(SERVER-60151): Until we are fully in C++20 mode, it is # easier to simply suppress C++20 deprecations. After we have # switched over we should address any actual deprecated usages # and then remove this flag. "_SILENCE_ALL_CXX20_DEPRECATION_WARNINGS", ] ) # /EHsc exception handling style for visual studio # /W3 warning level env.Append(CCFLAGS=["/EHsc", "/W3"]) # Suppress some warnings we don't like, or find necessary to # suppress. Please keep this list alphabetized and commented. env.Append( CCFLAGS=[ # C4068: unknown pragma. added so that we can specify unknown # pragmas for other compilers. "/wd4068", # C4244: 'conversion' conversion from 'type1' to 'type2', # possible loss of data. An integer type is converted to a # smaller integer type. "/wd4244", # C4267: 'var' : conversion from 'size_t' to 'type', possible # loss of data. When compiling with /Wp64, or when compiling # on a 64-bit operating system, type is 32 bits but size_t is # 64 bits when compiling for 64-bit targets. To fix this # warning, use size_t instead of a type. "/wd4267", # C4290: C++ exception specification ignored except to # indicate a function is not __declspec(nothrow). A function # is declared using exception specification, which Visual C++ # accepts but does not implement. "/wd4290", # C4351: On extremely old versions of MSVC (pre 2k5), default # constructing an array member in a constructor's # initialization list would not zero the array members "in # some cases". Since we don't target MSVC versions that old, # this warning is safe to ignore. "/wd4351", # C4355: 'this' : used in base member initializer list. The # this pointer is valid only within nonstatic member # functions. It cannot be used in the initializer list for a # base class. "/wd4355", # C4373: Older versions of MSVC would fail to make a function # in a derived class override a virtual function in the # parent, when defined inline and at least one of the # parameters is made const. The behavior is incorrect under # the standard. MSVC is fixed now, and the warning exists # merely to alert users who may have relied upon the older, # non-compliant behavior. Our code should not have any # problems with the older behavior, so we can just disable # this warning. "/wd4373", # C4800: 'type' : forcing value to bool 'true' or 'false' # (performance warning). This warning is generated when a # value that is not bool is assigned or coerced into type # bool. "/wd4800", # C4251: This warning attempts to prevent usage of CRT (C++ # standard library) types in DLL interfaces. That is a good # idea for DLLs you ship to others, but in our case, we know # that all DLLs are built consistently. Suppress the warning. "/wd4251", ] ) # mozjs requires the following # 'declaration' : no matching operator delete found; memory will not be freed if # initialization throws an exception env.Append(CCFLAGS=["/wd4291"]) # some warnings we should treat as errors: # c4013 # 'function' undefined; assuming extern returning int # This warning occurs when files compiled for the C language use functions not defined # in a header file. # c4099 # 'identifier' : type name first seen using 'objecttype1' now seen using 'objecttype2' # This warning occurs when classes and structs are declared with a mix of struct and class # which can cause linker failures # c4930 # 'identifier': prototyped function not called (was a variable definition intended?) # This warning indicates a most-vexing parse error, where a user declared a function that # was probably intended as a variable definition. A common example is accidentally # declaring a function called lock that takes a mutex when one meant to create a guard # object called lock on the stack. env.Append(CCFLAGS=["/we4013", "/we4099", "/we4930"]) env.Append( CPPDEFINES=[ "_CONSOLE", "_CRT_SECURE_NO_WARNINGS", "_ENABLE_EXTENDED_ALIGNED_STORAGE", "_SCL_SECURE_NO_WARNINGS", ] ) # this would be for pre-compiled headers, could play with it later # env.Append( CCFLAGS=['/Yu"pch.h"'] ) # Don't send error reports in case of internal compiler error env.Append(CCFLAGS=["/errorReport:none"]) # Select debugging format. /Zi gives faster links but seems to use more memory. if get_option("msvc-debugging-format") == "codeview" and debug_symbols: env["CCPDBFLAGS"] = "/Z7" elif get_option("msvc-debugging-format") == "pdb" and debug_symbols: env["CCPDBFLAGS"] = "/Zi /Fd${TARGET}.pdb" # The SCons built-in pdbGenerator always adds /DEBUG, but we would like # control over that flag so that users can override with /DEBUG:fastlink # for better local builds. So we overwrite the builtin. def pdbGenerator(env, target, source, for_signature): try: return ["/PDB:%s" % target[0].attributes.pdb] except (AttributeError, IndexError): return None env["_PDB"] = pdbGenerator # SCons by default adds debug flags /Z7, /Zi and /Debug # we want to remove these if debug_symbols are off if not debug_symbols: del env["CCPDBFLAGS"] # /DEBUG will tell the linker to create a .pdb file # which WinDbg and Visual Studio will use to resolve # symbols if you want to debug a release-mode image. # Note that this means we can't do parallel links in the build. # # Please also note that this has nothing to do with _DEBUG or optimization. # If the user set a /DEBUG flag explicitly, don't add # another. Otherwise use the standard /DEBUG flag, since we always # want PDBs. if not any(flag.startswith("/DEBUG") for flag in env["LINKFLAGS"]) and debug_symbols: env.Append(LINKFLAGS=["/DEBUG"]) # /MD: use the multithreaded, DLL version of the run-time library (MSVCRT.lib/MSVCR###.DLL) # /MDd: Defines _DEBUG, _MT, _DLL, and uses MSVCRTD.lib/MSVCRD###.DLL env.Append(CCFLAGS=["/MDd" if debugBuild else "/MD"]) if optBuild == "off": env.Append( CCFLAGS=["/Od"], # windows non optimized builds will cause the PDB to blow up in size, # this allows a larger PDB. The flag is undocumented at the time of writing # but the microsoft thread which brought about its creation can be found here: # https://developercommunity.visualstudio.com/t/pdb-limit-of-4-gib-is-likely-to-be-a-problem-in-a/904784 # # Without this flag MSVC will report a red herring error message, about disk space or invalid path. LINKFLAGS=["/pdbpagesize:16384"], ) if debugBuild: # /RTC1: - Enable Stack Frame Run-Time Error Checking; Reports when a variable is used # without having been initialized (implies /Od: no optimizations) env.Append(CCFLAGS=["/RTC1"]) else: # /O1: optimize for size # /O2: optimize for speed (as opposed to size) # /Oy-: disable frame pointer optimization (overrides /O2, only affects 32-bit) # /INCREMENTAL: NO - disable incremental link - avoid the level of indirection for function # calls optFlags = [] if optBuild == "size": optFlags += ["/Os"] elif optBuild == "debug": optFlags += ["/Ox", "/Zo"] else: optFlags += ["/O2"] optFlags += ["/Oy-"] env.Append(CCFLAGS=optFlags) env.Append(LINKFLAGS=["/INCREMENTAL:NO"]) # Support large object files since some unit-test sources contain a lot of code env.Append(CCFLAGS=["/bigobj"]) # Set Source and Executable character sets to UTF-8, this will produce a warning C4828 if the # file contains invalid UTF-8. env.Append(CCFLAGS=["/utf-8"]) # Specify standards conformance mode to the compiler. env.Append(CCFLAGS=["/permissive-"]) # Enables the __cplusplus preprocessor macro to report an updated value for recent C++ language # standards support. env.Append(CCFLAGS=["/Zc:__cplusplus"]) # Tells the compiler to preferentially call global operator delete or operator delete[] # functions that have a second parameter of type size_t when the size of the object is available. env.Append(CCFLAGS=["/Zc:sizedDealloc"]) # Treat volatile according to the ISO standard and do not guarantee acquire/release semantics. env.Append(CCFLAGS=["/volatile:iso"]) # Tell CL to produce more useful error messages. env.Append(CCFLAGS=["/diagnostics:caret"]) # This gives 32-bit programs 4 GB of user address space in WOW64, ignored in 64-bit builds. env.Append(LINKFLAGS=["/LARGEADDRESSAWARE"]) env.Append( LIBS=[ "DbgHelp", "Iphlpapi", "Psapi", "advapi32", "bcrypt", "crypt32", "dnsapi", "kernel32", "shell32", "pdh", "version", "winmm", "ws2_32", "secur32", ], ) # When building on visual studio, this sets the name of the debug symbols file if env.ToolchainIs("msvc") and debug_symbols: env["PDB"] = "${TARGET.base}.pdb" # Python uses APPDATA to determine the location of user installed # site-packages. If we do not pass this variable down to Python # subprocesses then anything installed with `pip install --user` # will be inaccessible leading to import errors. # # Use env['PLATFORM'] instead of TargetOSIs since we always want this # to run on Windows hosts but not always for Windows targets. if env["PLATFORM"] == "win32": appdata = os.getenv("APPDATA", None) if appdata is not None: env["ENV"]["APPDATA"] = appdata if env.TargetOSIs("posix"): # On linux, C code compiled with gcc/clang -std=c11 causes # __STRICT_ANSI__ to be set, and that drops out all of the feature # test definitions, resulting in confusing errors when we run C # language configure checks and expect to be able to find newer # POSIX things. Explicitly enabling _XOPEN_SOURCE fixes that, and # should be mostly harmless as on Linux, these macros are # cumulative. The C++ compiler already sets _XOPEN_SOURCE, and, # notably, setting it again does not disable any other feature # test macros, so this is safe to do. Other platforms like macOS # and BSD have crazy rules, so don't try this there. # # Furthermore, as both C++ compilers appear to define _GNU_SOURCE # unconditionally (because libstdc++ requires it), it seems # prudent to explicitly add that too, so that C language checks # see a consistent set of definitions. if env.TargetOSIs("linux"): env.AppendUnique( CPPDEFINES=[ ("_XOPEN_SOURCE", 700), "_GNU_SOURCE", ], ) # If shared and static object files stripped of their rightmost # dot-delimited suffix would collide, modify the shared library # ones so that they won't. We do this because if split dwarf is in # play, static and dynamic builds would otherwise overwrite each # other's .dwo files, because GCC strips the last suffix and adds # .dwo, rather than simply appending .dwo to the full filename. objsuffelts = env.subst("$OBJSUFFIX").split(".") shobjsuffelts = env.subst("$SHOBJSUFFIX").split(".") if objsuffelts[0:-1] == shobjsuffelts[0:-1]: env["SHOBJSUFFIX"] = ".dyn${OBJSUFFIX}" # Everything on OS X is position independent by default. if not env.TargetOSIs("darwin"): if get_option("runtime-hardening") == "on": # If runtime hardening is requested, then build anything # destined for an executable with the necessary flags for PIE. env.AppendUnique( PROGCFLAGS=["-fPIE"], PROGCCFLAGS=["-fPIE"], PROGCXXFLAGS=["-fPIE"], PROGLINKFLAGS=["-pie"], ) # -Winvalid-pch Warn if a precompiled header (see Precompiled Headers) is found in the search path but can't be used. env.Append( CCFLAGS=[ "-fasynchronous-unwind-tables", "-g2" if not env.TargetOSIs("emscripten") else "-g", "-Wall", "-Wsign-compare", "-Wno-unknown-pragmas", "-Winvalid-pch", ], ) if env.get("DWARF_VERSION"): if env.TargetOSIs("darwin"): env.FatalError("Setting DWARF_VERSION on darwin is not supported.") env.AppendUnique( CCFLAGS=["-gdwarf-$DWARF_VERSION"], LINKFLAGS=["-gdwarf-$DWARF_VERSION"], ) # TODO: At least on x86, glibc as of 2.3.4 will consult the # .eh_frame info via _Unwind_Backtrace to do backtracing without # needing the frame pointer, despite what the backtrace man page # actually says. We should see if we can drop the requirement that # we use libunwind here. can_nofp = env.TargetOSIs("darwin") or use_libunwind # For debug builds with tcmalloc, we need the frame pointer so it can # record the stack of allocations. can_nofp &= not ( debugBuild and (env["MONGO_ALLOCATOR"] in ["tcmalloc-google", "tcmalloc-gperf"]) ) # Only disable frame pointers if requested can_nofp &= "nofp" in selected_experimental_optimizations if not can_nofp: env.Append(CCFLAGS=["-fno-omit-frame-pointer"]) if "tbaa" not in selected_experimental_optimizations: env.Append(CCFLAGS=["-fno-strict-aliasing"]) # Enabling hidden visibility on non-darwin requires that we have # libunwind in play, since glibc backtrace will not work # correctly. if "vishidden" in selected_experimental_optimizations and ( env.TargetOSIs("darwin") or use_libunwind ): if link_model.startswith("dynamic"): # In dynamic mode, we can't make the default visibility # hidden because not all libraries have export tags. But # we can at least make inlines hidden. # # TODO: Except on macOS, where we observe lots of crashes # when we enable this. We should investigate further but # it isn't relevant for the purpose of exploring these # flags on linux, where they seem to work fine. if not env.TargetOSIs("darwin"): env.Append(CXXFLAGS=["-fvisibility-inlines-hidden"]) else: # In static mode, we need an escape hatch for a few # libraries that don't work correctly when built with # hidden visiblity. def conditional_visibility_generator(target, source, env, for_signature): if "DISALLOW_VISHIDDEN" in env: return return "-fvisibility=hidden" env.Append( CCFLAGS_VISIBILITY_HIDDEN_GENERATOR=conditional_visibility_generator, CCFLAGS="$CCFLAGS_VISIBILITY_HIDDEN_GENERATOR", ) # env.Append( " -Wconversion" ) TODO: this doesn't really work yet env.Append(CXXFLAGS=["-Woverloaded-virtual"]) # On OS X, clang doesn't want the pthread flag at link time, or it # issues warnings which make it impossible for us to declare link # warnings as errors. See http://stackoverflow.com/a/19382663. if not (env.TargetOSIs("darwin") and env.ToolchainIs("clang")): env.Append(LINKFLAGS=["-pthread"]) # SERVER-9761: Ensure early detection of missing symbols in dependent # libraries at program startup. For non-release dynamic builds we disable # this behavior in the interest of improved mongod startup times. # Xcode15 removed bind_at_load functionality so we cannot have a selection for macosx here # ld: warning: -bind_at_load is deprecated on macOS if has_option("release") or get_option("link-model") != "dynamic": if not env.TargetOSIs("macOS"): env.Append(LINKFLAGS=["-Wl,-z,now"]) # We need to use rdynamic for backtraces with glibc unless we have libunwind. nordyn = env.TargetOSIs("darwin") or use_libunwind # And of course only do rdyn if the experimenter asked for it. nordyn &= "nordyn" in selected_experimental_optimizations if nordyn: def export_symbol_generator(source, target, env, for_signature): symbols = copy.copy(env.get("EXPORT_SYMBOLS", [])) for lib in libdeps.get_libdeps(source, target, env, for_signature): if lib.env: symbols.extend(lib.env.get("EXPORT_SYMBOLS", [])) export_expansion = "${EXPORT_SYMBOL_FLAG}" return [f"-Wl,{export_expansion}{symbol}" for symbol in symbols] env["EXPORT_SYMBOL_GEN"] = export_symbol_generator # For darwin, we need the leading underscore but for others we # don't. Hacky but it works to jam that distinction into the # flag itself, since it already differs on darwin. if env.TargetOSIs("darwin"): env["EXPORT_SYMBOL_FLAG"] = "-exported_symbol,_" else: env["EXPORT_SYMBOL_FLAG"] = "--export-dynamic-symbol," env.Append( PROGLINKFLAGS=[ "$EXPORT_SYMBOL_GEN", ], ) elif not env.TargetOSIs("darwin"): env.Append( PROGLINKFLAGS=[ "-rdynamic", ], ) # make scons colorgcc friendly for key in ("HOME", "TERM"): try: env["ENV"][key] = os.environ[key] except KeyError: pass if has_option("gcov"): if not (env.TargetOSIs("linux") and (env.ToolchainIs("gcc", "clang"))): # TODO: This should become supported under: https://jira.mongodb.org/browse/SERVER-49877 env.FatalError( "Coverage option 'gcov' is currently only supported on linux with gcc and clang. See SERVER-49877." ) env.AppendUnique( CCFLAGS=["--coverage"], LINKFLAGS=["--coverage"], ) if optBuild == "off": env.Append(CCFLAGS=["-O0"]) else: if optBuild == "size": env.Append(CCFLAGS=["-Os"]) elif optBuild == "debug": env.Append(CCFLAGS=["-Og"]) else: if "O3" in selected_experimental_optimizations: env.Append(CCFLAGS=["-O3"]) else: env.Append(CCFLAGS=["-O2"]) if "treevec" in selected_experimental_optimizations: env.Append(CCFLAGS=["-ftree-vectorize"]) wiredtiger = False if get_option("wiredtiger") == "on": # Wiredtiger only supports 64-bit architecture, and will fail to compile on 32-bit # so disable WiredTiger automatically on 32-bit since wiredtiger is on by default if env["TARGET_ARCH"] == "i386": env.FatalError( "WiredTiger is not supported on 32-bit platforms\n" "Re-run scons with --wiredtiger=off to build on 32-bit platforms" ) else: wiredtiger = True if not env.TargetOSIs("windows", "macOS") and (env.ToolchainIs("GCC", "clang")): # By default, apply our current microarchitecture minima. If the # user has customized a flag of the same name in any of CCFLAGS, # CFLAGS, or CXXFLAGS, we disable applying our default to # CCFLAGS. We are assuming the user knows what they are doing, # e.g. we don't try to be smart and notice that they applied it to # CXXFLAGS and therefore still apply it to CFLAGS since they # didn't customize that. Basically, don't send those flags in # unless you a) mean it, and b) know what you are doing, and c) # cover all your bases by either setting it via CCFLAGS, or # setting it for both C and C++ by setting both of CFLAGS and # CXXFLAGS. default_targeting_flags_for_architecture = { "aarch64": {"-march=": "armv8.2-a", "-mtune=": "generic"}, "i386": {"-march=": "nocona", "-mtune=": "generic"}, "ppc64le": {"-mcpu=": "power8", "-mtune=": "power8", "-mcmodel=": "medium"}, "s390x": {"-march=": "z196", "-mtune=": "zEC12"}, } # If we are enabling vectorization in sandybridge mode, we'd # rather not hit the 256 wide vector instructions because the # heavy versions can cause clock speed reductions. if "sandybridge" in selected_experimental_optimizations: default_targeting_flags_for_architecture["x86_64"] = { "-march=": "sandybridge", "-mtune=": "generic", "-mprefer-vector-width=": "128", } default_targeting_flags = default_targeting_flags_for_architecture.get(env["TARGET_ARCH"]) if default_targeting_flags: search_variables = ["CCFLAGS", "CFLAGS", "CXXFLAGS"] for targeting_flag, targeting_flag_value in default_targeting_flags.items(): if not any( flag_value.startswith(targeting_flag) for search_variable in search_variables for flag_value in env[search_variable] ): env.Append(CCFLAGS=[f"{targeting_flag}{targeting_flag_value}"]) # discover modules, and load the (python) module for each module's build.py mongo_modules = moduleconfig.discover_modules("src/mongo/db/modules", get_option("modules")) has_ninja_module = False for module in mongo_modules: if hasattr(module, "NinjaFile"): has_ninja_module = True break if get_option("ninja") != "disabled" and has_ninja_module: env.FatalError( textwrap.dedent("""\ ERROR: Ninja tool option '--ninja' should not be used with the ninja module. Using both options simultaneously may clobber build.ninja files. Remove the ninja module directory or use '--modules= ' to select no modules. If using enterprise module, explicitly set '--modules=' to exclude the ninja module.""") ) if has_ninja_module: print( "WARNING: You are attempting to use the unsupported/legacy ninja module, instead of the integrated ninja generator. You are strongly encouraged to remove the ninja module from your module list and invoke scons with --ninja generate-ninja" ) # --- check system --- ssl_provider = None http_client = get_option("enable-http-client") def isSanitizerEnabled(self, sanitizerName): if "SANITIZERS_ENABLED" not in self: return False if sanitizerName == "fuzzer": return "fuzzer-no-link" in self["SANITIZERS_ENABLED"] return sanitizerName in self["SANITIZERS_ENABLED"] env.AddMethod(isSanitizerEnabled, "IsSanitizerEnabled") def doConfigure(myenv): global wiredtiger global ssl_provider global http_client # Check that the compilers work. # # TODO: Currently, we have some flags already injected. Eventually, this should test the # bare compilers, and we should re-check at the very end that TryCompile and TryLink still # work with the flags we have selected. if myenv.ToolchainIs("msvc"): compiler_minimum_string = "Microsoft Visual Studio 2022 17.0" compiler_test_body = textwrap.dedent( """ #if !defined(_MSC_VER) #error #endif #if _MSC_VER < 1930 #error %s or newer is required to build MongoDB #endif int main(int argc, char* argv[]) { return 0; } """ % compiler_minimum_string ) elif myenv.ToolchainIs("gcc"): compiler_minimum_string = "GCC 11.3" compiler_test_body = textwrap.dedent( """ #if !defined(__GNUC__) || defined(__clang__) #error #endif #if (__GNUC__ < 11) || (__GNUC__ == 11 && __GNUC_MINOR__ < 3) #error %s or newer is required to build MongoDB #endif int main(int argc, char* argv[]) { return 0; } """ % compiler_minimum_string ) elif env.ToolchainIs("clang"): compiler_minimum_string = "clang 12.0 (or Apple XCode 13.0)" compiler_test_body = textwrap.dedent( """ #if !defined(__clang__) #error #endif #if defined(__apple_build_version__) #if __apple_build_version__ < 13000029 #error %s or newer is required to build MongoDB #endif #elif (__clang_major__ < 12) || (__clang_major__ == 12 && __clang_minor__ < 0) #error %s or newer is required to build MongoDB #endif int main(int argc, char* argv[]) { return 0; } """ % (compiler_minimum_string, compiler_minimum_string) ) else: myenv.ConfError("Error: can't check compiler minimum; don't know this compiler...") def CheckForMinimumCompiler(context, language): extension_for = { "C": ".c", "C++": ".cpp", } context.Message( "Checking if %s compiler is %s or newer..." % (language, compiler_minimum_string) ) result = context.TryCompile(compiler_test_body, extension_for[language]) context.Result(result) return result conf = Configure( myenv, help=False, custom_tests={ "CheckForMinimumCompiler": CheckForMinimumCompiler, }, ) c_compiler_validated = conf.CheckForMinimumCompiler("C") cxx_compiler_validated = conf.CheckForMinimumCompiler("C++") suppress_invalid = has_option("disable-minimum-compiler-version-enforcement") if releaseBuild and suppress_invalid: env.FatalError("--disable-minimum-compiler-version-enforcement is forbidden with --release") if not (c_compiler_validated and cxx_compiler_validated): if not suppress_invalid: env.ConfError("ERROR: Refusing to build with compiler that does not meet requirements") print("WARNING: Ignoring failed compiler version check per explicit user request.") print("WARNING: The build may fail, binaries may crash, or may run but corrupt data...") # Figure out what our minimum windows version is. If the user has specified, then use # that. if env.TargetOSIs("windows"): if has_option("win-version-min"): win_version_min = get_option("win-version-min") else: # If no minimum version has been specified, use our default. win_version_min = "win10" env["WIN_VERSION_MIN"] = win_version_min win_version_min = win_version_min_choices[win_version_min] env.Append(CPPDEFINES=[("_WIN32_WINNT", "0x" + win_version_min[0])]) env.Append(CPPDEFINES=[("BOOST_USE_WINAPI_VERSION", "0x" + win_version_min[0])]) env.Append(CPPDEFINES=[("NTDDI_VERSION", "0x" + win_version_min[0] + win_version_min[1])]) conf.Finish() # We require macOS 10.14 or newer if env.TargetOSIs("darwin"): # TODO: Better error messages, mention the various -mX-version-min-flags in the error, and # single source of truth for versions, plumbed through #ifdef ladder and error messages. def CheckDarwinMinima(context): test_body = """ #include #include #include #if TARGET_OS_OSX && (__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_14) #error 1 #endif """ context.Message( "Checking for sufficient {0} target version minimum... ".format( context.env["TARGET_OS"] ) ) ret = context.TryCompile(textwrap.dedent(test_body), ".c") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckDarwinMinima": CheckDarwinMinima, }, ) if not conf.CheckDarwinMinima(): conf.env.ConfError("Required target minimum of macOS 10.14 not found") conf.Finish() def CheckFlag(env, flag, tool, extension, link, **mutation): def CheckFlagTest(context, tool, extension, flag): if link: if tool == "C": test_body = """ #include #include int main() { printf("Hello, World!"); return EXIT_SUCCESS; }""" elif tool == "C++": test_body = """ #include #include int main() { std::cout << "Hello, World!" << std::endl; return EXIT_SUCCESS; }""" context.Message("Checking if linker supports %s... " % (flag)) ret = context.TryLink(textwrap.dedent(test_body), extension) else: test_body = "" context.Message("Checking if %s compiler supports %s... " % (tool, flag)) ret = context.TryCompile(textwrap.dedent(test_body), extension) context.Result(ret) return ret if env.ToolchainIs("msvc"): env.FatalError("AddFlagIfSupported is not currently supported with MSVC") test_mutation = mutation if env.ToolchainIs("gcc"): test_mutation = copy.deepcopy(mutation) # GCC helpfully doesn't issue a diagnostic on unknown flags of the form -Wno-xxx # unless other diagnostics are triggered. That makes it tough to check for support # for -Wno-xxx. To work around, if we see that we are testing for a flag of the # form -Wno-xxx (but not -Wno-error=xxx), we also add -Wxxx to the flags. GCC does # warn on unknown -Wxxx style flags, so this lets us probe for availablity of # -Wno-xxx. for kw in list(test_mutation.keys()): test_flags = test_mutation[kw] for test_flag in test_flags: if test_flag.startswith("-Wno-") and not test_flag.startswith("-Wno-error="): test_flags.append(re.sub("^-Wno-", "-W", test_flag)) # If the user has selected ``configure` in # `disable-warnings-as-errors`, the usual mechanisms that # would inject Werror or similar are disabled for # conftests. But AddFlagIfSupported requires that those flags # be used. Disable the generators so we have explicit control. cloned = env.Clone( CCFLAGS_GENERATE_WERROR=[], CXXFLAGS_GENERATE_WERROR=[], LINKFLAGS_GENERATE_WERROR=[], ) cloned.Append(**test_mutation) # Add these *after* the test mutation, so that the mutation # can't step on the warnings-as-errors state. cloned.Append( CCFLAGS=["$CCFLAGS_WERROR"], CXXFLAGS=["$CXXFLAGS_WERROR"], LINKFLAGS=["$LINKFLAGS_WERROR"], ) conf = Configure( cloned, help=False, custom_tests={ "CheckFlag": lambda ctx: CheckFlagTest(ctx, tool, extension, flag), }, ) available = conf.CheckFlag() conf.Finish() return available def AddFlagIfSupported(env, flag, tool, extension, link, **mutation): available = CheckFlag(env, flag, tool, extension, link, **mutation) if available: env.Append(**mutation) return available conf_check_vars = { "CFLAGS": {"tool": "C", "extension": ".c", "link": False}, "CCFLAGS": {"tool": "C", "extension": ".c", "link": False}, "CXXFLAGS": {"tool": "C++", "extension": ".cpp", "link": False}, "LINKFLAGS": {"tool": "C", "extension": ".c", "link": True}, "SHLINKFLAGS": {"tool": "C", "extension": ".c", "link": True}, } def var_func(env, flag, var, func): kwargs = dict({var: [flag]}, **conf_check_vars[var]) return func(env, flag, **kwargs) for var in conf_check_vars: myenv.AddMethod( functools.partial(var_func, var=var, func=AddFlagIfSupported), f"AddTo{var}IfSupported" ) myenv.AddMethod( functools.partial(var_func, var=var, func=CheckFlag), f"Check{var}Supported" ) if myenv.ToolchainIs("gcc", "clang"): # This tells clang/gcc to use the gold linker if it is available - we prefer the gold linker # because it is much faster. Don't use it if the user has already configured another linker # selection manually. if any(flag.startswith("-fuse-ld=") for flag in env["LINKFLAGS"]): myenv.FatalError( "Use the '--linker' option instead of modifying the LINKFLAGS directly." ) linker_ld = get_option("linker") if linker_ld == "bfd": myenv.FatalError("The linker 'bfd' is not supported.") elif linker_ld == "auto": if not env.TargetOSIs("darwin", "macOS"): if not myenv.AddToLINKFLAGSIfSupported("-fuse-ld=lld"): myenv.FatalError( "The recommended linker 'lld' is not supported with the current compiler configuration, you can try the 'gold' linker with '--linker=gold'." ) elif link_model.startswith("dynamic") and linker_ld == "bfd": # BFD is not supported due to issues with it causing warnings from some of # the third party libraries that mongodb is linked with: # https://jira.mongodb.org/browse/SERVER-49465 myenv.FatalError(f"Linker {linker_ld} is not supported with dynamic link model builds.") else: if not myenv.AddToLINKFLAGSIfSupported(f"-fuse-ld={linker_ld}"): myenv.FatalError(f"Linker {linker_ld} could not be configured.") if has_option("gcov") and myenv.AddToCCFLAGSIfSupported("-fprofile-update=single"): myenv.AppendUnique(LINKFLAGS=["-fprofile-update=single"]) detectCompiler = Configure( myenv, help=False, custom_tests={ "CheckForCXXLink": CheckForCXXLink, }, ) if not detectCompiler.CheckCC(): env.ConfError( "C compiler {0} doesn't work", detectEnv["CC"], ) if not detectCompiler.CheckCXX(): env.ConfError( "C++ compiler {0} doesn't work", detectEnv["CXX"], ) if not detectCompiler.CheckForCXXLink(): env.ConfError( "C++ compiler {0} can't link C++ programs", detectEnv["CXX"], ) detectCompiler.Finish() if myenv.ToolchainIs("clang", "gcc"): # This warning was added in g++-4.8. myenv.AddToCCFLAGSIfSupported("-Wno-unused-local-typedefs") # Clang likes to warn about unused functions, which seems a tad aggressive and breaks # -Werror, which we want to be able to use. myenv.AddToCCFLAGSIfSupported("-Wno-unused-function") # TODO: Note that the following two flags are added to CCFLAGS even though they are # really C++ specific. We need to do this because SCons passes CXXFLAGS *before* # CCFLAGS, but CCFLAGS contains -Wall, which re-enables the warnings we are trying to # suppress. In the future, we should move all warning flags to CCWARNFLAGS and # CXXWARNFLAGS and add these to CCOM and CXXCOM as appropriate. # # Clang likes to warn about unused private fields, but some of our third_party # libraries have such things. myenv.AddToCCFLAGSIfSupported("-Wno-unused-private-field") # Prevents warning about using deprecated features (such as auto_ptr in c++11) # Using -Wno-error=deprecated-declarations does not seem to work on some compilers, # including at least g++-4.6. myenv.AddToCCFLAGSIfSupported("-Wno-deprecated-declarations") # As of clang-3.4, this warning appears in v8, and gets escalated to an error. myenv.AddToCCFLAGSIfSupported("-Wno-tautological-constant-out-of-range-compare") # As of clang in Android NDK 17, these warnings appears in boost and/or ICU, and get escalated to errors myenv.AddToCCFLAGSIfSupported("-Wno-tautological-constant-compare") myenv.AddToCCFLAGSIfSupported("-Wno-tautological-unsigned-zero-compare") myenv.AddToCCFLAGSIfSupported("-Wno-tautological-unsigned-enum-zero-compare") # New in clang-3.4, trips up things mostly in third_party, but in a few places in the # primary mongo sources as well. myenv.AddToCCFLAGSIfSupported("-Wno-unused-const-variable") # Prevents warning about unused but set variables found in boost version 1.49 # in boost/date_time/format_date_parser.hpp which does not work for compilers # GCC >= 4.6. Error explained in https://svn.boost.org/trac/boost/ticket/6136 . myenv.AddToCCFLAGSIfSupported("-Wno-unused-but-set-variable") # This has been suppressed in gcc 4.8, due to false positives, but not in clang. So # we explicitly disable it here. myenv.AddToCCFLAGSIfSupported("-Wno-missing-braces") # Suppress warnings about not consistently using override everywhere in a class. It seems # very pedantic, and we have a fair number of instances. myenv.AddToCCFLAGSIfSupported("-Wno-inconsistent-missing-override") # Don't issue warnings about potentially evaluated expressions myenv.AddToCCFLAGSIfSupported("-Wno-potentially-evaluated-expression") # SERVER-76472 we don't try to maintain ABI so disable warnings about possible ABI issues. myenv.AddToCCFLAGSIfSupported("-Wno-psabi") # Warn about moves of prvalues, which can inhibit copy elision. myenv.AddToCXXFLAGSIfSupported("-Wpessimizing-move") # Disable warning about variables that may not be initialized # Failures are triggered in the case of boost::optional in GCC 4.8.x # TODO: re-evaluate when we move to GCC 5.3 # see: http://stackoverflow.com/questions/21755206/how-to-get-around-gcc-void-b-4-may-be-used-uninitialized-in-this-funct myenv.AddToCXXFLAGSIfSupported("-Wno-maybe-uninitialized") # Disable warning about templates that can't be implicitly instantiated. It is an attempt to # make a link error into an easier-to-debug compiler failure, but it triggers false # positives if explicit instantiation is used in a TU that can see the full definition. This # is a problem at least for the S2 headers. myenv.AddToCXXFLAGSIfSupported("-Wno-undefined-var-template") # This warning was added in clang-4.0, but it warns about code that is required on some # platforms. Since the warning just states that 'explicit instantiation of [a template] that # occurs after an explicit specialization has no effect', it is harmless on platforms where # it isn't required myenv.AddToCXXFLAGSIfSupported("-Wno-instantiation-after-specialization") # This warning was added in clang-5 and flags many of our lambdas. Since it isn't actively # harmful to capture unused variables we are suppressing for now with a plan to fix later. myenv.AddToCCFLAGSIfSupported("-Wno-unused-lambda-capture") # Enable sized deallocation support. myenv.AddToCXXFLAGSIfSupported("-fsized-deallocation") # This warning was added in Apple clang version 11 and flags many explicitly defaulted move # constructors and assignment operators for being implicitly deleted, which is not useful. myenv.AddToCXXFLAGSIfSupported("-Wno-defaulted-function-deleted") # SERVER-44856: Our windows builds complain about unused # exception parameters, but GCC and clang don't seem to do # that for us automatically. In the interest of making it more # likely to catch these errors early, add the (currently clang # only) flag that turns it on. myenv.AddToCXXFLAGSIfSupported("-Wunused-exception-parameter") # TODO SERVER-58675 - Remove this suppression after abseil is upgraded myenv.AddToCXXFLAGSIfSupported("-Wno-deprecated-builtins") # Check if we can set "-Wnon-virtual-dtor" when "-Werror" is set. The only time we can't set it is on # clang 3.4, where a class with virtual function(s) and a non-virtual destructor throws a warning when # it shouldn't. def CheckNonVirtualDtor(context): test_body = """ class Base { public: virtual void foo() const = 0; protected: ~Base() {}; }; class Derived : public Base { public: virtual void foo() const {} }; """ context.Message("Checking if -Wnon-virtual-dtor works reasonably... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret myenvClone = myenv.Clone() myenvClone.Append( CCFLAGS=[ "$CCFLAGS_WERROR", "-Wnon-virtual-dtor", ], ) conf = Configure( myenvClone, help=False, custom_tests={ "CheckNonVirtualDtor": CheckNonVirtualDtor, }, ) if conf.CheckNonVirtualDtor(): myenv.Append(CXXFLAGS=["-Wnon-virtual-dtor"]) conf.Finish() # As of XCode 9, this flag must be present (it is not enabled # by -Wall), in order to enforce that -mXXX-version-min=YYY # will enforce that you don't use APIs from ZZZ. if env.TargetOSIs("darwin"): env.AddToCCFLAGSIfSupported("-Wunguarded-availability") env.AddToCCFLAGSIfSupported("-Wno-enum-constexpr-conversion") # TODO SERVER-54659 - ASIO depends on std::result_of which was removed in C++ 20 myenv.Append(CPPDEFINES=["ASIO_HAS_STD_INVOKE_RESULT"]) # This is needed to compile boost on the newer xcodes myenv.Append(CPPDEFINES=["BOOST_NO_CXX98_FUNCTION_BASE"]) if get_option("runtime-hardening") == "on": # Enable 'strong' stack protection preferentially, but fall back to 'all' if it is not # available. Note that we need to add these to the LINKFLAGS as well, since otherwise we # might not link libssp when we need to (see SERVER-12456). if myenv.ToolchainIs("gcc", "clang"): if myenv.AddToCCFLAGSIfSupported("-fstack-protector-strong"): myenv.Append( LINKFLAGS=[ "-fstack-protector-strong", ], ) elif myenv.AddToCCFLAGSIfSupported("-fstack-protector-all"): myenv.Append( LINKFLAGS=[ "-fstack-protector-all", ], ) if "cfex" in selected_experimental_runtime_hardenings: myenv.Append( CFLAGS=[ "-fexceptions", ], ) if "stackclash" in selected_experimental_runtime_hardenings: myenv.AddToCCFLAGSIfSupported("-fstack-clash-protection") if "controlflow" in selected_experimental_runtime_hardenings: myenv.AddToCCFLAGSIfSupported("-fcf-protection=full") if myenv.ToolchainIs("clang"): # TODO: There are several interesting things to try here, but they each have # consequences we need to investigate. # # - fsanitize=bounds: This does static bounds checking. We can # probably turn this on along with fsanitize-trap so that we # don't depend on the ASAN runtime. # # - fsanitize=safestack: This looks very interesting, and is # probably what we want. However there are a few problems: # # - It relies on having the RT library available, and it is # unclear whether we can ship binaries that depend on # that. # # - It is incompatible with a shared object build. # # - It may not work with SpiderMonkey due to needing to # inform the GC about the stacks so that mark-sweep # # - fsanitize=cfi: Again, very interesting, however it # requires LTO builds. pass if has_option("osx-version-min"): message = """ The --osx-version-min option is no longer supported. To specify a target minimum for Darwin platforms, please explicitly add the appropriate options to CCFLAGS and LINKFLAGS on the command line: macOS: scons CCFLAGS="-mmacosx-version-min=10.14" LINKFLAGS="-mmacosx-version-min=10.14" .. Note that MongoDB requires macOS 10.14 or later. """ myenv.ConfError(textwrap.dedent(message)) usingLibStdCxx = False if has_option("libc++"): # TODO SERVER-54659 - ASIO depends on std::result_of which was removed in C++ 20 myenv.Append(CPPDEFINES=["ASIO_HAS_STD_INVOKE_RESULT"]) if not myenv.ToolchainIs("clang"): myenv.FatalError("libc++ is currently only supported for clang") if myenv.AddToCXXFLAGSIfSupported("-stdlib=libc++"): myenv.Append(LINKFLAGS=["-stdlib=libc++"]) else: myenv.ConfError("libc++ requested, but compiler does not support -stdlib=libc++") else: def CheckLibStdCxx(context): test_body = """ #include #if !defined(__GLIBCXX__) #error #endif """ context.Message("Checking if we are using libstdc++... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckLibStdCxx": CheckLibStdCxx, }, ) usingLibStdCxx = conf.CheckLibStdCxx() conf.Finish() if myenv.ToolchainIs("msvc"): if get_option("cxx-std") == "20": myenv.AppendUnique(CCFLAGS=["/std:c++20"]) else: if get_option("cxx-std") == "20": if not myenv.AddToCXXFLAGSIfSupported("-std=c++20"): myenv.ConfError("Compiler does not honor -std=c++20") if not myenv.AddToCFLAGSIfSupported("-std=c11"): myenv.ConfError("C++20 mode selected for C++ files, but can't enable C11 for C files") if using_system_version_of_cxx_libraries(): print("WARNING: System versions of C++ libraries must be compiled with C++20 support") def CheckCxx20(context): test_body = """ #if __cplusplus < 202002L #error #endif #include [[maybe_unused]] constexpr auto spaceship_operator_is_a_cxx20_feature = 2 <=> 4; """ context.Message("Checking for C++20... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckCxx20": CheckCxx20, }, ) if get_option("cxx-std") == "20" and not conf.CheckCxx20(): myenv.ConfError("C++20 support is required to build MongoDB") conf.Finish() # If we are using libstdc++, check to see if we are using a # libstdc++ that is older than our GCC minimum of 5.3.0. This is # primarly to help people using clang on OS X but forgetting to # use --libc++ (or set the target OS X version high enough to get # it as the default). We would, ideally, check the __GLIBCXX__ # version, but for various reasons this is not workable. Instead, # we switch on the fact that the header # wasn't introduced until libstdc++ 5.3.0. Yes, this is a terrible # hack. if usingLibStdCxx: def CheckModernLibStdCxx(context): test_body = """ #if !__has_include() #error "libstdc++ from GCC 5.3.0 or newer is required" #endif """ context.Message("Checking for libstdc++ 5.3.0 or better... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckModernLibStdCxx": CheckModernLibStdCxx, }, ) suppress_invalid = has_option("disable-minimum-compiler-version-enforcement") if not conf.CheckModernLibStdCxx() and not suppress_invalid: myenv.ConfError( "When using libstdc++, MongoDB requires libstdc++ from GCC 5.3.0 or newer" ) conf.Finish() if has_option("use-glibcxx-debug"): # If we are using a modern libstdc++ and this is a debug build and we control all C++ # dependencies, then turn on the debugging features in libstdc++. # TODO: Need a new check here. if not debugBuild: myenv.FatalError("--use-glibcxx-debug requires --dbg=on") if not usingLibStdCxx: myenv.FatalError( "--use-glibcxx-debug is only compatible with the GNU implementation " "of the C++ standard libary" ) if using_system_version_of_cxx_libraries(): myenv.FatalError( "--use-glibcxx-debug not compatible with system versions of " "C++ libraries." ) myenv.Append(CPPDEFINES=["_GLIBCXX_DEBUG"]) # Check if we have a modern Windows SDK if env.TargetOSIs("windows"): def CheckWindowsSDKVersion(context): test_body = """ #include #if !defined(NTDDI_WINBLUE) #error Need Windows SDK Version 8.1 or higher #endif """ context.Message("Checking Windows SDK is 8.1 or newer... ") ret = context.TryCompile(textwrap.dedent(test_body), ".c") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckWindowsSDKVersion": CheckWindowsSDKVersion, }, ) if not conf.CheckWindowsSDKVersion(): myenv.ConfError("Windows SDK Version 8.1 or higher is required to build MongoDB") conf.Finish() # Check if we are on a POSIX system by testing if _POSIX_VERSION is defined. def CheckPosixSystem(context): test_body = """ // POSIX requires the existence of unistd.h, so if we can't include unistd.h, we // are definitely not a POSIX system. #include #if !defined(_POSIX_VERSION) #error not a POSIX system #endif """ context.Message("Checking if we are on a POSIX system... ") ret = context.TryCompile(textwrap.dedent(test_body), ".c") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckPosixSystem": CheckPosixSystem, }, ) posix_system = conf.CheckPosixSystem() conf.Finish() # Check if we are on a system that support the POSIX clock_gettime function # and the "monotonic" clock. if posix_system: conf = Configure( myenv, help=False, ) # On 32-bit systems, we need to define this in order to get access to # the 64-bit versions of fseek, etc. # except on 32 bit android where it breaks boost if not conf.CheckTypeSize("off_t", includes="#include ", expect=8): if not env.TargetOSIs("android"): myenv.Append(CPPDEFINES=["_FILE_OFFSET_BITS=64"]) conf.Finish() if has_option("sanitize") and get_option("sanitize") != "": if not myenv.ToolchainIs("clang", "gcc"): env.FatalError("sanitize is only supported with clang or gcc") # sanitizer libs may inject undefined refs (for hooks) at link time, but # the symbols will be available at runtime via the compiler runtime lib. env.Append(LINKFLAGS="-Wl,--allow-shlib-undefined") if myenv.ToolchainIs("gcc"): # GCC's implementation of ASAN depends on libdl. env.Append(LIBS=["dl"]) sanitizer_list = get_option("sanitize").split(",") using_asan = "address" in sanitizer_list using_fsan = "fuzzer" in sanitizer_list using_lsan = "leak" in sanitizer_list using_tsan = "thread" in sanitizer_list using_ubsan = "undefined" in sanitizer_list using_msan = "memory" in sanitizer_list if get_option("shared-libsan") == "on" and len(sanitizer_list) > 0: if not myenv.ToolchainIs("clang") or not myenv.TargetOSIs("linux"): env.FatalError("Error: --shared-libsan is only supported with clang on linux") def get_san_lib_path(sanitizer): # TODO SERVER-83727: the v4 clang toolchain doesn't support shared TSAN. Add # support here once the toolchain is upgraded. san_to_lib = { "address": "asan", "undefined": "ubsan_standalone", } sanitizer_lib = san_to_lib.get(sanitizer) if sanitizer_lib is None: env.FatalError( f"Error: --shared-libsan is not supported with {sanitizer} sanitizer" ) arch = env["TARGET_ARCH"] san_rt_name = f"libclang_rt.{sanitizer_lib}-{arch}.so" p = subprocess.run( [env["CXX"], f"-print-file-name={san_rt_name}"], capture_output=True, text=True ) clang_rt_path = p.stdout.strip() if not os.path.isfile(clang_rt_path): env.FatalError(f"Error: couldn't find sanitizer runtime library {san_rt_name}") return clang_rt_path env["SANITIZER_RUNTIME_LIBS"] = [ get_san_lib_path(sanitizer) for sanitizer in sanitizer_list ] if "thread" not in sanitizer_list: env.Append(LINKFLAGS=["-rtlib=compiler-rt", "-unwindlib=libgcc"]) if using_lsan: env.FatalError("Please use --sanitize=address instead of --sanitize=leak") if (using_asan or using_msan) and env["MONGO_ALLOCATOR"] in [ "tcmalloc-google", "tcmalloc-gperf", ]: # There are multiply defined symbols between the sanitizer and # our vendorized tcmalloc. env.FatalError("Cannot use --sanitize=address or --sanitize=memory with tcmalloc") if not myenv.ToolchainIs("clang") and using_msan: env.FatalError("Memory Sanitizer (MSan) is only supported with clang.") if using_fsan: def CheckForFuzzerCompilerSupport(context): test_body = """ #include #include // fuzz_target.cc extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { return 0; } """ context.Message("Checking if libfuzzer is supported by the compiler... ") context.env.AppendUnique( LINKFLAGS=[ "-fprofile-instr-generate", "-fcoverage-mapping", "-fsanitize=fuzzer", ], CCFLAGS=[ "-fprofile-instr-generate", "-fcoverage-mapping", ], ) ret = context.TryLink(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret confEnv = myenv.Clone() fuzzerConf = Configure( confEnv, help=False, custom_tests={ "CheckForFuzzerCompilerSupport": CheckForFuzzerCompilerSupport, }, ) if not fuzzerConf.CheckForFuzzerCompilerSupport(): myenv.FatalError("libfuzzer is not supported by the compiler") fuzzerConf.Finish() # We can't include the fuzzer flag with the other sanitize flags # The libfuzzer library already has a main function, which will cause the dependencies check # to fail sanitizer_list.remove("fuzzer") sanitizer_list.append("fuzzer-no-link") # These flags are needed to generate a coverage report myenv.Append( LINKFLAGS=[ "-fprofile-instr-generate", "-fcoverage-mapping", ], ) myenv.Append( CCFLAGS=[ "-fprofile-instr-generate", "-fcoverage-mapping", ], ) sanitizer_option = "-fsanitize=" + ",".join(sanitizer_list) if myenv.AddToCCFLAGSIfSupported(sanitizer_option): myenv.Append(LINKFLAGS=[sanitizer_option]) myenv.Append(CCFLAGS=["-fno-omit-frame-pointer"]) else: myenv.ConfError("Failed to enable sanitizers with flag: {0}", sanitizer_option) if get_option("shared-libsan") == "on": shared_libsan_option = "-shared-libsan" if myenv.AddToCCFLAGSIfSupported(shared_libsan_option): myenv.Append(LINKFLAGS=[shared_libsan_option]) myenv["SANITIZERS_ENABLED"] = sanitizer_list if has_option("sanitize-coverage") and using_fsan: sanitize_coverage_list = get_option("sanitize-coverage") sanitize_coverage_option = "-fsanitize-coverage=" + sanitize_coverage_list if myenv.AddToCCFLAGSIfSupported(sanitize_coverage_option): myenv.Append(LINKFLAGS=[sanitize_coverage_option]) else: myenv.ConfError( "Failed to enable -fsanitize-coverage with flag: {0}", sanitize_coverage_option ) denyfiles_map = { "address": myenv.File("#etc/asan.denylist"), "thread": myenv.File("#etc/tsan.denylist"), "undefined": myenv.File("#etc/ubsan.denylist"), "memory": myenv.File("#etc/msan.denylist"), } # Select those unique deny files that are associated with the # currently enabled sanitizers, but filter out those that are # zero length. denyfiles = {v for (k, v) in denyfiles_map.items() if k in sanitizer_list} denyfiles = [f for f in denyfiles if os.stat(f.path).st_size != 0] # Filter out any denylist options that the toolchain doesn't support. supportedDenyfiles = [] denyfilesTestEnv = myenv.Clone() for denyfile in denyfiles: if denyfilesTestEnv.AddToCCFLAGSIfSupported(f"-fsanitize-blacklist={denyfile}"): supportedDenyfiles.append(denyfile) denyfilesTestEnv = None supportedDenyfiles = sorted(supportedDenyfiles) # If we ended up with any denyfiles after the above filters, # then expand them into compiler flag arguments, and use a # generator to return at command line expansion time so that # we can change the signature if the file contents change. if supportedDenyfiles: # Unconditionally using the full path can affect SCons cached builds, so we only do # this in cases where we know it's going to matter. denylist_options = [ f"-fsanitize-blacklist={denyfile.path}" for denyfile in supportedDenyfiles ] if "ICECC" in env and env["ICECC"]: # Make these files available to remote icecream builds if requested. # These paths *must* be absolute to match the paths in the remote # toolchain archive. Local builds remain relative. local_denylist_options = denylist_options[:] denylist_options = [ f"-fsanitize-blacklist={denyfile.abspath}" for denyfile in supportedDenyfiles ] # Build a regex of all the regexes in the denylist # the regex in the denylist are a shell wildcard format # https://clang.llvm.org/docs/SanitizerSpecialCaseList.html#format # so a bit of massaging (* -> .*) to get a python regex. icecc_denylist_regexes = [] for denyfile in supportedDenyfiles: for line in denyfile.get_contents().decode("utf-8").split("\n"): if line.strip().startswith("src:"): regex_line = line.replace("src:", "").strip() regex_line = re.escape(regex_line) icecc_denylist_regexes += [regex_line.replace("\\*", ".*")] icecc_denylist_regex = re.compile("^(?:" + "|".join(icecc_denylist_regexes) + ")$") def is_local_compile(env, target, source, for_signature): return icecc_denylist_regex.match(str(source[0])) is not None env["ICECC_LOCAL_COMPILATION_FILTER"] = is_local_compile # If a sanitizer is in use with a denylist file, we have to ensure they get # added to the toolchain package that gets sent to the remote hosts so they # can be found by the remote compiler. env.Append(ICECC_CREATE_ENV_ADDFILES=supportedDenyfiles) if "CCACHE" in env and env["CCACHE"]: # Work around the fact that some versions of ccache either don't yet support # -fsanitize-blacklist at all or only support one instance of it. This will # work on any version of ccache because the point is only to ensure that the # resulting hash for any compiled object is guaranteed to take into account # the effect of any sanitizer denylist files used as part of the build. # TODO: This will no longer be required when the following pull requests/ # issues have been merged and deployed. # https://github.com/ccache/ccache/pull/258 # https://github.com/ccache/ccache/issues/318 env.Append(CCACHE_EXTRAFILES=supportedDenyfiles) env["CCACHE_EXTRAFILES_USE_SOURCE_PATHS"] = True def CCSanitizerDenylistGenerator(source, target, env, for_signature): # TODO: SERVER-60915 use new conftest API if "conftest" in str(target[0]): return "" # TODO: SERVER-64620 use scanner instead of for_signature if for_signature: return [f.get_csig() for f in supportedDenyfiles] # Check if the denylist gets a match and if so it will be local # build and should use the non-abspath. # NOTE: in non icecream builds denylist_options becomes relative paths. if ( env.subst("$ICECC_LOCAL_COMPILATION_FILTER", target=target, source=source) == "True" ): return local_denylist_options return denylist_options def LinkSanitizerDenylistGenerator(source, target, env, for_signature): # TODO: SERVER-60915 use new conftest API if "conftest" in str(target[0]): return "" # TODO: SERVER-64620 use scanner instead of for_signature if for_signature: return [f.get_csig() for f in supportedDenyfiles] return denylist_options myenv.AppendUnique( CC_SANITIZER_DENYLIST_GENERATOR=CCSanitizerDenylistGenerator, LINK_SANITIZER_DENYLIST_GENERATOR=LinkSanitizerDenylistGenerator, CCFLAGS="${CC_SANITIZER_DENYLIST_GENERATOR}", LINKFLAGS="${LINK_SANITIZER_DENYLIST_GENERATOR}", ) symbolizer_option = "" if env.get("LLVM_SYMBOLIZER", False): llvm_symbolizer = env["LLVM_SYMBOLIZER"] if not os.path.isabs(llvm_symbolizer): llvm_symbolizer = myenv.WhereIs(llvm_symbolizer) if not myenv.File(llvm_symbolizer).exists(): myenv.FatalError(f"Symbolizer binary at path {llvm_symbolizer} does not exist") symbolizer_option = f':external_symbolizer_path="{llvm_symbolizer}"' elif using_asan or using_tsan or using_ubsan or using_msan: myenv.FatalError( "The address, thread, memory, and undefined behavior sanitizers require llvm-symbolizer for meaningful reports. Please set LLVM_SYMBOLIZER to the path to llvm-symbolizer in your SCons invocation" ) if using_asan: # Unfortunately, abseil requires that we make these macros # (this, and THREAD_ and UNDEFINED_BEHAVIOR_ below) set, # because apparently it is too hard to query the running # compiler. We do this unconditionally because abseil is # basically pervasive via the 'base' library. myenv.AppendUnique(CPPDEFINES=["ADDRESS_SANITIZER"]) # If anything is changed, added, or removed in either asan_options or # lsan_options, be sure to make the corresponding changes to the # appropriate build variants in etc/evergreen.yml asan_options_clear = [ "detect_leaks=1", "check_initialization_order=true", "strict_init_order=true", "abort_on_error=1", "disable_coredump=0", "handle_abort=1", "strict_string_checks=true", "detect_invalid_pointer_pairs=1", ] asan_options = ":".join(asan_options_clear) lsan_options = ( f"report_objects=1:suppressions={myenv.File('#etc/lsan.suppressions').abspath}" ) env["ENV"]["ASAN_OPTIONS"] = asan_options + symbolizer_option env["ENV"]["LSAN_OPTIONS"] = lsan_options + symbolizer_option if using_msan: # Makes it easier to debug memory failures at the cost of some perf myenv.Append(CCFLAGS=["-fsanitize-memory-track-origins"]) env["ENV"]["MSAN_OPTIONS"] = symbolizer_option if using_tsan: if use_libunwind: # TODO: SERVER-48622 # # See https://github.com/google/sanitizers/issues/943 # for why we disallow combining TSAN with # libunwind. We could, atlernatively, have added logic # to automate the decision about whether to enable # libunwind based on whether TSAN is enabled, but that # logic is already complex, and it feels better to # make it explicit that using TSAN means you won't get # the benefits of libunwind. Fixing this is: env.FatalError( "Cannot use libunwind with TSAN, please add --use-libunwind=off to your compile flags" ) # We add supressions based on the library file in etc/tsan.suppressions # so the link-model needs to be dynamic. if not link_model.startswith("dynamic"): env.FatalError("TSAN is only supported with dynamic link models") # If anything is changed, added, or removed in # tsan_options, be sure to make the corresponding changes # to the appropriate build variants in etc/evergreen.yml # # TODO SERVER-49121: die_after_fork=0 is a temporary # setting to allow tests to continue while we figure out # why we're running afoul of it. # # TODO SERVER-65936: report_thread_leaks=0 suppresses # reporting thread leaks, which we have because we don't # do a clean shutdown of the ServiceContext. # tsan_options = f"abort_on_error=1:disable_coredump=0:handle_abort=1:halt_on_error=1:report_thread_leaks=0:die_after_fork=0:history_size=4:suppressions={myenv.File('#etc/tsan.suppressions').abspath}" myenv["ENV"]["TSAN_OPTIONS"] = tsan_options + symbolizer_option myenv.AppendUnique(CPPDEFINES=["THREAD_SANITIZER"]) if using_ubsan: # By default, undefined behavior sanitizer doesn't stop on # the first error. Make it so. Newer versions of clang # have renamed the flag. if not myenv.AddToCCFLAGSIfSupported("-fno-sanitize-recover"): myenv.AddToCCFLAGSIfSupported("-fno-sanitize-recover=undefined") myenv.AppendUnique(CPPDEFINES=["UNDEFINED_BEHAVIOR_SANITIZER"]) # If anything is changed, added, or removed in ubsan_options, be # sure to make the corresponding changes to the appropriate build # variants in etc/evergreen.yml ubsan_options = "print_stacktrace=1" myenv["ENV"]["UBSAN_OPTIONS"] = ubsan_options + symbolizer_option # In dynamic builds, the `vptr` sanitizer check can # require additional LIBDEPS edges. That is very # inconvenient, because such builds can't use z,defs. The # result is a very fragile link graph, where refactoring # the link graph in one place can have surprising effects # in others. Instead, we just disable the `vptr` sanitizer # for dynamic builds. We tried some other approaches in # SERVER-49798 of adding a new LIBDEPS_TYPEINFO type, but # that didn't address the fundamental issue that the # correct link graph for a dynamic+ubsan build isn't the # same as the correct link graph for a regular dynamic # build. if link_model == "dynamic": if myenv.AddToCCFLAGSIfSupported("-fno-sanitize=vptr"): myenv.AppendUnique(LINKFLAGS=["-fno-sanitize=vptr"]) if myenv.ToolchainIs("msvc") and optBuild != "off": # http://blogs.msdn.com/b/vcblog/archive/2013/09/11/introducing-gw-compiler-switch.aspx # myenv.Append(CCFLAGS=["/Gw", "/Gy"]) myenv.Append(LINKFLAGS=["/OPT:REF"]) # http://blogs.msdn.com/b/vcblog/archive/2014/03/25/linker-enhancements-in-visual-studio-2013-update-2-ctp2.aspx # myenv.Append(CCFLAGS=["/Zc:inline"]) if myenv.ToolchainIs("clang"): # We add this flag to make clang emit debug info for c++ stl types so that our pretty # printers will work with newer clang's which omit this debug info. This does increase # the overall debug info size. myenv.AddToCCFLAGSIfSupported("-fno-limit-debug-info") if myenv.ToolchainIs("gcc", "clang"): # Pass -gdwarf{32,64} if an explicit value was selected # or defaulted. Fail the build if we can't honor the # selection. if myenv["DWARF_WIDTH"]: if myenv.AddToCCFLAGSIfSupported("-gdwarf$DWARF_WIDTH"): myenv.AppendUnique(LINKFLAGS=["-gdwarf$DWARF_WIDTH"]) else: myenv.FatalError("Could not enable selected dwarf width") # try to determine the if dwarf64 is viable, and fallback to dwarf32 if not elif myenv.CheckCCFLAGSSupported("-gdwarf64"): def CheckForDWARF64Support(context): context.Message("Checking that DWARF64 format is viable... ") try: dwarf_version = int(myenv.get("DWARF_VERSION", 0)) except ValueError: dwarf_version = None if dwarf_version is None or dwarf_version <= 4: result = False else: test_body = """ #include #include int main() { std::cout << "Hello, World" << std::endl; return EXIT_SUCCESS; } """ original_ccflags = context.env.get("CCFLAGS") original_linkflags = context.env.get("LINKFLAGS") context.env.Append(CCFLAGS=["-gdwarf64"], LINKFLAGS=["-gdwarf64"]) ret = context.TryLink(textwrap.dedent(test_body), ".cpp") context.env["CCFLAGS"] = original_ccflags context.env["LINKFLAGS"] = original_linkflags if not ret: context.Result("unknown") return False regex = re.compile(r"^\s*Length:.*[64|32]-bit\)$", re.MULTILINE) p = subprocess.run( [context.env["READELF"], "-wi", context.lastTarget.path], capture_output=True, text=True, ) matches = re.findall(regex, p.stdout) address_types = set() for match in matches: address_types.add(match[-len("(XX-bit)") :]) result = len(address_types) == 1 and list(address_types)[0] == "(64-bit)" context.Result(result) return result conf = Configure( myenv, help=False, custom_tests={ "CheckForDWARF64Support": CheckForDWARF64Support, }, ) if conf.CheckForDWARF64Support(): myenv["DWARF_WIDTH"] = 64 myenv.AppendUnique(LINKFLAGS=["-gdwarf64"], CCFLAGS=["-gdwarf64"]) else: myenv["DWARF_WIDTH"] = 32 myenv.AppendUnique(LINKFLAGS=["-gdwarf32"], CCFLAGS=["-gdwarf32"]) conf.Finish() if myenv["DWARF_WIDTH"] == 32 and link_model != "dynamic": # This will create an extra section where debug types can be referred from, # reducing other section sizes. This helps most with big static links as there # will be lots of duplicate debug type info. if myenv.AddToCCFLAGSIfSupported("-fdebug-types-section"): myenv.AppendUnique(LINKFLAGS=["-fdebug-types-section"]) # Turn off debug symbols. Due to g0 disabling any previously added debugging flags, # it is easier to append g0 near the end rather than trying to not add all the other # debug flags. This should be added after any debug flags. if not debug_symbols: myenv.AppendUnique(LINKFLAGS=["-g0"], CCFLAGS=["-g0"]) # Our build is already parallel. if not myenv.AddToLINKFLAGSIfSupported("-Wl,--no-threads"): myenv.AddToLINKFLAGSIfSupported("--Wl,--threads=1") # Explicitly enable GNU build id's if the linker supports it. myenv.AddToLINKFLAGSIfSupported("-Wl,--build-id") # Explicitly use the new gnu hash section if the linker offers # it, except on android since older runtimes seem to not # support it. For that platform, use 'both'. if env.TargetOSIs("android"): myenv.AddToLINKFLAGSIfSupported("-Wl,--hash-style=both") else: myenv.AddToLINKFLAGSIfSupported("-Wl,--hash-style=gnu") # Try to have the linker tell us about ODR violations. Don't # use it when using clang with libstdc++, as libstdc++ was # probably built with GCC. That combination appears to cause # false positives for the ODR detector. See SERVER-28133 for # additional details. if has_option("detect-odr-violations"): if myenv.ToolchainIs("clang") and usingLibStdCxx: env.FatalError( "The --detect-odr-violations flag does not work with clang and libstdc++" ) if optBuild != "off": env.FatalError( "The --detect-odr-violations flag is expected to only be reliable with --opt=off" ) if linker_ld != "gold": myenv.FatalError( "The --detect-odr-violations flag currently only works with --linker=gold" ) myenv.AddToLINKFLAGSIfSupported("-Wl,--detect-odr-violations") # Disallow an executable stack. Also, issue a warning if any files are found that would # cause the stack to become executable if the noexecstack flag was not in play, so that we # can find them and fix them. We do this here after we check for ld.gold because the # --warn-execstack is currently only offered with gold. myenv.AddToLINKFLAGSIfSupported("-Wl,-z,noexecstack") myenv.AddToLINKFLAGSIfSupported("-Wl,--warn-execstack") # If possible with the current linker, mark relocations as read-only. myenv.AddToLINKFLAGSIfSupported("-Wl,-z,relro") if has_option("thin-lto"): if not myenv.AddToCCFLAGSIfSupported( "-flto=thin" ) or not myenv.AddToLINKFLAGSIfSupported("-flto=thin"): myenv.ConfError("Failed to enable thin LTO") if linker_ld != "gold" and not env.TargetOSIs("darwin", "macOS") and optBuild != "off": if has_option("pgo"): print("WARNING: skipping symbol ordering as pgo is enabled") else: myenv.AppendUnique( CCFLAGS=["-ffunction-sections"], LINKFLAGS=[ "-Wl,--symbol-ordering-file=symbols.orderfile", "-Wl,--no-warn-symbol-ordering", ], ) else: print("WARNING: lld linker is required to sort symbols") if has_option("pgo-profile"): if ( not myenv.ToolchainIs("clang") or not myenv.TargetOSIs("linux") or linker_ld == "gold" ): myenv.FatalError("Error: pgo only works on linux with clang + lld") myenv.AppendUnique( CCFLAGS=["-fprofile-instr-generate"], LINKFLAGS=["-fprofile-instr-generate"], ) if has_option("pgo"): if ( not myenv.ToolchainIs("clang") or not myenv.TargetOSIs("linux") or linker_ld == "gold" ): myenv.FatalError("Error: pgo only works on linux with clang + lld") myenv.AppendUnique( _NON_CONF_CCFLAGS_GEN=["-fprofile-use=./default.profdata"], ) myenv["CCFLAGS_WERROR"].remove("-Werror") # As far as we know these flags only apply on posix-y systems, # and not on Darwin. if env.TargetOSIs("posix") and not env.TargetOSIs("darwin"): # Disable debug compression in both the assembler and linker # by default. If the user requested compression, only allow # the zlib-gabi form. debug_compress = get_option("debug-compress") # If a value was provided on the command line for --debug-compress, it should # inhibit the application of auto, so strip it out. if "auto" in debug_compress and len(debug_compress) > 1: debug_compress = debug_compress[1:] # Disallow saying --debug-compress=off --debug-compress=ld and similar if "off" in debug_compress and len(debug_compress) > 1: env.FatalError("Cannot combine 'off' for --debug-compress with other values") # Transform the 'auto' argument into a real value. if "auto" in debug_compress: debug_compress = [] # We only automatically enable ld compression for # dynamic builds because it seems to use enormous # amounts of memory in static builds. if link_model.startswith("dynamic"): debug_compress.append("ld") compress_type = "zlib-gabi" compress_flag = "compress-debug-sections" myenv.AddToCCFLAGSIfSupported( f"-Wa,--{compress_flag}={compress_type}" if "as" in debug_compress else f"-Wa,--no{compress_flag}", ) # We shouldn't enable debug compression in the linker # (meaning our final binaries contain compressed debug # info) unless our local elf environment appears to at # least be aware of SHF_COMPRESSED. This seems like a # necessary precondition, but is it sufficient? # # https://gnu.wildebeest.org/blog/mjw/2016/01/13/elf-libelf-compressed-sections-and-elfutils/ def CheckElfHForSHF_COMPRESSED(context): test_body = """ #include #if !defined(SHF_COMPRESSED) #error #endif """ context.Message("Checking elf.h for SHF_COMPRESSED... ") ret = context.TryCompile(textwrap.dedent(test_body), ".c") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckElfHForSHF_COMPRESSED": CheckElfHForSHF_COMPRESSED, }, ) have_shf_compressed = conf.CheckElfHForSHF_COMPRESSED() conf.Finish() if have_shf_compressed and "ld" in debug_compress: myenv.AddToLINKFLAGSIfSupported( f"-Wl,--{compress_flag}={compress_type}", ) else: myenv.AddToLINKFLAGSIfSupported( f"-Wl,--{compress_flag}=none", ) if "fnsi" in selected_experimental_optimizations: myenv.AddToCCFLAGSIfSupported("-fno-semantic-interposition") # Avoid deduping symbols on OS X debug builds, as it takes a long time. if optBuild == "off" and myenv.ToolchainIs("clang") and env.TargetOSIs("darwin"): myenv.AddToLINKFLAGSIfSupported("-Wl,-no_deduplicate") # Apply any link time optimization settings as selected by the 'lto' option. if has_option("lto"): if myenv.ToolchainIs("msvc"): # Note that this is actually more aggressive than LTO, it is whole program # optimization due to /GL. However, this is historically what we have done for # windows, so we are keeping it. # # /GL implies /LTCG, so no need to say it in CCFLAGS, but we do need /LTCG on the # link flags. myenv.Append(CCFLAGS=["/GL"]) myenv.Append(LINKFLAGS=["/LTCG"]) myenv.Append(ARFLAGS=["/LTCG"]) elif myenv.ToolchainIs("gcc", "clang"): # For GCC and clang, the flag is -flto, and we need to pass it both on the compile # and link lines. if not myenv.AddToCCFLAGSIfSupported("-flto") or not myenv.AddToLINKFLAGSIfSupported( "-flto" ): myenv.ConfError( "Link time optimization requested, " "but selected compiler does not honor -flto" ) if myenv.TargetOSIs("darwin"): myenv.AddToLINKFLAGSIfSupported("-Wl,-object_path_lto,${TARGET}.lto") else: # According to intel benchmarks -fno-plt increases perf # See PM-2215 if linker_ld != "gold": myenv.ConfError("lto compilation currently only works with the --linker=gold") if link_model != "object": myenv.ConfError( "lto compilation currently only works with the --link-model=object" ) if not myenv.AddToCCFLAGSIfSupported( "-fno-plt" ) or not myenv.AddToLINKFLAGSIfSupported("-fno-plt"): myenv.ConfError("-fno-plt is not supported by the compiler") else: myenv.ConfError("Don't know how to enable --lto on current toolchain") if get_option("runtime-hardening") == "on" and optBuild != "off": # Older glibc doesn't work well with _FORTIFY_SOURCE=2. Selecting 2.11 as the minimum was an # emperical decision, as that is the oldest non-broken glibc we seem to require. It is possible # that older glibc's work, but we aren't trying. # # https://gforge.inria.fr/tracker/?func=detail&group_id=131&atid=607&aid=14070 # https://github.com/jedisct1/libsodium/issues/202 def CheckForGlibcKnownToSupportFortify(context): test_body = """ #include #if !__GLIBC_PREREQ(2, 11) #error #endif """ context.Message("Checking for glibc with non-broken _FORTIFY_SOURCE...") ret = context.TryCompile(textwrap.dedent(test_body), ".c") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "CheckForFortify": CheckForGlibcKnownToSupportFortify, }, ) # Fortify only possibly makes sense on POSIX systems, and we know that clang is not a valid # combination: # # http://lists.llvm.org/pipermail/cfe-dev/2015-November/045852.html # if env.TargetOSIs("posix") and not env.ToolchainIs("clang") and conf.CheckForFortify(): conf.env.Append( CPPDEFINES=[ ("_FORTIFY_SOURCE", 2), ], ) myenv = conf.Finish() # Our build generally assumes that we have C11-compliant libc headers for # C++ source. On most systems, that will be the case. However, on systems # using glibc older than 2.18 (or other libc implementations that have # stubbornly refused to update), we need to add some preprocessor defines. # # See: https://sourceware.org/bugzilla/show_bug.cgi?id=15366 # # These headers are only fully standards-compliant on POSIX platforms. Windows # in particular doesn't implement inttypes.h if env.TargetOSIs("posix"): def NeedStdCLimitMacros(context): test_body = """ #undef __STDC_LIMIT_MACROS #include #if defined(INT64_MAX) # error #endif """ context.Message("Checking whether to define __STDC_LIMIT_MACROS... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret def NeedStdCConstantMacros(context): test_body = """ #undef __STDC_CONSTANT_MACROS #include #if defined(INTMAX_C) # error #endif """ context.Message("Checking whether to define __STDC_CONSTANT_MACROS... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret def NeedStdCFormatMacros(context): test_body = """ #undef __STDC_FORMAT_MACROS #include #if defined(PRIx64) # error #endif """ context.Message("Checking whether to define __STDC_FORMAT_MACROS... ") ret = context.TryCompile(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret conf = Configure( myenv, help=False, custom_tests={ "NeedStdCLimitMacros": NeedStdCLimitMacros, "NeedStdCConstantMacros": NeedStdCConstantMacros, "NeedStdCFormatMacros": NeedStdCFormatMacros, }, ) conf.env.AppendUnique( CPPDEFINES=[ "__STDC_LIMIT_MACROS" if conf.NeedStdCLimitMacros() else "", "__STDC_CONSTANT_MACROS" if conf.NeedStdCConstantMacros() else "", "__STDC_FORMAT_MACROS" if conf.NeedStdCFormatMacros() else "", ] ) myenv = conf.Finish() # We set this with GCC on x86 platforms to work around # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43052 if myenv.ToolchainIs("gcc") and (env["TARGET_ARCH"] in ["i386", "x86_64"]): if "builtin-memcmp" not in selected_experimental_optimizations: myenv.AddToCCFLAGSIfSupported("-fno-builtin-memcmp") def CheckBoostMinVersion(context): compile_test_body = textwrap.dedent(""" #include #if BOOST_VERSION < 104900 #error #endif """) context.Message("Checking if system boost version is 1.49 or newer...") result = context.TryCompile(compile_test_body, ".cpp") context.Result(result) return result conf = Configure( myenv, custom_tests={ "CheckBoostMinVersion": CheckBoostMinVersion, }, ) libdeps.setup_conftests(conf) ### --ssl checks def checkOpenSSL(conf): sslLibName = "ssl" cryptoLibName = "crypto" sslLinkDependencies = ["crypto", "dl"] if conf.env.TargetOSIs("freebsd"): sslLinkDependencies = ["crypto"] if conf.env.TargetOSIs("windows"): sslLibName = "ssleay32" cryptoLibName = "libeay32" sslLinkDependencies = ["libeay32"] # Used to import system certificate keychains if conf.env.TargetOSIs("darwin"): conf.env.AppendUnique( FRAMEWORKS=[ "CoreFoundation", "Security", ] ) def maybeIssueDarwinSSLAdvice(env): if env.TargetOSIs("macOS"): advice = textwrap.dedent("""\ NOTE: Recent versions of macOS no longer ship headers for the system OpenSSL libraries. NOTE: Either build without the --ssl flag, or describe how to find OpenSSL. NOTE: Set the include path for the OpenSSL headers with the CPPPATH SCons variable. NOTE: Set the library path for OpenSSL libraries with the LIBPATH SCons variable. NOTE: If you are using HomeBrew, and have installed OpenSSL, this might look like: \tscons CPPPATH=/usr/local/opt/openssl/include LIBPATH=/usr/local/opt/openssl/lib ... NOTE: Consult the output of 'brew info openssl' for details on the correct paths.""") print(advice) brew = env.WhereIs("brew") if brew: try: # TODO: If we could programmatically extract the paths from the info output # we could give a better message here, but brew info's machine readable output # doesn't seem to include the whole 'caveats' section. message = subprocess.check_output([brew, "info", "openssl"]).decode("utf-8") advice = textwrap.dedent("""\ NOTE: HomeBrew installed to {0} appears to have OpenSSL installed. NOTE: Consult the output from '{0} info openssl' to determine CPPPATH and LIBPATH.""").format( brew, message ) print(advice) except: pass if not conf.CheckLibWithHeader( cryptoLibName, ["openssl/crypto.h"], "C", "SSLeay_version(0);", autoadd=True, ): maybeIssueDarwinSSLAdvice(conf.env) conf.env.ConfError("Couldn't find OpenSSL crypto.h header and library") def CheckLibSSL(context): res = SCons.Conftest.CheckLib( context, libs=[sslLibName], extra_libs=sslLinkDependencies, header='#include "openssl/ssl.h"', language="C", call="SSL_version(NULL);", autoadd=True, ) context.did_show_result = 1 return not res conf.AddTest("CheckLibSSL", CheckLibSSL) if not conf.CheckLibSSL(): maybeIssueDarwinSSLAdvice(conf.env) conf.env.ConfError("Couldn't find OpenSSL ssl.h header and library") def CheckLinkSSL(context): test_body = """ #include #include #include int main() { SSL_library_init(); SSL_load_error_strings(); ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); ERR_free_strings(); return EXIT_SUCCESS; } """ context.Message("Checking that linking to OpenSSL works...") ret = context.TryLink(textwrap.dedent(test_body), ".c") context.Result(ret) return ret conf.AddTest("CheckLinkSSL", CheckLinkSSL) if not conf.CheckLinkSSL(): maybeIssueDarwinSSLAdvice(conf.env) conf.env.ConfError("SSL is enabled, but is unavailable") # We require ssl by default unless the user has specified --ssl=off require_ssl = get_option("ssl") != "off" # The following platform checks setup both # ssl_provider for TLS implementation # and MONGO_CRYPTO for hashing support. # # MONGO_CRYPTO is always enabled regardless of --ssl=on/off # However, ssl_provider will be rewritten to 'none' if --ssl=off if conf.env.TargetOSIs("windows"): # SChannel on Windows ssl_provider = "windows" conf.env.Append(MONGO_CRYPTO=["windows"]) elif conf.env.TargetOSIs("darwin", "macOS"): # SecureTransport on macOS ssl_provider = "apple" conf.env.Append(MONGO_CRYPTO=["apple"]) conf.env.AppendUnique(FRAMEWORKS=["CoreFoundation", "Security"]) elif require_ssl: checkOpenSSL(conf) # Working OpenSSL available, use it. conf.env.Append(MONGO_CRYPTO=["openssl"]) ssl_provider = "openssl" else: # If we don't need an SSL build, we can get by with TomCrypt. conf.env.Append(MONGO_CRYPTO=["tom"]) if require_ssl: # Either crypto engine is native, # or it's OpenSSL and has been checked to be working. print("Using SSL Provider: {0}".format(ssl_provider)) else: ssl_provider = "none" def checkHTTPLib(required=False): # WinHTTP available on Windows if env.TargetOSIs("windows"): return True # libcurl on all other platforms if conf.CheckLibWithHeader( "curl", ["curl/curl.h"], "C", "curl_global_init(0);", autoadd=False, ): return True if required: env.ConfError("Could not find and curl lib") return False if use_system_version_of_library("pcre2"): conf.FindSysLibDep("pcre2", ["pcre2-8"]) else: conf.env.Prepend(CPPDEFINES=["PCRE2_STATIC"]) if use_system_version_of_library("snappy"): conf.FindSysLibDep("snappy", ["snappy"]) if use_system_version_of_library("zlib"): conf.FindSysLibDep("zlib", ["zdll" if conf.env.TargetOSIs("windows") else "z"]) if use_system_version_of_library("zstd"): conf.FindSysLibDep("zstd", ["libzstd" if conf.env.TargetOSIs("windows") else "zstd"]) if use_system_version_of_library("stemmer"): conf.FindSysLibDep("stemmer", ["stemmer"]) if use_system_version_of_library("yaml"): conf.FindSysLibDep("yaml", ["yaml-cpp"]) if use_system_version_of_library("fmt"): conf.FindSysLibDep("fmt", ["fmt"]) if use_system_version_of_library("tomcrypt"): conf.FindSysLibDep("tomcrypt", ["tomcrypt"]) if use_system_version_of_library("libunwind"): conf.FindSysLibDep("unwind", ["unwind"]) if not conf.FindSysLibDep("lzma", ["lzma"]): myenv.ConfError( "Cannot find system library 'lzma' required for use with system libunwind" ) if use_system_version_of_library("intel_decimal128"): conf.FindSysLibDep("intel_decimal128", ["bid"]) if use_system_version_of_library("icu"): conf.FindSysLibDep("icudata", ["icudata"]) # We can't use FindSysLibDep() for icui18n and icuuc below, since SConf.CheckLib() (which # FindSysLibDep() relies on) doesn't expose an 'extra_libs' parameter to indicate that the # library being tested has additional dependencies (icuuc depends on icudata, and icui18n # depends on both). As a workaround, we skip the configure check for these two libraries and # manually assign the library name. We hope that if the user has icudata installed on their # system, then they also have icu18n and icuuc installed. conf.env["LIBDEPS_ICUI18N_SYSLIBDEP"] = "icui18n" conf.env["LIBDEPS_ICUUC_SYSLIBDEP"] = "icuuc" if wiredtiger and use_system_version_of_library("wiredtiger"): if not conf.CheckCXXHeader("wiredtiger.h"): myenv.ConfError("Cannot find wiredtiger headers") conf.FindSysLibDep("wiredtiger", ["wiredtiger"]) conf.env.Append( CPPDEFINES=[ "ABSL_FORCE_ALIGNED_ACCESS", "BOOST_ENABLE_ASSERT_DEBUG_HANDLER", # TODO: Ideally, we could not set this define in C++20 # builds, but at least our current Xcode 12 doesn't offer # std::atomic_ref, so we cannot. "BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF", "BOOST_LOG_NO_SHORTHAND_NAMES", "BOOST_LOG_USE_NATIVE_SYSLOG", "BOOST_LOG_WITHOUT_THREAD_ATTR", "BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS", "BOOST_SYSTEM_NO_DEPRECATED", "BOOST_THREAD_USES_DATETIME", ("BOOST_THREAD_VERSION", "5"), ] ) if link_model.startswith("dynamic") and not link_model == "dynamic-sdk": conf.env.AppendUnique( CPPDEFINES=[ "BOOST_LOG_DYN_LINK", ] ) if use_system_version_of_library("protobuf"): conf.FindSysLibDep("protobuf", ["protobuf"]) conf.FindSysLibDep("protoc", ["protoc"]) if use_system_version_of_library("grpc"): conf.FindSysLibDep("grpc", ["grpc"]) conf.FindSysLibDep("grpcxx", ["grpc++"]) conf.FindSysLibDep("grpcxx_reflection", ["grpc++_reflection"]) if posix_system: conf.CheckLib("rt") conf.CheckLib("dl") conf.env["_HAVEPCAP"] = conf.CheckLib(["pcap", "wpcap"], autoadd=False) if env.TargetOSIs("solaris"): conf.CheckLib("nsl") conf.env["MONGO_BUILD_SASL_CLIENT"] = bool(has_option("use-sasl-client")) if conf.env["MONGO_BUILD_SASL_CLIENT"] and not conf.CheckLibWithHeader( "sasl2", ["stddef.h", "sasl/sasl.h"], "C", "sasl_version_info(0, 0, 0, 0, 0, 0);", autoadd=False, ): myenv.ConfError("Couldn't find SASL header/libraries") # requires ports devel/libexecinfo to be installed if env.TargetOSIs("freebsd", "openbsd"): if not conf.CheckLib("execinfo"): myenv.ConfError("Cannot find libexecinfo, please install devel/libexecinfo.") # 'tcmalloc' needs to be the last library linked. Please, add new libraries before this # point. if myenv["MONGO_ALLOCATOR"] == "tcmalloc-google": if use_system_version_of_library("tcmalloc-google"): conf.FindSysLibDep("tcmalloc-google", ["tcmalloc"]) elif myenv["MONGO_ALLOCATOR"] == "tcmalloc-gperf": if use_system_version_of_library("tcmalloc-gperf"): conf.FindSysLibDep("tcmalloc-gperf", ["tcmalloc"]) elif myenv["MONGO_ALLOCATOR"] in ["system"]: pass else: myenv.FatalError(f"Invalid --allocator parameter: {env['MONGO_ALLOCATOR']}") def CheckStdAtomic(context, base_type, extra_message): test_body = """ #include int main(int argc, char* argv[]) {{ std::atomic<{0}> x; x.store(0); // Use argc to ensure we can't optimize everything away. {0} y = argc; x.fetch_add(y); x.fetch_sub(y); x.exchange(y); if (x.compare_exchange_strong(y, x) && x.is_lock_free()) return 0; return x.load(); }} """.format(base_type) context.Message( "Checking if std::atomic<{0}> works{1}... ".format(base_type, extra_message) ) ret = context.TryLink(textwrap.dedent(test_body), ".cpp") context.Result(ret) return ret conf.AddTest("CheckStdAtomic", CheckStdAtomic) def check_all_atomics(extra_message=""): for t in ("int64_t", "uint64_t", "int32_t", "uint32_t"): if not conf.CheckStdAtomic(t, extra_message): return False return True if not check_all_atomics(): if not conf.CheckLib("atomic", symbol=None, header=None, language="C", autoadd=1): myenv.ConfError( "Some atomic ops are not intrinsically supported, but " "no libatomic found" ) if not check_all_atomics(" with libatomic"): myenv.ConfError("The toolchain does not support std::atomic, cannot continue") def CheckMongoCMinVersion(context): compile_test_body = textwrap.dedent(""" #include #if !MONGOC_CHECK_VERSION(1,13,0) #error #endif """) context.Message("Checking if mongoc version is 1.13.0 or newer...") result = context.TryCompile(compile_test_body, ".cpp") context.Result(result) return result conf.AddTest("CheckMongoCMinVersion", CheckMongoCMinVersion) conf.env["MONGO_HAVE_LIBMONGOC"] = False # ask each module to configure itself and the build environment. moduleconfig.configure_modules(mongo_modules, conf) # Resolve --enable-http-client if http_client == "auto": if checkHTTPLib(): http_client = "on" else: print("Disabling http-client as libcurl was not found") http_client = "off" elif http_client == "on": checkHTTPLib(required=True) if get_option("xray") == "on": if not (myenv.ToolchainIs("clang") and env.TargetOSIs("linux")): env.FatalError("LLVM Xray is only supported with clang on linux") myenv.AppendUnique( CCFLAGS=[ "-fxray-instrument", "-fxray-instruction-threshold=" + str(get_option("xray-instruction-threshold")), ], LINKFLAGS=["-fxray-instrument"], ) if "ldap" in myenv.get("MONGO_ENTERPRISE_FEATURES", []): if myenv.TargetOSIs("windows"): conf.env["MONGO_LDAP_LIB"] = ["Wldap32"] else: have_ldap_h = conf.CheckLibWithHeader( "ldap", ["ldap.h"], "C", 'ldap_is_ldap_url("ldap://127.0.0.1");', autoadd=False, ) have_lber_h = conf.CheckLibWithHeader( "lber", ["lber.h"], "C", "ber_free(NULL, 0);", autoadd=False, ) if have_ldap_h: conf.env.AppendUnique(MONGO_LDAP_LIB=["ldap"]) else: myenv.ConfError( "Could not find and ldap library from OpenLDAP, " "required for LDAP authorization in the enterprise build" ) if have_lber_h: conf.env.AppendUnique(MONGO_LDAP_LIB=["lber"]) else: myenv.ConfError( "Could not find and lber library from OpenLDAP, " "required for LDAP authorizaton in the enterprise build" ) if "sasl" in myenv.get("MONGO_ENTERPRISE_FEATURES", []): if conf.env.TargetOSIs("windows"): conf.env["MONGO_GSSAPI_IMPL"] = "sspi" conf.env["MONGO_GSSAPI_LIB"] = ["secur32"] else: if conf.CheckLib(library="gssapi_krb5", autoadd=False): conf.env["MONGO_GSSAPI_IMPL"] = "gssapi" if conf.env.TargetOSIs("freebsd"): conf.env.AppendUnique(MONGO_GSSAPI_LIB=["gssapi"]) conf.env.AppendUnique(MONGO_GSSAPI_LIB=["gssapi_krb5"]) myenv = conf.Finish() return myenv env = doConfigure(env) saslconf = Configure(env) have_sasl_lib = saslconf.CheckLibWithHeader( "sasl2", ["stddef.h", "sasl/sasl.h"], "C", "sasl_version_info(0, 0, 0, 0, 0, 0);", autoadd=False, ) saslconf.Finish() env["NINJA_SYNTAX"] = "#site_scons/third_party/ninja_syntax.py" if env.ToolchainIs("clang"): env["ICECC_COMPILER_TYPE"] = "clang" elif env.ToolchainIs("gcc"): env["ICECC_COMPILER_TYPE"] = "gcc" # Now that we are done with configure checks, enable ccache and # icecream if requested. if "CCACHE" in env and env["CCACHE"]: ccache = Tool("ccache") if not ccache.exists(env): env.FatalError(f"Failed to load ccache tool with CCACHE={env['CCACHE']}") ccache(env) if "ICECC" in env and env["ICECC"]: env["ICECREAM_VERBOSE"] = env.Verbose() env["ICECREAM_TARGET_DIR"] = "$BUILD_ROOT/scons/icecream" # Posssibly multiple ninja files are in play, and there are cases where ninja will # use the wrong icecc run script, so we must create a unique script per ninja variant # for ninja to track separately. We will use the variant dir which contains the each # separate ninja builds meta files. This has to be under an additional flag then just # ninja disabled, because the run icecc script is generated under a context where ninja # is always disabled via the scons callback mechanism. The __NINJA_NO flag is intended # to differentiate this particular context. if env.get("__NINJA_NO") or get_option("ninja") != "disabled": env["ICECREAM_RUN_SCRIPT_SUBPATH"] = "$VARIANT_DIR" icecream = Tool("icecream") if not icecream.exists(env): # SERVER-70648: Need to revert on how to update icecream if "ICECREAM_VERSION" in env and env["ICECREAM_VERSION"] < parse_version("1.3"): env.FatalError( textwrap.dedent("""\ Please refer to the following commands to update your icecream: sudo add-apt-repository ppa:mongodb-dev-prod/mongodb-build sudo apt update sudo apt-get --only-upgrade install icecc """) ) env.FatalError(f"Failed to load icecream tool with ICECC={env['ICECC']}") icecream(env) # Defaults for SCons provided flags. SetOption only sets the option to our value # if the user did not provide it. So for any flag here if it's explicitly passed # the values below set with SetOption will be overwritten. # # Default j to the number of CPUs on the system. Note: in containers this # reports the number of CPUs for the host system. Perhaps in a future version of # psutil it will instead report the correct number when in a container. # # The presence of the variable ICECC means the icecream tool is # enabled and so the default j value should scale accordingly. In this # scenario multiply the cpu count by 8 to set a reasonable default since the # cluster can handle many more jobs than your local machine but is # still throttled by your cpu count in the sense that you can only # handle so many python threads sending out jobs. # # Capitalize on the weird way SCons handles arguments to determine if # the user configured it or not. If not, it is under our control. Try # to set some helpful defaults. initial_num_jobs = env.GetOption("num_jobs") altered_num_jobs = initial_num_jobs + 1 env.SetOption("num_jobs", altered_num_jobs) cpu_count = psutil.cpu_count() if env.GetOption("num_jobs") == altered_num_jobs: # psutil.cpu_count returns None when it can't determine the # number. This always fails on BSD's for example. If the user # didn't specify, and we can't determine for a parallel build, it # is better to make the user restart and be explicit, rather than # give them a very slow build. if cpu_count is None: if get_option("ninja") != "disabled": env.FatalError( "Cannot auto-determine the appropriate size for the Ninja local_job pool. Please regenerate with an explicit -j argument to SCons" ) else: env.FatalError( "Cannot auto-determine the appropriate build parallelism on this platform. Please build with an explicit -j argument to SCons" ) if "ICECC" in env and env["ICECC"] and get_option("ninja") == "disabled": # If SCons is driving and we are using icecream, scale up the # number of jobs. The icerun integration will prevent us from # overloading the local system. env.SetOption("num_jobs", 8 * cpu_count) else: # Otherwise, either icecream isn't in play, so just use local # concurrency for SCons builds, or we are generating for # Ninja, in which case num_jobs controls the size of the local # pool. Scale that up to the number of local CPUs. env.SetOption("num_jobs", cpu_count) else: if ( not has_option("force-jobs") and ("ICECC" not in env or not env["ICECC"]) and env.GetOption("num_jobs") > cpu_count ): env.FatalError( "ERROR: Icecream not enabled while using -j higher than available cpu's. " + "Use --force-jobs to override." ) if ( get_option("ninja") != "disabled" and ("ICECC" not in env or not env["ICECC"]) and not has_option("force-jobs") ): print( f"WARNING: Icecream not enabled - Ninja concurrency will be capped at {cpu_count} jobs " + "without regard to the -j value passed to it. " + "Generate your ninja file with --force-jobs to disable this behavior." ) env["NINJA_MAX_JOBS"] = cpu_count if get_option("ninja") != "disabled": if env.ToolchainIs("gcc", "clang"): env.AppendUnique(CCFLAGS=["-fdiagnostics-color"]) ninja_builder = Tool("ninja") env["NINJA_BUILDDIR"] = env.Dir("$NINJA_BUILDDIR") ninja_builder.generate(env) ninjaConf = Configure( env, help=False, custom_tests={ "CheckNinjaCompdbExpand": env.CheckNinjaCompdbExpand, }, ) env["NINJA_COMPDB_EXPAND"] = ninjaConf.CheckNinjaCompdbExpand() ninjaConf.Finish() # TODO: API for getting the sconscripts programmatically # exists upstream: https://github.com/SCons/scons/issues/3625 def ninja_generate_deps(env, target, source, for_signature): # TODO SERVER-72851 add api for vars files to exec other vars files # this would allow us to get rid of this regex here def find_nested_variable_files(variables_file): variable_files = [variables_file] with open(variables_file, "r") as file: data = file.read() pattern = "exec\\(open\\(['\"](.*)['\"]\, ['\"][a-z]+['\"]\\).read\\(\\)\\)" nested_files = re.findall(pattern, data) for file_name in nested_files: variable_files.extend(find_nested_variable_files(file_name)) return variable_files # vars files can be from outside of the repo dir and can exec other vars files # so we cannot just glob them variables_files = [] for variable_file in variables_files_args: variables_files.extend(find_nested_variable_files(variable_file)) dependencies = env.Flatten( [ "SConstruct", "WORKSPACE.bazel", "BUILD.bazel", ".bazelrc", ".bazelignore", glob(os.path.join("src", "**", "SConscript"), recursive=True), glob(os.path.join("src", "**", "BUILD.bazel"), recursive=True), glob(os.path.join("buildscripts", "**", "*.py"), recursive=True), glob(os.path.join("bazel", "**", "*.bzl"), recursive=True), glob(os.path.join(os.path.expanduser("~/.scons/"), "**", "*.py"), recursive=True), glob(os.path.join("site_scons", "**", "*.py"), recursive=True), glob(os.path.join("buildscripts", "**", "*.py"), recursive=True), glob(os.path.join("src/third_party/scons-*", "**", "*.py"), recursive=True), glob(os.path.join("src/mongo/db/modules", "**", "*.py"), recursive=True), variables_files, ] ) return dependencies env["NINJA_REGENERATE_DEPS"] = ninja_generate_deps if env.TargetOSIs("windows"): # The /b option here will make sure that windows updates the mtime # when copying the file. This allows to not need to use restat for windows # copy commands. copy_install_cmd = "cmd.exe /c copy /b $in $out 1>NUL" else: copy_install_cmd = "install $in $out" if env.GetOption("install-action") == "hardlink": if env.TargetOSIs("windows"): install_cmd = f"cmd.exe /c mklink /h $out $in 1>nul || {copy_install_cmd}" else: install_cmd = f"ln $in $out || {copy_install_cmd}" elif env.GetOption("install-action") == "symlink": # macOS's ln and Windows mklink command do not support relpaths # out of the box so we will precompute during generation in a # custom handler. def symlink_install_action_function(_env, node): # should only be one output and input for this case output_file = _env.NinjaGetOutputs(node)[0] input_file = _env.NinjaGetDependencies(node)[0] try: relpath = os.path.relpath(input_file, os.path.dirname(output_file)) except ValueError: relpath = os.path.abspath(input_file) return { "outputs": [output_file], "rule": "INSTALL", "inputs": [input_file], "implicit": _env.NinjaGetDependencies(node), "variables": {"precious": node.precious, "relpath": relpath}, } env.NinjaRegisterFunctionHandler("installFunc", symlink_install_action_function) if env.TargetOSIs("windows"): install_cmd = "cmd.exe /c mklink $out $relpath 1>nul" else: install_cmd = "ln -s $relpath $out" else: install_cmd = copy_install_cmd env.NinjaRule("INSTALL", install_cmd, description="Installed $out", pool="install_pool") if env.TargetOSIs("windows"): # This is a workaround on windows for SERVER-48691 where the line length # in response files is too long: # https://developercommunity.visualstudio.com/content/problem/441978/fatal-error-lnk1170-line-in-command-file-contains.html # # Ninja currently does not support # storing a newline in the ninja file, and therefore you can not # easily generate it to the response files. The only documented # way to get newlines into the response file is to use the $in_newline # variable in the rule. # # This workaround will move most of the object or lib links into the # inputs and then make the respone file consist of the inputs plus # whatever options are left in the original response content # more info can be found here: # https://github.com/ninja-build/ninja/pull/1223/files/e71bcceefb942f8355aab83ab447d702354ba272#r179526824 # https://github.com/ninja-build/ninja/issues/1000 # we are making a new special rule which will leverage # the $in_newline to get newlines into our response file env.NinjaRule( "WINLINK", "$env$WINLINK @$out.rsp", description="Linked $out", deps=None, pool="link_pool", use_depfile=False, use_response_file=True, response_file_content="$rspc $in_newline", ) # Setup the response file content generation to use our workaround rule # for LINK commands. provider = env.NinjaGenResponseFileProvider( "WINLINK", "$LINK", ) env.NinjaRuleMapping("${LINKCOM}", provider) env.NinjaRuleMapping(env["LINKCOM"], provider) # The workaround function will move some of the content from the rspc # variable into the nodes inputs. We only want to move build nodes because # inputs must be files, so we make sure the the option in the rspc # file starts with the build directory. def winlink_workaround(env, node, ninja_build): if ninja_build and "rspc" in ninja_build["variables"]: rsp_content = [] inputs = [] for opt in ninja_build["variables"]["rspc"].split(): # if its a candidate to go in the inputs add it, else keep it in the non-newline # rsp_content list if opt.startswith(str(env.Dir("$BUILD_DIR"))) and opt != str(node): inputs.append(opt) else: rsp_content.append(opt) ninja_build["variables"]["rspc"] = " ".join(rsp_content) ninja_build["inputs"] += [ infile for infile in inputs if infile not in ninja_build["inputs"] ] # We apply the workaround to all Program nodes as they have potential # response files that have lines that are too long. # This will setup a callback function for a node # so that when its been processed, we can make some final adjustments before # its generated to the ninja file. def winlink_workaround_emitter(target, source, env): env.NinjaSetBuildNodeCallback(target[0], winlink_workaround) return target, source builder = env["BUILDERS"]["Program"] base_emitter = builder.emitter new_emitter = SCons.Builder.ListEmitter([base_emitter, winlink_workaround_emitter]) builder.emitter = new_emitter # We can create empty files for FAKELIB in Ninja because it # does not care about content signatures. We have to # write_uuid_to_file for FAKELIB in SCons because SCons does. env.NinjaRule( rule="FAKELIB", command="cmd /c copy 1>NUL NUL $out" if env["PLATFORM"] == "win32" else "touch $out", ) def fakelib_in_ninja(env, node): """Generates empty .a files""" return { "outputs": [node.get_path()], "rule": "FAKELIB", "implicit": [str(s) for s in node.sources], } env.NinjaRegisterFunctionHandler("write_uuid_to_file", fakelib_in_ninja) def ninja_test_list_builder(env, node): test_files = [test_file.path for test_file in env["MONGO_TEST_REGISTRY"][node.path]] files = " ".join(test_files) return { "outputs": [node.get_path()], "rule": "TEST_LIST", "implicit": test_files, "variables": { "files": files, }, } if env["PLATFORM"] == "win32": cmd = 'cmd.exe /c del "$out" && for %a in ($files) do (echo %a >> "$out")' else: cmd = 'rm -f "$out"; for i in $files; do echo "$$i" >> "$out"; done;' env.NinjaRule( rule="TEST_LIST", description="Compiled test list: $out", command=cmd, ) env.NinjaRegisterFunctionHandler("test_list_builder_action", ninja_test_list_builder) env["NINJA_GENERATED_SOURCE_ALIAS_NAME"] = "generated-sources" gdb_index_enabled = env.get("GDB_INDEX") if gdb_index_enabled == "auto" and link_model == "dynamic": gdb_index_enabled = True if gdb_index_enabled is True: gdb_index = Tool("gdb_index") if gdb_index.exists(env): gdb_index.generate(env) elif env.get("GDB_INDEX") != "auto": env.FatalError("Could not enable explicit request for gdb index generation.") if env.get("ENABLE_GRPC_BUILD"): env.Tool("protobuf_compiler") if (get_option("separate-debug") == "on" or env.TargetOSIs("windows")) and debug_symbols: separate_debug = Tool("separate_debug") if not separate_debug.exists(env): env.FatalError( "Cannot honor --separate-debug because the separate_debug.py Tool reported as nonexistent" ) separate_debug(env) if env["SPLIT_DWARF"] == "auto": # For static builds, splitting out the dwarf info reduces memory requirments, link time # and binary size significantly. It's affect is less prominent in dynamic builds. The downside # is .dwo files use absolute paths in the debug info, so it's not relocatable. # We also found the running splitdwarf with dwarf5 failed to compile # so unless we set DWARF_VERSION = 4 we are going to turn off split dwarf env["SPLIT_DWARF"] = ( not link_model == "dynamic" and env.ToolchainIs("gcc", "clang") and not env.TargetOSIs("darwin") and env.CheckCCFLAGSSupported("-gsplit-dwarf") and env.get("DWARF_VERSION") == 4 and not gdb_index_enabled ) if env["SPLIT_DWARF"]: if env.TargetOSIs("darwin"): env.FatalError("Setting SPLIT_DWARF=1 on darwin is not supported.") if env.get("DWARF_VERSION") != 4: env.FatalError( "Running split dwarf outside of DWARF4 has shown compilation issues when using DWARF5 and gdb index. Disabling this functionality for now. Use SPLIT_DWARF=0 to disable building with split dwarf or use DWARF_VERSION=4 to pin to DWARF version 4." ) if gdb_index_enabled: env.FatalError( "SPLIT_DWARF is not supported when using GDB_INDEX. Use GDB_INDEX=0 to allow enabling SPLIT_DWARF" ) env.Tool("split_dwarf") env["AUTO_ARCHIVE_TARBALL_SUFFIX"] = "tgz" env["AIB_META_COMPONENT"] = "all" env["AIB_BASE_COMPONENT"] = "common" env["AIB_DEFAULT_COMPONENT"] = "mongodb" env.Tool("auto_install_binaries") env.Tool("auto_archive") env.DeclareRoles( roles=[ env.Role( name="base", ), env.Role( name="debug", ), env.Role( name="dev", dependencies=[ "runtime", ], ), env.Role( name="meta", ), env.Role( name="runtime", dependencies=[ # On windows, we want the runtime role to depend # on the debug role so that PDBs end in the # runtime package. "debug" if env.TargetOSIs("windows") else None, ], silent=True, ), ], base_role="base", meta_role="meta", ) def _aib_debugdir(source, target, env, for_signature): for s in source: origin = getattr(s.attributes, "debug_file_for", None) oentry = env.Entry(origin) osuf = oentry.get_suffix() map_entry = env["AIB_SUFFIX_MAP"].get(osuf) if map_entry: return map_entry[0] env.FatalError( "Unable to find debuginfo file in _aib_debugdir: (source='{}')".format(str(source)) ) if debug_symbols: env["PREFIX_DEBUGDIR"] = _aib_debugdir env.AddSuffixMapping( { "$PROGSUFFIX": env.SuffixMap( directory="$PREFIX_BINDIR", default_role="runtime", ), "$SHLIBSUFFIX": env.SuffixMap( directory="$PREFIX_BINDIR" if mongo_platform.get_running_os_name() == "windows" else "$PREFIX_LIBDIR", default_role="runtime", ), ".debug": env.SuffixMap( directory="$PREFIX_DEBUGDIR", default_role="debug", ), ".dSYM": env.SuffixMap( directory="$PREFIX_DEBUGDIR", default_role="debug", ), ".pdb": env.SuffixMap( directory="$PREFIX_DEBUGDIR", default_role="debug", ), } ) env.AddPackageNameAlias( component="dist", role="runtime", name="mongodb-dist", ) env.AddPackageNameAlias( component="dist", role="debug", name="mongodb-dist-debugsymbols", ) env.AddPackageNameAlias( component="dist-test", role="runtime", name="mongodb-binaries", ) env.AddPackageNameAlias( component="dist-test", role="debug", name="mongo-debugsymbols", ) env.AddPackageNameAlias( component="dbtest", role="runtime", name="dbtest-binary", ) env.AddPackageNameAlias( component="dbtest", role="debug", name="dbtest-debugsymbols", ) env.AddPackageNameAlias( component="jstestshell", role="runtime", name="mongodb-jstestshell", ) env.AddPackageNameAlias( component="jstestshell", role="debug", name="mongodb-jstestshell-debugsymbols", ) env.AddPackageNameAlias( component="mongocryptd", role="runtime", name="mongodb-cryptd", ) env.AddPackageNameAlias( component="mongocryptd", role="debug", name="mongodb-cryptd-debugsymbols", ) env.AddPackageNameAlias( component="benchmarks", role="runtime", name="benchmarks", ) env.AddPackageNameAlias( component="benchmarks", role="debug", name="benchmarks-debugsymbols", ) env.AddPackageNameAlias( component="mh", role="runtime", # TODO: we should be able to move this to where the mqlrun binary is # defined when AIB correctly uses environments instead of hooking into # the first environment used. name="mh-binaries", ) env.AddPackageNameAlias( component="mh", role="debug", # TODO: we should be able to move this to where the mqlrun binary is # defined when AIB correctly uses environments instead of hooking into # the first environment used. name="mh-debugsymbols", ) env.AutoInstall( target="$PREFIX", source="$PRETTY_PRINTER_TEST_LIST", AIB_ROLE="runtime", AIB_COMPONENT="pretty-printer-tests", AIB_COMPONENTS_EXTRA=["dist-test"], ) env.AutoInstall( ".", "$BENCHMARK_LIST", AIB_COMPONENT="benchmarks", AIB_ROLE="runtime", ) if "SANITIZER_RUNTIME_LIBS" in env: env.AutoInstall( target="$PREFIX_LIBDIR", source=[env.File(path) for path in env["SANITIZER_RUNTIME_LIBS"]], AIB_COMPONENT="dist", AIB_ROLE="runtime", AIB_COMPONENTS_EXTRA=["dist-test"], ) env["RPATH_ESCAPED_DOLLAR_ORIGIN"] = "\\$$$$ORIGIN" def isSupportedStreamsPlatform(thisEnv): # TODO https://jira.mongodb.org/browse/SERVER-74961: Support other platforms. # linux x86 and ARM64 are supported. return ( thisEnv.TargetOSIs("linux") and thisEnv["TARGET_ARCH"] in ("x86_64", "aarch64") and ssl_provider == "openssl" ) def shouldBuildStreams(thisEnv): if releaseBuild: # The streaming enterprise module and dependencies are only included in release builds. # when streams-release-build is set. return get_option("streams-release-build") and isSupportedStreamsPlatform(thisEnv) else: return isSupportedStreamsPlatform(thisEnv) env.AddMethod(shouldBuildStreams, "ShouldBuildStreams") def prefix_libdir_rpath_generator(env, source, target, for_signature): # If the PREFIX_LIBDIR has an absolute path, we will use that directly as # RPATH because that indicates the final install destination of the libraries. prefix_libdir = env.subst("$PREFIX_LIBDIR") if os.path.isabs(prefix_libdir): return ["$PREFIX_LIBDIR"] # If the PREFIX_LIBDIR is not an absolute path, we will use a relative path # from the bin to the lib dir. lib_rel = os.path.relpath(prefix_libdir, env.subst("$PREFIX_BINDIR")) if env["PLATFORM"] == "posix": return f"$RPATH_ESCAPED_DOLLAR_ORIGIN/{lib_rel}" if env["PLATFORM"] == "darwin": return f"@loader_path/{lib_rel}" if get_option("link-model").startswith("dynamic"): env["PREFIX_LIBDIR_RPATH_GENERATOR"] = prefix_libdir_rpath_generator if env["PLATFORM"] == "posix": env.AppendUnique( RPATH=["$PREFIX_LIBDIR_RPATH_GENERATOR"], LINKFLAGS=[ # Most systems *require* -z,origin to make origin work, but android # blows up at runtime if it finds DF_ORIGIN_1 in DT_FLAGS_1. # https://android.googlesource.com/platform/bionic/+/cbc80ba9d839675a0c4891e2ab33f39ba51b04b2/linker/linker.h#68 # https://android.googlesource.com/platform/bionic/+/cbc80ba9d839675a0c4891e2ab33f39ba51b04b2/libc/include/elf.h#215 "-Wl,-z,origin" if not env.TargetOSIs("android") else [], "-Wl,--enable-new-dtags", ], SHLINKFLAGS=[ # -h works for both the sun linker and the gnu linker. "-Wl,-h,${TARGET.file}", ], ) elif env["PLATFORM"] == "darwin": # The darwin case uses an adhoc implementation of RPATH for SCons # since SCons does not support RPATH directly for macOS: # https://github.com/SCons/scons/issues/2127 # so we setup RPATH and LINKFLAGS ourselves. env["RPATHPREFIX"] = "-Wl,-rpath," env["RPATHSUFFIX"] = "" env.AppendUnique( LINKFLAGS="${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}", SHLINKFLAGS=[ "-Wl,-install_name,@rpath/${TARGET.file}", ], RPATH=["$PREFIX_LIBDIR_RPATH_GENERATOR"], ) env.Default(env.Alias("install-default")) # Load the compilation_db tool. We want to do this after configure so we don't end up with # compilation database entries for the configure tests, which is weird. # We also set a few tools we know will not work with compilationdb, these # wrapper tools get appended on the front of the command and in most # cases don't want that in the compilation database. env["_COMPILATIONDB_IGNORE_WRAPPERS"] = env.get("COMPILATIONDB_IGNORE_WRAPPERS", "").split(",") if get_option("ninja") == "disabled": env.Tool("compilation_db") incremental_link = Tool("incremental_link") if incremental_link.exists(env): incremental_link(env) # Resource Files are Windows specific def env_windows_resource_file(env, path): if env.TargetOSIs("windows"): return [env.RES(path)] else: return [] env.AddMethod(env_windows_resource_file, "WindowsResourceFile") # --- lint ---- if get_option("lint-scope") == "changed": patch_file = env.Command( target="$BUILD_DIR/current.git.patch", source=[env.WhereIs("git")], action="${SOURCES[0]} diff $GITDIFFFLAGS > $TARGET", ) env.AlwaysBuild(patch_file) clang_format = env.Command( target="#lint-clang-format", source=[ "buildscripts/clang_format.py", patch_file, ], action="REVISION=$REVISION $PYTHON ${SOURCES[0]} lint-git-diff", ) eslint = env.Command( target="#lint-eslint", source=[ "buildscripts/eslint.py", patch_file, ], action="REVISION=$REVISION $PYTHON ${SOURCES[0]} lint-git-diff", ) else: clang_format = env.Command( target="#lint-clang-format", source=[ "buildscripts/clang_format.py", ], action="$PYTHON ${SOURCES[0]} lint-all", ) eslint = env.Command( target="#lint-eslint", source=[ "buildscripts/eslint.py", ], action="$PYTHON ${SOURCES[0]} --dirmode lint jstests/ src/mongo", ) pylinters = env.Command( target="#lint-pylinters", source=[ "buildscripts/pylinters.py", ], action="$PYTHON ${SOURCES[0]} lint", ) quickmongolint = env.Command( target="#lint-lint.py", source=["buildscripts/quickmongolint.py"], action="$PYTHON ${SOURCES[0]} lint", ) lint_errorcodes = env.Command( target="#lint-errorcodes", source=["buildscripts/errorcodes.py"], action="$PYTHON ${SOURCES[0]} --quiet", ) env.Alias("lint", [quickmongolint, eslint, clang_format, pylinters, lint_errorcodes]) env.Alias("lint-fast", [eslint, clang_format, pylinters, lint_errorcodes]) env.AlwaysBuild("lint") env.AlwaysBuild("lint-fast") # ---- INSTALL ------- def getSystemInstallName(): arch_name = env.subst("$MONGO_DISTARCH") # We need to make sure the directory names inside dist tarballs are permanently # consistent, even if the target OS name used in scons is different. Any differences # between the names used by env.TargetOSIs/env.GetTargetOSName should be added # to the translation dictionary below. os_name_translations = { "windows": "win32", "macOS": "macos", } os_name = env.GetTargetOSName() os_name = os_name_translations.get(os_name, os_name) n = os_name + "-" + arch_name if len(mongo_modules): n += "-" + "-".join(m.name for m in mongo_modules) dn = env.subst("$MONGO_DISTMOD") if len(dn) > 0: n = n + "-" + dn return n # This function will add the version.txt file to the source tarball # so that versioning will work without having the git repo available. def add_version_to_distsrc(env, archive): version_file_path = env.subst("$MONGO_DIST_SRC_PREFIX") + "version.json" if version_file_path not in archive: version_data = { "version": env["MONGO_VERSION"], "githash": env["MONGO_GIT_HASH"], } archive.append_file_contents( version_file_path, json.dumps( version_data, sort_keys=True, indent=4, separators=(",", ": "), ), ) env.AddDistSrcCallback(add_version_to_distsrc) env["SERVER_DIST_BASENAME"] = env.subst("mongodb-%s-$MONGO_DISTNAME" % (getSystemInstallName())) env["MH_DIST_BASENAME"] = "mh" if get_option("legacy-tarball") == "true": if ( "tar-dist" not in COMMAND_LINE_TARGETS and "zip-dist" not in COMMAND_LINE_TARGETS and "archive-dist" not in COMMAND_LINE_TARGETS ): env.FatalError("option --legacy-tarball only valid with an archive-dist target") env["PREFIX"] = "$SERVER_DIST_BASENAME" module_sconscripts = moduleconfig.get_module_sconscripts(mongo_modules) # This generates a numeric representation of the version string so that # you can easily compare versions of MongoDB without having to parse # the version string. # # Examples: # 5.1.1-123 => ['5', '1', '1', '123', None, None] => [5, 1, 2, -100] # 5.1.1-rc2 => ['5', '1', '1', 'rc2', 'rc', '2'] => [5, 1, 1, -23] # 5.1.1-rc2-123 => ['5', '1', '1', 'rc2-123', 'rc', '2'] => [5, 1, 1, -23] # 5.1.0-alpha-123 => ['5', '1', '0', 'alpha-123', 'alpha', ''] => [5, 1, 0, -50] # 5.1.0-alpha1-123 => ['5', '1', '0', 'alpha1-123', 'alpha', '1'] => [5, 1, 0, -49] # 5.1.1 => ['5', '1', '1', '', None, None] => [5, 1, 1, 0] version_parts = [ x for x in re.match( r"^(\d+)\.(\d+)\.(\d+)-?((?:(rc|alpha)(\d?))?.*)?", env["MONGO_VERSION"] ).groups() ] version_extra = version_parts[3] if version_parts[3] else "" if version_parts[4] == "rc": version_parts[3] = int(version_parts[5]) + -25 elif version_parts[4] == "alpha": if version_parts[5] == "": version_parts[3] = -50 else: version_parts[3] = int(version_parts[5]) + -50 elif version_parts[3]: version_parts[2] = int(version_parts[2]) + 1 version_parts[3] = -100 else: version_parts[3] = 0 version_parts = [int(x) for x in version_parts[:4]] # The following symbols are exported for use in subordinate SConscript files. # Ideally, the SConscript files would be purely declarative. They would only # import build environment objects, and would contain few or no conditional # statements or branches. # # Currently, however, the SConscript files do need some predicates for # conditional decision making that hasn't been moved up to this SConstruct file, # and they are exported here, as well. Export( [ "debugBuild", "endian", "get_option", "have_sasl_lib", "has_option", "http_client", "jsEngine", "module_sconscripts", "optBuild", "releaseBuild", "selected_experimental_optimizations", "serverJs", "ssl_provider", "use_libunwind", "use_system_libunwind", "use_system_version_of_library", "use_vendored_libunwind", "version_extra", "version_parts", "wiredtiger", ] ) def injectModule(env, module, **kwargs): injector = env["MODULE_INJECTORS"].get(module) if injector: return injector(env, **kwargs) return env env.AddMethod(injectModule, "InjectModule") if get_option("ninja") == "disabled": compileCommands = env.CompilationDatabase("compile_commands.json") # Initialize generated-sources Alias as a placeholder so that it can be used as a # dependency for compileCommands. This Alias will be properly updated in other SConscripts. env.Depends(compileCommands, env.Alias("generated-sources")) compileDb = env.Alias("compiledb", compileCommands) msvc_version = "" if "MSVC_VERSION" in env and env["MSVC_VERSION"]: msvc_version = "--version " + env["MSVC_VERSION"] + " " # Microsoft Visual Studio Project generation for code browsing if get_option("ninja") == "disabled": vcxprojFile = env.Command( "mongodb.vcxproj", compileCommands, r"$PYTHON buildscripts\make_vcxproj.py " + msvc_version + "mongodb", ) vcxproj = env.Alias("vcxproj", vcxprojFile) # TODO: maybe make these work like the other archive- aliases # even though they aren't piped through AIB? distSrc = env.DistSrc("distsrc.tar", NINJA_SKIP=True) env.NoCache(distSrc) env.Alias("distsrc-tar", distSrc) distSrcGzip = env.GZip( target="distsrc.tgz", source=[distSrc], NINJA_SKIP=True, ) env.NoCache(distSrcGzip) env.Alias("distsrc-tgz", distSrcGzip) distSrcZip = env.DistSrc("distsrc.zip", NINJA_SKIP=True) env.NoCache(distSrcZip) env.Alias("distsrc-zip", distSrcZip) env.Alias("distsrc", "distsrc-tgz") # Do this as close to last as possible before reading SConscripts, so # that any tools that may have injected other things via emitters are included # among the side effect adornments. env.Tool("task_limiter") if has_option("jlink"): link_jobs = env.SetupTaskLimiter( name="jlink", concurrency_ratio=get_option("jlink"), builders=["Program", "SharedLibrary", "LoadableModule"], ) if get_option("ninja") != "disabled": env["NINJA_LINK_JOBS"] = link_jobs if env.get("UNITTESTS_COMPILE_CONCURRENCY"): if hasattr(SCons.Tool, "cxx"): c_suffixes = SCons.Tool.cxx.CXXSuffixes else: c_suffixes = SCons.Tool.msvc.CXXSuffixes env.SetupTaskLimiter( name="unit_cc", concurrency_ratio=float(env.get("UNITTESTS_COMPILE_CONCURRENCY")), builders={"Object": c_suffixes, "SharedObject": c_suffixes}, source_file_regex=r"^.*_test\.cpp$", ) first_half_flag = False def half_source_emitter(target, source, env): global first_half_flag if first_half_flag: first_half_flag = False if "conftest" not in str(target[0]) and not str(source[0]).endswith("_test.cpp"): env.Alias("compile_first_half_non_test_source", target) else: first_half_flag = True return target, source # Cribbed from Tool/cc.py and Tool/c++.py. It would be better if # we could obtain this from SCons. _CSuffixes = [".c"] if not SCons.Util.case_sensitive_suffixes(".c", ".C"): _CSuffixes.append(".C") _CXXSuffixes = [".cpp", ".cc", ".cxx", ".c++", ".C++"] if SCons.Util.case_sensitive_suffixes(".c", ".C"): _CXXSuffixes.append(".C") for object_builder in SCons.Tool.createObjBuilders(env): emitterdict = object_builder.builder.emitter for suffix in emitterdict.keys(): if suffix not in _CSuffixes + _CXXSuffixes: continue base = emitterdict[suffix] emitterdict[suffix] = SCons.Builder.ListEmitter( [ base, half_source_emitter, ] ) # Keep this late in the game so that we can investigate attributes set by all the tools that have run. if has_option("cache"): if get_option("cache") == "nolinked": def noCacheEmitter(target, source, env): for t in target: try: if getattr(t.attributes, "thin_archive", False): continue except AttributeError: pass env.NoCache(t) return target, source def addNoCacheEmitter(builder): origEmitter = builder.emitter if SCons.Util.is_Dict(origEmitter): for k, v in origEmitter: origEmitter[k] = SCons.Builder.ListEmitter([v, noCacheEmitter]) elif SCons.Util.is_List(origEmitter): origEmitter.append(noCacheEmitter) else: builder.emitter = SCons.Builder.ListEmitter([origEmitter, noCacheEmitter]) addNoCacheEmitter(env["BUILDERS"]["Program"]) addNoCacheEmitter(env["BUILDERS"]["StaticLibrary"]) addNoCacheEmitter(env["BUILDERS"]["SharedLibrary"]) addNoCacheEmitter(env["BUILDERS"]["SharedArchive"]) addNoCacheEmitter(env["BUILDERS"]["LoadableModule"]) if env.GetOption("patch-build-mongot-url"): binary_url = env.GetOption("patch-build-mongot-url") env.Command( target="mongot-localdev", source=[], action=[ f"curl {binary_url} | tar xvz", ], ) env.AutoInstall( target="$PREFIX_BINDIR", source=["mongot-localdev"], AIB_COMPONENT="mongot", AIB_ROLE="runtime", AIB_COMPONENTS_EXTRA=["dist-test"], ) # mongot is a MongoDB-specific process written as a wrapper around Lucene. Using Lucene, mongot # indexes MongoDB databases to provide our customers with full text search capabilities. # # --build-mongot is utilized as a compile flag by the evergreen build variants that run end-to-end # search suites. It downloads & bundles mongot with the other mongo binaries. These binaries become # available to the build variants in question when the binaries are extracted via archive_dist_test # during compilation. elif env.GetOption("build-mongot"): # '--build-mongot` can be 'latest' or'release' # - 'latest' describes the binaries created by the most recent commit merged to 10gen/mongot. # - 'release' refers to the mongot binaries running in atlas prod. binary_ver_str = env.GetOption("build-mongot") platform_str = "" if mongo_platform.is_running_os("linux"): platform_str = "linux" elif mongo_platform.is_running_os("darwin"): platform_str = "macos" else: print("mongot is only supported on macOS and linux") Exit(1) arch_str = "x86_64" # macos arm64 is not supported by mongot, but macos x86_64 runs on it successfully if mongo_platform.is_arm_processor() and platform_str != "macos": arch_str = "aarch64" db_contrib_tool = env.Command( target=["$BUILD_ROOT/db_contrib_tool_venv/bin/db-contrib-tool"], source=[], action=[ "rm -rf $BUILD_ROOT/db_contrib_tool_venv", f"{sys.executable} -m virtualenv -p {sys.executable} $BUILD_ROOT/db_contrib_tool_venv", "$BUILD_ROOT/db_contrib_tool_venv/bin/python3 -m pip install db-contrib-tool", ], BUILD_ROOT=env.Dir("$BUILD_ROOT").path, ) env.Command( target=["mongot-localdev"], source=db_contrib_tool, action=[ f"$SOURCE setup-mongot-repro-env {binary_ver_str} --platform={platform_str} --architecture={arch_str}", "mv build/mongot-localdev mongot-localdev", ], ENV=os.environ, ) env.AutoInstall( target="$PREFIX_BINDIR", source=["mongot-localdev"], AIB_COMPONENT="mongot", AIB_ROLE="runtime", AIB_COMPONENTS_EXTRA=["dist-test"], ) # load the tool late to make sure we can copy over any new # emitters/scanners we may have created in the SConstruct when # we go to make stand in bazel builders for the various scons builders # __NINJA_NO is ninja callback to scons signal, in that case we care about # scons only targets not thin targets. env.Tool("integrate_bazel") if env.get("__NINJA_NO") == "1": env.LoadBazelBuilders() def noop(*args, **kwargs): pass env.AddMethod(noop, "WaitForBazel") env.AddMethod(noop, "BazelAutoInstall") if env.get("__NINJA_NO") != "1": BAZEL_AUTOINSTALLED_LIBDEPS = set() # the next emitters will read link lists # to determine dependencies in order for scons # to handle the install def bazel_auto_install_emitter(target, source, env): for libdep in env.Flatten(env.get("LIBDEPS", [])) + env.Flatten( env.get("LIBDEPS_PRIVATE", []) ): libdep_node = libdeps._get_node_with_ixes( env, env.Entry(libdep).abspath, "SharedLibrary" ) try: shlib_suffix = env.subst("$SHLIBSUFFIX") bazel_libdep = env.File( f"#/{env['SCONS2BAZEL_TARGETS'].bazel_output(libdep_node.path)}" ) if str(bazel_libdep).endswith(shlib_suffix): if bazel_libdep not in BAZEL_AUTOINSTALLED_LIBDEPS: env.BazelAutoInstall(bazel_libdep, shlib_suffix) BAZEL_AUTOINSTALLED_LIBDEPS.add(bazel_libdep) env.Depends( env.GetAutoInstalledFiles(target[0]), env.GetAutoInstalledFiles(bazel_libdep), ) except KeyError: if env.Verbose(): print(f"BazelAutoInstall not processing non bazel target:\n{libdep_node}") return target, source for builder_name in ["Program", "SharedLibrary"]: builder = env["BUILDERS"][builder_name] base_emitter = builder.emitter new_emitter = SCons.Builder.ListEmitter([base_emitter, bazel_auto_install_emitter]) builder.emitter = new_emitter def bazel_program_auto_install_emitter(target, source, env): if env.GetOption("link-model") == "dynamic-sdk": return target, source bazel_target = env["SCONS2BAZEL_TARGETS"].bazel_target(target[0].path) linkfile = bazel_target.replace("//src/", "bazel-bin/src/") + "_links.list" linkfile = "/".join(linkfile.rsplit(":", 1)) with open(os.path.join(env.Dir("#").abspath, linkfile)) as f: query_results = f.read() filtered_results = "" for lib in query_results.splitlines(): bazel_out_path = lib.replace("\\", "/").replace( f"{env['BAZEL_OUT_DIR']}/src", "bazel-bin/src" ) if os.path.exists( env.File("#/" + bazel_out_path + ".exclude_lib").abspath.replace("\\", "/") ): continue filtered_results += lib + "\n" query_results = filtered_results t = target[0] suffix = getattr(t.attributes, "aib_effective_suffix", t.get_suffix()) bazel_node = env.File( t.abspath.replace( f"{env.Dir('#').abspath}/{env['BAZEL_OUT_DIR']}/src", env.Dir("$BUILD_DIR").path ) ) prog_output = env.BazelAutoInstallSingleTarget(bazel_node, suffix, bazel_node) libs = [] debugs = [] for lib in query_results.splitlines(): libdep = env.File( lib.replace("\\", "/").replace(f"{env['BAZEL_OUT_DIR']}/src", "$BUILD_DIR") ) libdep_node = libdeps._get_node_with_ixes( env, env.Entry(libdep).abspath, "SharedLibrary" ) bazel_libdep = env.File( f"#/{env['SCONS2BAZEL_TARGETS'].bazel_output(libdep_node.path)}" ) shlib_suffix = env.subst("$SHLIBSUFFIX") if str(bazel_libdep).endswith(shlib_suffix): if bazel_libdep not in BAZEL_AUTOINSTALLED_LIBDEPS: env.BazelAutoInstall(bazel_libdep, shlib_suffix) BAZEL_AUTOINSTALLED_LIBDEPS.add(bazel_libdep) libs.append(env.GetAutoInstalledFiles(bazel_libdep)[0]) if hasattr(bazel_libdep.attributes, "separate_debug_files"): debugs.append( env.GetAutoInstalledFiles( getattr(bazel_libdep.attributes, "separate_debug_files")[0] )[0] ) env.Depends(prog_output[0], libs) if len(prog_output) == 2: env.Depends(prog_output[1], debugs) return target, source builder = env["BUILDERS"]["BazelProgram"] base_emitter = builder.emitter new_emitter = SCons.Builder.ListEmitter([bazel_program_auto_install_emitter, base_emitter]) builder.emitter = new_emitter def injectMongoIncludePaths(thisEnv): if thisEnv.get("BAZEL_OUT_DIR"): thisEnv.AppendUnique(CPPPATH=["#$BAZEL_OUT_DIR/src"]) thisEnv.AppendUnique(CPPPATH=["$BUILD_DIR"]) env.AddMethod(injectMongoIncludePaths, "InjectMongoIncludePaths") gen_header_paths = [ (pathlib.Path(env.Dir("$BAZEL_OUT_DIR").path) / "src" / "mongo").as_posix(), (pathlib.Path(env.Dir("$BUILD_DIR").path) / "mongo").as_posix(), ] replacements = { "@MONGO_BUILD_DIR@": ("|".join([path + "/.*" for path in gen_header_paths])), "@MONGO_BRACKET_BUILD_DIR@": (";".join(gen_header_paths)), } clang_tidy_config = env.Substfile( target=".clang-tidy", source=[ ".clang-tidy.in", ], SUBST_DICT=replacements, ) env.Alias("generated-sources", clang_tidy_config) if get_option("bazel-includes-info"): env.Tool("bazel_includes_info") env.WaitForBazel() if str(env["LIBDEPS_GRAPH_ALIAS"]) in COMMAND_LINE_TARGETS: # The find_symbols binary is a small fast C binary which will extract the missing # symbols from the target library, and discover what linked libraries supply it. This # setups the binary to be built. find_symbols_env = env.Clone() find_symbols_env.VariantDir("${BUILD_DIR}/libdeps", "buildscripts/libdeps", duplicate=0) find_symbols_env.Program( target="${BUILD_DIR}/libdeps/find_symbols", source=["${BUILD_DIR}/libdeps/find_symbols.c"], CFLAGS=["-O3"], ) env.SConscript( must_exist=1, dirs=[ "src", ], duplicate=False, exports=[ "env", ], variant_dir="$BUILD_DIR", ) # TODO: find a way to consolidate SConscript calls to one call in # SConstruct so they all use variant_dir env.SConscript( must_exist=1, dirs=[ "jstests", ], duplicate=False, exports=[ "env", ], ) # Critically, this approach is technically incorrect. While all MongoDB # SConscript files use our add_option wrapper, builtin tools can # access SCons's GetOption/AddOption methods directly, causing their options # to not be validated by this block. (_, leftover) = _parser.parse_args(sys.argv) # leftover contains unrecognized options, including environment variables,and # the argv[0]. If we only look at flags starting with --, and we skip the first # leftover value (argv[0]), anything that remains is an invalid option invalid_options = list(filter(lambda x: x.startswith("--"), leftover[1:])) if len(invalid_options) > 0: # users frequently misspell "variables-files" (note two `s`s) as # "variable-files" or "variables-file". Detect and help them out. for opt in invalid_options: bad_var_file_opts = ["--variable-file", "--variables-file", "--variable-files"] if opt in bad_var_file_opts or any( [opt.startswith(f"{bad_opt}=") for bad_opt in bad_var_file_opts] ): print( f"WARNING: You supplied the invalid parameter '{opt}' to SCons. Did you mean --variables-files (both words plural)?" ) fatal_error(None, f"ERROR: unknown options supplied to scons: {invalid_options}") # Declare the cache prune target cachePrune = env.Command( target="#cache-prune", source=[ "#buildscripts/scons_cache_prune.py", ], action="$PYTHON ${SOURCES[0]} --cache-dir=${CACHE_DIR.abspath} --cache-size=${CACHE_SIZE} --prune-ratio=${CACHE_PRUNE_TARGET/100.00}", CACHE_DIR=env.Dir(cacheDir), ) env.AlwaysBuild(cachePrune) # Add a trivial Alias called `configure`. This makes it simple to run, # or re-run, the SConscript reading and conf tests, but not build any # real targets. This can be helpful when you are planning a dry-run # build, or simply want to validate your changes to SConstruct, tools, # and all the other setup that happens before we begin a real graph # walk. env.Alias("configure", None) env.CreateConsolidatedTargets() # We have finished all SConscripts and targets, so we can ask # auto_install_binaries to finalize the installation setup. env.FinalizeInstallDependencies() # Create a install-all-meta alias that excludes unittests. This is most useful in # static builds where the resource requirements of linking 100s of static unittest # binaries is prohibitive. candidate_nodes = set() for child in env.Alias("install-all-meta")[0].all_children(): candidate_nodes.add(child) for gchild in child.all_children(): candidate_nodes.add(gchild) names = [ f'install-{env["AIB_META_COMPONENT"]}', "install-tests", env["UNITTEST_ALIAS"], "install-first-quarter-unittests", "install-second-quarter-unittests", "install-third-quarter-unittests", "install-fourth-quarter-unittests", ] env.Alias( "install-all-meta-but-not-unittests", [ node for node in candidate_nodes if str(node) not in names and not str(node).startswith(tuple([prefix_name + "-" for prefix_name in names])) ], ) # prove prefix only operates on real AIB_COMPONENTS, unittests is now a meta component # made up of all the quarter components combined. We create the prove alias for this meta # component for compatibility with the past and ease of use. env.Alias( "prove-unittests", [ "prove-first-quarter-unittests", "prove-second-quarter-unittests", "prove-third-quarter-unittests", "prove-fourth-quarter-unittests", ], ) # We don't want installing files to cause them to flow into the cache, # since presumably we can re-install them from the origin if needed. env.NoCache(env.FindInstalledFiles()) # Substitute environment variables in any build targets so that we can # say, for instance: # # > scons --prefix=/foo/bar '$DESTDIR' # or # > scons \$BUILD_DIR/mongo/base # # That way, you can reference targets under the variant dir or install # path via an invariant name. # # We need to replace the values in the BUILD_TARGETS object in-place # because SCons wants it to be a particular object. for i, s in enumerate(BUILD_TARGETS): BUILD_TARGETS[i] = env.subst(s) # Do any final checks the Libdeps linter may need to do once all # SConscripts have been read but before building begins. libdeps.LibdepLinter(env).final_checks() libdeps.generate_libdeps_graph(env) # We put this next section at the end of the SConstruct since all the targets # have been declared, and we know all possible bazel targets so # we can now generate this info into a file for the ninja build to consume. if env.GetOption("ninja") != "disabled" and env.get("__NINJA_NO") != "1": # convert the SCons FunctioAction into a format that ninja can understand env.NinjaRegisterFunctionHandler("bazel_builder_action", env.NinjaBazelBuilder) # we generate the list of all targets that were labeled Bazel* builder targets # via the emitter, this outputs a json file which will be read during the ninja # build. env.GenerateBazelInfoForNinja()