xref: /freebsd/tools/build/make.py (revision d55de30efd09ee011bf4604be1c15ce4155775c1)
1af6a4c17SAlex Richardson#!/usr/bin/env python3
2af6a4c17SAlex Richardson# PYTHON_ARGCOMPLETE_OKAY
3af6a4c17SAlex Richardson# -
44d846d26SWarner Losh# SPDX-License-Identifier: BSD-2-Clause
5af6a4c17SAlex Richardson#
6af6a4c17SAlex Richardson# Copyright (c) 2018 Alex Richardson <arichardson@FreeBSD.org>
7af6a4c17SAlex Richardson#
8af6a4c17SAlex Richardson# Redistribution and use in source and binary forms, with or without
9af6a4c17SAlex Richardson# modification, are permitted provided that the following conditions
10af6a4c17SAlex Richardson# are met:
11af6a4c17SAlex Richardson# 1. Redistributions of source code must retain the above copyright
12af6a4c17SAlex Richardson#    notice, this list of conditions and the following disclaimer.
13af6a4c17SAlex Richardson# 2. Redistributions in binary form must reproduce the above copyright
14af6a4c17SAlex Richardson#    notice, this list of conditions and the following disclaimer in the
15af6a4c17SAlex Richardson#    documentation and/or other materials provided with the distribution.
16af6a4c17SAlex Richardson#
17af6a4c17SAlex Richardson# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18af6a4c17SAlex Richardson# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19af6a4c17SAlex Richardson# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20af6a4c17SAlex Richardson# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21af6a4c17SAlex Richardson# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22af6a4c17SAlex Richardson# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23af6a4c17SAlex Richardson# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24af6a4c17SAlex Richardson# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25af6a4c17SAlex Richardson# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26af6a4c17SAlex Richardson# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27af6a4c17SAlex Richardson# SUCH DAMAGE.
28af6a4c17SAlex Richardson#
29af6a4c17SAlex Richardson#
30af6a4c17SAlex Richardson
31af6a4c17SAlex Richardson# This script makes it easier to build on non-FreeBSD systems by bootstrapping
32af6a4c17SAlex Richardson# bmake and inferring required compiler variables.
33af6a4c17SAlex Richardson#
34af6a4c17SAlex Richardson# On FreeBSD you can use it the same way as just calling make:
35af6a4c17SAlex Richardson# `MAKEOBJDIRPREFIX=~/obj ./tools/build/make.py buildworld -DWITH_FOO`
36af6a4c17SAlex Richardson#
37af6a4c17SAlex Richardson# On Linux and MacOS you will either need to set XCC/XCXX/XLD/XCPP or pass
38af6a4c17SAlex Richardson# --cross-bindir to specify the path to the cross-compiler bindir:
39af6a4c17SAlex Richardson# `MAKEOBJDIRPREFIX=~/obj ./tools/build/make.py
40af6a4c17SAlex Richardson# --cross-bindir=/path/to/cross/compiler buildworld -DWITH_FOO TARGET=foo
41af6a4c17SAlex Richardson# TARGET_ARCH=bar`
42af6a4c17SAlex Richardsonimport argparse
43af6a4c17SAlex Richardsonimport os
44af6a4c17SAlex Richardsonimport shlex
45af6a4c17SAlex Richardsonimport shutil
46af6a4c17SAlex Richardsonimport subprocess
47af6a4c17SAlex Richardsonimport sys
48af6a4c17SAlex Richardsonfrom pathlib import Path
49af6a4c17SAlex Richardson
50af6a4c17SAlex Richardson
515157b451SJessica Clarke# List of targets that are independent of TARGET/TARGET_ARCH and thus do not
525157b451SJessica Clarke# need them to be set. Keep in the same order as Makefile documents them (if
535157b451SJessica Clarke# they are documented).
545157b451SJessica Clarkemach_indep_targets = [
555157b451SJessica Clarke    "cleanuniverse",
565157b451SJessica Clarke    "universe",
575157b451SJessica Clarke    "universe-toolchain",
58edec803cSJessica Clarke    "tinderbox",
595157b451SJessica Clarke    "worlds",
605157b451SJessica Clarke    "kernels",
615157b451SJessica Clarke    "kernel-toolchains",
625157b451SJessica Clarke    "targets",
635157b451SJessica Clarke    "toolchains",
645157b451SJessica Clarke    "makeman",
655157b451SJessica Clarke    "sysent",
665157b451SJessica Clarke]
675157b451SJessica Clarke
685157b451SJessica Clarke
69af6a4c17SAlex Richardsondef run(cmd, **kwargs):
70af6a4c17SAlex Richardson    cmd = list(map(str, cmd))  # convert all Path objects to str
71af6a4c17SAlex Richardson    debug("Running", cmd)
72af6a4c17SAlex Richardson    subprocess.check_call(cmd, **kwargs)
73af6a4c17SAlex Richardson
74af6a4c17SAlex Richardson
7569cfdc81SJessica Clarke# Always bootstraps in order to control bmake's config to ensure compatibility
76af6a4c17SAlex Richardsondef bootstrap_bmake(source_root, objdir_prefix):
77af6a4c17SAlex Richardson    bmake_source_dir = source_root / "contrib/bmake"
78af6a4c17SAlex Richardson    bmake_build_dir = objdir_prefix / "bmake-build"
79af6a4c17SAlex Richardson    bmake_install_dir = objdir_prefix / "bmake-install"
80af6a4c17SAlex Richardson    bmake_binary = bmake_install_dir / "bin/bmake"
8169cfdc81SJessica Clarke    bmake_config = bmake_install_dir / ".make-py-config"
82af6a4c17SAlex Richardson
8369cfdc81SJessica Clarke    bmake_source_version = subprocess.run([
84b771d570SJessica Clarke        "sh", "-c", ". \"$0\"/VERSION; echo $_MAKE_VERSION", bmake_source_dir],
85b771d570SJessica Clarke        stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
8669cfdc81SJessica Clarke    try:
8769cfdc81SJessica Clarke        bmake_source_version = int(bmake_source_version)
8869cfdc81SJessica Clarke    except ValueError:
8969cfdc81SJessica Clarke        sys.exit("Invalid source bmake version '" + bmake_source_version + "'")
9069cfdc81SJessica Clarke
9169cfdc81SJessica Clarke    bmake_installed_version = 0
9269cfdc81SJessica Clarke    if bmake_binary.exists():
9369cfdc81SJessica Clarke        bmake_installed_version = subprocess.run([
9469cfdc81SJessica Clarke            bmake_binary, "-r", "-f", "/dev/null", "-V", "MAKE_VERSION"],
95b771d570SJessica Clarke            stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
9669cfdc81SJessica Clarke        try:
9769cfdc81SJessica Clarke            bmake_installed_version = int(bmake_installed_version.strip())
9869cfdc81SJessica Clarke        except ValueError:
9969cfdc81SJessica Clarke            print("Invalid installed bmake version '" +
10069cfdc81SJessica Clarke                  bmake_installed_version + "', treating as not present")
101af6a4c17SAlex Richardson
102af6a4c17SAlex Richardson    configure_args = [
10362f243acSJessica Clarke        "--with-default-sys-path=.../share/mk:" +
10462f243acSJessica Clarke        str(bmake_install_dir / "share/mk"),
105af6a4c17SAlex Richardson        "--with-machine=amd64",  # TODO? "--with-machine-arch=amd64",
106af6a4c17SAlex Richardson        "--without-filemon", "--prefix=" + str(bmake_install_dir)]
10769cfdc81SJessica Clarke
10869cfdc81SJessica Clarke    configure_args_str = ' '.join([shlex.quote(x) for x in configure_args])
10969cfdc81SJessica Clarke    if bmake_config.exists():
11069cfdc81SJessica Clarke        last_configure_args_str = bmake_config.read_text()
11169cfdc81SJessica Clarke    else:
11269cfdc81SJessica Clarke        last_configure_args_str = ""
11369cfdc81SJessica Clarke
11469cfdc81SJessica Clarke    debug("Source bmake version: " + str(bmake_source_version))
11569cfdc81SJessica Clarke    debug("Installed bmake version: " + str(bmake_installed_version))
11669cfdc81SJessica Clarke    debug("Configure args: " + configure_args_str)
11769cfdc81SJessica Clarke    debug("Last configure args: " + last_configure_args_str)
11869cfdc81SJessica Clarke
11969cfdc81SJessica Clarke    if bmake_installed_version == bmake_source_version and \
12069cfdc81SJessica Clarke       configure_args_str == last_configure_args_str:
12169cfdc81SJessica Clarke        return bmake_binary
12269cfdc81SJessica Clarke
12369cfdc81SJessica Clarke    print("Bootstrapping bmake...")
12469cfdc81SJessica Clarke    if bmake_build_dir.exists():
12569cfdc81SJessica Clarke        shutil.rmtree(str(bmake_build_dir))
12669cfdc81SJessica Clarke    if bmake_install_dir.exists():
12769cfdc81SJessica Clarke        shutil.rmtree(str(bmake_install_dir))
12869cfdc81SJessica Clarke
12969cfdc81SJessica Clarke    os.makedirs(str(bmake_build_dir))
13069cfdc81SJessica Clarke
13169cfdc81SJessica Clarke    env = os.environ.copy()
13269cfdc81SJessica Clarke    global new_env_vars
13369cfdc81SJessica Clarke    env.update(new_env_vars)
13469cfdc81SJessica Clarke
135af6a4c17SAlex Richardson    run(["sh", bmake_source_dir / "boot-strap"] + configure_args,
136af6a4c17SAlex Richardson        cwd=str(bmake_build_dir), env=env)
137af6a4c17SAlex Richardson    run(["sh", bmake_source_dir / "boot-strap", "op=install"] + configure_args,
138af6a4c17SAlex Richardson        cwd=str(bmake_build_dir))
13969cfdc81SJessica Clarke    bmake_config.write_text(configure_args_str)
14069cfdc81SJessica Clarke
141af6a4c17SAlex Richardson    print("Finished bootstrapping bmake...")
142af6a4c17SAlex Richardson    return bmake_binary
143af6a4c17SAlex Richardson
144af6a4c17SAlex Richardson
145af6a4c17SAlex Richardsondef debug(*args, **kwargs):
146af6a4c17SAlex Richardson    global parsed_args
147af6a4c17SAlex Richardson    if parsed_args.debug:
148af6a4c17SAlex Richardson        print(*args, **kwargs)
149af6a4c17SAlex Richardson
150af6a4c17SAlex Richardson
151af6a4c17SAlex Richardsondef is_make_var_set(var):
152af6a4c17SAlex Richardson    return any(
153af6a4c17SAlex Richardson        x.startswith(var + "=") or x == ("-D" + var) for x in sys.argv[1:])
154af6a4c17SAlex Richardson
155af6a4c17SAlex Richardson
156af6a4c17SAlex Richardsondef check_required_make_env_var(varname, binary_name, bindir):
157af6a4c17SAlex Richardson    global new_env_vars
158af6a4c17SAlex Richardson    if os.getenv(varname):
159af6a4c17SAlex Richardson        return
160af6a4c17SAlex Richardson    if not bindir:
161af6a4c17SAlex Richardson        sys.exit("Could not infer value for $" + varname + ". Either set $" +
162af6a4c17SAlex Richardson                 varname + " or pass --cross-bindir=/cross/compiler/dir/bin")
163af6a4c17SAlex Richardson    # try to infer the path to the tool
164af6a4c17SAlex Richardson    guess = os.path.join(bindir, binary_name)
165af6a4c17SAlex Richardson    if not os.path.isfile(guess):
166af6a4c17SAlex Richardson        sys.exit("Could not infer value for $" + varname + ": " + guess +
167af6a4c17SAlex Richardson                 " does not exist")
168af6a4c17SAlex Richardson    new_env_vars[varname] = guess
169af6a4c17SAlex Richardson    debug("Inferred", varname, "as", guess)
170accf9611SUlrich Spörlein    global parsed_args
171accf9611SUlrich Spörlein    if parsed_args.debug:
172accf9611SUlrich Spörlein        run([guess, "--version"])
173af6a4c17SAlex Richardson
1743b4da25eSJessica Clarke
1752b181156SAlex Richardsondef check_xtool_make_env_var(varname, binary_name):
1762b181156SAlex Richardson    # Avoid calling brew --prefix on macOS if all variables are already set:
1772b181156SAlex Richardson    if os.getenv(varname):
1782b181156SAlex Richardson        return
1792b181156SAlex Richardson    global parsed_args
1802b181156SAlex Richardson    if parsed_args.cross_bindir is None:
1812b181156SAlex Richardson        parsed_args.cross_bindir = default_cross_toolchain()
1822b181156SAlex Richardson    return check_required_make_env_var(varname, binary_name,
1832b181156SAlex Richardson                                       parsed_args.cross_bindir)
184af6a4c17SAlex Richardson
1853b4da25eSJessica Clarke
186af6a4c17SAlex Richardsondef default_cross_toolchain():
187af6a4c17SAlex Richardson    # default to homebrew-installed clang on MacOS if available
188af6a4c17SAlex Richardson    if sys.platform.startswith("darwin"):
189af6a4c17SAlex Richardson        if shutil.which("brew"):
190b771d570SJessica Clarke            llvm_dir = subprocess.run([
191b771d570SJessica Clarke                "brew", "--prefix", "llvm"],
192b771d570SJessica Clarke                stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.strip()
193a26ace4dSAlex Richardson            debug("Inferred LLVM dir as", llvm_dir)
194a26ace4dSAlex Richardson            try:
195a26ace4dSAlex Richardson                if llvm_dir and Path(llvm_dir.decode("utf-8"), "bin").exists():
196a26ace4dSAlex Richardson                    return str(Path(llvm_dir.decode("utf-8"), "bin"))
197a26ace4dSAlex Richardson            except OSError:
198a26ace4dSAlex Richardson                return None
199af6a4c17SAlex Richardson    return None
200af6a4c17SAlex Richardson
201af6a4c17SAlex Richardson
202af6a4c17SAlex Richardsonif __name__ == "__main__":
203af6a4c17SAlex Richardson    parser = argparse.ArgumentParser(
204af6a4c17SAlex Richardson        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
205af6a4c17SAlex Richardson    parser.add_argument("--host-bindir",
206af6a4c17SAlex Richardson                        help="Directory to look for cc/c++/cpp/ld to build "
207af6a4c17SAlex Richardson                             "host (" + sys.platform + ") binaries",
208af6a4c17SAlex Richardson                        default="/usr/bin")
209a26ace4dSAlex Richardson    parser.add_argument("--cross-bindir", default=None,
210af6a4c17SAlex Richardson                        help="Directory to look for cc/c++/cpp/ld to build "
211af6a4c17SAlex Richardson                             "target binaries (only needed if XCC/XCPP/XLD "
212af6a4c17SAlex Richardson                             "are not set)")
213af6a4c17SAlex Richardson    parser.add_argument("--cross-compiler-type", choices=("clang", "gcc"),
214af6a4c17SAlex Richardson                        default="clang",
215af6a4c17SAlex Richardson                        help="Compiler type to find in --cross-bindir (only "
216af6a4c17SAlex Richardson                             "needed if XCC/XCPP/XLD are not set)"
217af6a4c17SAlex Richardson                             "Note: using CC is currently highly experimental")
218af6a4c17SAlex Richardson    parser.add_argument("--host-compiler-type", choices=("cc", "clang", "gcc"),
219af6a4c17SAlex Richardson                        default="cc",
220af6a4c17SAlex Richardson                        help="Compiler type to find in --host-bindir (only "
221af6a4c17SAlex Richardson                             "needed if CC/CPP/CXX are not set). ")
222af6a4c17SAlex Richardson    parser.add_argument("--debug", action="store_true",
223af6a4c17SAlex Richardson                        help="Print information on inferred env vars")
22431ba4ce8SAlex Richardson    parser.add_argument("--bootstrap-toolchain", action="store_true",
22531ba4ce8SAlex Richardson                        help="Bootstrap the toolchain instead of using an "
22631ba4ce8SAlex Richardson                             "external one (experimental and not recommended)")
227af6a4c17SAlex Richardson    parser.add_argument("--clean", action="store_true",
228af6a4c17SAlex Richardson                        help="Do a clean rebuild instead of building with "
22943e083beSAlex Richardson                             "-DWITHOUT_CLEAN")
230af6a4c17SAlex Richardson    parser.add_argument("--no-clean", action="store_false", dest="clean",
231af6a4c17SAlex Richardson                        help="Do a clean rebuild instead of building with "
23243e083beSAlex Richardson                             "-DWITHOUT_CLEAN")
233af6a4c17SAlex Richardson    try:
234af6a4c17SAlex Richardson        import argcomplete  # bash completion:
235af6a4c17SAlex Richardson
236af6a4c17SAlex Richardson        argcomplete.autocomplete(parser)
237af6a4c17SAlex Richardson    except ImportError:
238af6a4c17SAlex Richardson        pass
239af6a4c17SAlex Richardson    parsed_args, bmake_args = parser.parse_known_args()
240af6a4c17SAlex Richardson
241af6a4c17SAlex Richardson    MAKEOBJDIRPREFIX = os.getenv("MAKEOBJDIRPREFIX")
242af6a4c17SAlex Richardson    if not MAKEOBJDIRPREFIX:
243af6a4c17SAlex Richardson        sys.exit("MAKEOBJDIRPREFIX is not set, cannot continue!")
244af6a4c17SAlex Richardson    if not Path(MAKEOBJDIRPREFIX).is_dir():
245af6a4c17SAlex Richardson        sys.exit(
246*d55de30eSJose Luis Duran            "Chosen MAKEOBJDIRPREFIX=" + MAKEOBJDIRPREFIX + " doesn't exist!")
247af6a4c17SAlex Richardson    objdir_prefix = Path(MAKEOBJDIRPREFIX).absolute()
248af6a4c17SAlex Richardson    source_root = Path(__file__).absolute().parent.parent.parent
249af6a4c17SAlex Richardson
250af6a4c17SAlex Richardson    new_env_vars = {}
251af6a4c17SAlex Richardson    if not sys.platform.startswith("freebsd"):
252af6a4c17SAlex Richardson        if not is_make_var_set("TARGET") or not is_make_var_set("TARGET_ARCH"):
2535157b451SJessica Clarke            if not set(sys.argv).intersection(set(mach_indep_targets)):
254af6a4c17SAlex Richardson                sys.exit("TARGET= and TARGET_ARCH= must be set explicitly "
255af6a4c17SAlex Richardson                         "when building on non-FreeBSD")
25631ba4ce8SAlex Richardson    if not parsed_args.bootstrap_toolchain:
257af6a4c17SAlex Richardson        # infer values for CC/CXX/CPP
258af6a4c17SAlex Richardson        if parsed_args.host_compiler_type == "gcc":
259af6a4c17SAlex Richardson            default_cc, default_cxx, default_cpp = ("gcc", "g++", "cpp")
260accf9611SUlrich Spörlein        # FIXME: this should take values like `clang-9` and then look for
261accf9611SUlrich Spörlein        # clang-cpp-9, etc. Would alleviate the need to set the bindir on
262accf9611SUlrich Spörlein        # ubuntu/debian at least.
263af6a4c17SAlex Richardson        elif parsed_args.host_compiler_type == "clang":
264af6a4c17SAlex Richardson            default_cc, default_cxx, default_cpp = (
265af6a4c17SAlex Richardson                "clang", "clang++", "clang-cpp")
266af6a4c17SAlex Richardson        else:
267af6a4c17SAlex Richardson            default_cc, default_cxx, default_cpp = ("cc", "c++", "cpp")
268af6a4c17SAlex Richardson
269af6a4c17SAlex Richardson        check_required_make_env_var("CC", default_cc, parsed_args.host_bindir)
270af6a4c17SAlex Richardson        check_required_make_env_var("CXX", default_cxx,
271af6a4c17SAlex Richardson                                    parsed_args.host_bindir)
272af6a4c17SAlex Richardson        check_required_make_env_var("CPP", default_cpp,
273af6a4c17SAlex Richardson                                    parsed_args.host_bindir)
274af6a4c17SAlex Richardson        # Using the default value for LD is fine (but not for XLD!)
275af6a4c17SAlex Richardson
276af6a4c17SAlex Richardson        # On non-FreeBSD we need to explicitly pass XCC/XLD/X_COMPILER_TYPE
277d037edf8SAlex Richardson        use_cross_gcc = parsed_args.cross_compiler_type == "gcc"
2782b181156SAlex Richardson        check_xtool_make_env_var("XCC", "gcc" if use_cross_gcc else "clang")
2792b181156SAlex Richardson        check_xtool_make_env_var("XCXX", "g++" if use_cross_gcc else "clang++")
2802b181156SAlex Richardson        check_xtool_make_env_var("XCPP",
2812b181156SAlex Richardson                                 "cpp" if use_cross_gcc else "clang-cpp")
2822b181156SAlex Richardson        check_xtool_make_env_var("XLD", "ld" if use_cross_gcc else "ld.lld")
283d037edf8SAlex Richardson
284d037edf8SAlex Richardson        # We also need to set STRIPBIN if there is no working strip binary
285d037edf8SAlex Richardson        # in $PATH.
286d037edf8SAlex Richardson        if not shutil.which("strip"):
287d037edf8SAlex Richardson            if sys.platform.startswith("darwin"):
288d037edf8SAlex Richardson                # On macOS systems we have to use /usr/bin/strip.
28988db1cc9SAlex Richardson                sys.exit("Cannot find required tool 'strip'. Please install "
29088db1cc9SAlex Richardson                         "the host compiler and command line tools.")
291d037edf8SAlex Richardson            if parsed_args.host_compiler_type == "clang":
292d037edf8SAlex Richardson                strip_binary = "llvm-strip"
293d037edf8SAlex Richardson            else:
294d037edf8SAlex Richardson                strip_binary = "strip"
295d037edf8SAlex Richardson            check_required_make_env_var("STRIPBIN", strip_binary,
2962b181156SAlex Richardson                                        parsed_args.host_bindir)
297d037edf8SAlex Richardson        if os.getenv("STRIPBIN") or "STRIPBIN" in new_env_vars:
298d037edf8SAlex Richardson            # If we are setting STRIPBIN, we have to set XSTRIPBIN to the
299d037edf8SAlex Richardson            # default if it is not set otherwise already.
300d037edf8SAlex Richardson            if not os.getenv("XSTRIPBIN") and not is_make_var_set("XSTRIPBIN"):
301d037edf8SAlex Richardson                # Use the bootstrapped elftoolchain strip:
302d037edf8SAlex Richardson                new_env_vars["XSTRIPBIN"] = "strip"
303af6a4c17SAlex Richardson
304af6a4c17SAlex Richardson    bmake_binary = bootstrap_bmake(source_root, objdir_prefix)
305af6a4c17SAlex Richardson    # at -j1 cleandir+obj is unbearably slow. AUTO_OBJ helps a lot
306af6a4c17SAlex Richardson    debug("Adding -DWITH_AUTO_OBJ")
307af6a4c17SAlex Richardson    bmake_args.append("-DWITH_AUTO_OBJ")
308af6a4c17SAlex Richardson    if parsed_args.clean is False:
309af6a4c17SAlex Richardson        bmake_args.append("-DWITHOUT_CLEAN")
310af6a4c17SAlex Richardson    if (parsed_args.clean is None and not is_make_var_set("NO_CLEAN")
311af6a4c17SAlex Richardson            and not is_make_var_set("WITHOUT_CLEAN")):
312af6a4c17SAlex Richardson        # Avoid accidentally deleting all of the build tree and wasting lots of
313af6a4c17SAlex Richardson        # time cleaning directories instead of just doing a rm -rf ${.OBJDIR}
3143b4da25eSJessica Clarke        want_clean = input("You did not set -DWITHOUT_CLEAN/--(no-)clean."
315af6a4c17SAlex Richardson                           " Did you really mean to do a clean build? y/[N] ")
316af6a4c17SAlex Richardson        if not want_clean.lower().startswith("y"):
31743e083beSAlex Richardson            bmake_args.append("-DWITHOUT_CLEAN")
318af6a4c17SAlex Richardson
319af6a4c17SAlex Richardson    env_cmd_str = " ".join(
320af6a4c17SAlex Richardson        shlex.quote(k + "=" + v) for k, v in new_env_vars.items())
321af6a4c17SAlex Richardson    make_cmd_str = " ".join(
322af6a4c17SAlex Richardson        shlex.quote(s) for s in [str(bmake_binary)] + bmake_args)
323af6a4c17SAlex Richardson    debug("Running `env ", env_cmd_str, " ", make_cmd_str, "`", sep="")
324af6a4c17SAlex Richardson    os.environ.update(new_env_vars)
325b7ac17b4SAlfredo Dal'Ava Junior
326b7ac17b4SAlfredo Dal'Ava Junior    # Fedora defines bash function wrapper for some shell commands and this
327b7ac17b4SAlfredo Dal'Ava Junior    # makes 'which <command>' return the function's source code instead of
328b7ac17b4SAlfredo Dal'Ava Junior    # the binary path. Undefine it to restore the original behavior.
329b7ac17b4SAlfredo Dal'Ava Junior    os.unsetenv("BASH_FUNC_which%%")
330b7ac17b4SAlfredo Dal'Ava Junior    os.unsetenv("BASH_FUNC_ml%%")
331b7ac17b4SAlfredo Dal'Ava Junior    os.unsetenv("BASH_FUNC_module%%")
332b7ac17b4SAlfredo Dal'Ava Junior
333af6a4c17SAlex Richardson    os.chdir(str(source_root))
334af6a4c17SAlex Richardson    os.execv(str(bmake_binary), [str(bmake_binary)] + bmake_args)
335