1*af6a4c17SAlex Richardson#!/usr/bin/env python3 2*af6a4c17SAlex Richardson# PYTHON_ARGCOMPLETE_OKAY 3*af6a4c17SAlex Richardson# - 4*af6a4c17SAlex Richardson# SPDX-License-Identifier: BSD-2-Clause-FreeBSD 5*af6a4c17SAlex Richardson# 6*af6a4c17SAlex Richardson# Copyright (c) 2018 Alex Richardson <arichardson@FreeBSD.org> 7*af6a4c17SAlex Richardson# 8*af6a4c17SAlex Richardson# Redistribution and use in source and binary forms, with or without 9*af6a4c17SAlex Richardson# modification, are permitted provided that the following conditions 10*af6a4c17SAlex Richardson# are met: 11*af6a4c17SAlex Richardson# 1. Redistributions of source code must retain the above copyright 12*af6a4c17SAlex Richardson# notice, this list of conditions and the following disclaimer. 13*af6a4c17SAlex Richardson# 2. Redistributions in binary form must reproduce the above copyright 14*af6a4c17SAlex Richardson# notice, this list of conditions and the following disclaimer in the 15*af6a4c17SAlex Richardson# documentation and/or other materials provided with the distribution. 16*af6a4c17SAlex Richardson# 17*af6a4c17SAlex Richardson# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18*af6a4c17SAlex Richardson# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19*af6a4c17SAlex Richardson# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20*af6a4c17SAlex Richardson# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21*af6a4c17SAlex Richardson# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22*af6a4c17SAlex Richardson# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23*af6a4c17SAlex Richardson# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24*af6a4c17SAlex Richardson# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25*af6a4c17SAlex Richardson# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26*af6a4c17SAlex Richardson# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27*af6a4c17SAlex Richardson# SUCH DAMAGE. 28*af6a4c17SAlex Richardson# 29*af6a4c17SAlex Richardson# $FreeBSD$ 30*af6a4c17SAlex Richardson# 31*af6a4c17SAlex Richardson 32*af6a4c17SAlex Richardson# This script makes it easier to build on non-FreeBSD systems by bootstrapping 33*af6a4c17SAlex Richardson# bmake and inferring required compiler variables. 34*af6a4c17SAlex Richardson# 35*af6a4c17SAlex Richardson# On FreeBSD you can use it the same way as just calling make: 36*af6a4c17SAlex Richardson# `MAKEOBJDIRPREFIX=~/obj ./tools/build/make.py buildworld -DWITH_FOO` 37*af6a4c17SAlex Richardson# 38*af6a4c17SAlex Richardson# On Linux and MacOS you will either need to set XCC/XCXX/XLD/XCPP or pass 39*af6a4c17SAlex Richardson# --cross-bindir to specify the path to the cross-compiler bindir: 40*af6a4c17SAlex Richardson# `MAKEOBJDIRPREFIX=~/obj ./tools/build/make.py 41*af6a4c17SAlex Richardson# --cross-bindir=/path/to/cross/compiler buildworld -DWITH_FOO TARGET=foo 42*af6a4c17SAlex Richardson# TARGET_ARCH=bar` 43*af6a4c17SAlex Richardsonimport argparse 44*af6a4c17SAlex Richardsonimport os 45*af6a4c17SAlex Richardsonimport shlex 46*af6a4c17SAlex Richardsonimport shutil 47*af6a4c17SAlex Richardsonimport subprocess 48*af6a4c17SAlex Richardsonimport sys 49*af6a4c17SAlex Richardsonfrom pathlib import Path 50*af6a4c17SAlex Richardson 51*af6a4c17SAlex Richardson 52*af6a4c17SAlex Richardsondef run(cmd, **kwargs): 53*af6a4c17SAlex Richardson cmd = list(map(str, cmd)) # convert all Path objects to str 54*af6a4c17SAlex Richardson debug("Running", cmd) 55*af6a4c17SAlex Richardson subprocess.check_call(cmd, **kwargs) 56*af6a4c17SAlex Richardson 57*af6a4c17SAlex Richardson 58*af6a4c17SAlex Richardsondef bootstrap_bmake(source_root, objdir_prefix): 59*af6a4c17SAlex Richardson bmake_source_dir = source_root / "contrib/bmake" 60*af6a4c17SAlex Richardson bmake_build_dir = objdir_prefix / "bmake-build" 61*af6a4c17SAlex Richardson bmake_install_dir = objdir_prefix / "bmake-install" 62*af6a4c17SAlex Richardson bmake_binary = bmake_install_dir / "bin/bmake" 63*af6a4c17SAlex Richardson 64*af6a4c17SAlex Richardson if (bmake_install_dir / "bin/bmake").exists(): 65*af6a4c17SAlex Richardson return bmake_binary 66*af6a4c17SAlex Richardson print("Bootstrapping bmake...") 67*af6a4c17SAlex Richardson # TODO: check if the host system bmake is new enough and use that instead 68*af6a4c17SAlex Richardson if not bmake_build_dir.exists(): 69*af6a4c17SAlex Richardson os.makedirs(str(bmake_build_dir)) 70*af6a4c17SAlex Richardson env = os.environ.copy() 71*af6a4c17SAlex Richardson global new_env_vars 72*af6a4c17SAlex Richardson env.update(new_env_vars) 73*af6a4c17SAlex Richardson 74*af6a4c17SAlex Richardson if sys.platform.startswith("linux"): 75*af6a4c17SAlex Richardson # Work around the deleted file bmake/missing/sys/cdefs.h 76*af6a4c17SAlex Richardson # TODO: bmake should keep the compat sys/cdefs.h 77*af6a4c17SAlex Richardson env["CFLAGS"] = "-I{src}/tools/build/cross-build/include/common " \ 78*af6a4c17SAlex Richardson "-I{src}/tools/build/cross-build/include/linux " \ 79*af6a4c17SAlex Richardson "-D_GNU_SOURCE=1".format(src=source_root) 80*af6a4c17SAlex Richardson configure_args = [ 81*af6a4c17SAlex Richardson "--with-default-sys-path=" + str(bmake_install_dir / "share/mk"), 82*af6a4c17SAlex Richardson "--with-machine=amd64", # TODO? "--with-machine-arch=amd64", 83*af6a4c17SAlex Richardson "--without-filemon", "--prefix=" + str(bmake_install_dir)] 84*af6a4c17SAlex Richardson run(["sh", bmake_source_dir / "boot-strap"] + configure_args, 85*af6a4c17SAlex Richardson cwd=str(bmake_build_dir), env=env) 86*af6a4c17SAlex Richardson 87*af6a4c17SAlex Richardson run(["sh", bmake_source_dir / "boot-strap", "op=install"] + configure_args, 88*af6a4c17SAlex Richardson cwd=str(bmake_build_dir)) 89*af6a4c17SAlex Richardson print("Finished bootstrapping bmake...") 90*af6a4c17SAlex Richardson return bmake_binary 91*af6a4c17SAlex Richardson 92*af6a4c17SAlex Richardson 93*af6a4c17SAlex Richardsondef debug(*args, **kwargs): 94*af6a4c17SAlex Richardson global parsed_args 95*af6a4c17SAlex Richardson if parsed_args.debug: 96*af6a4c17SAlex Richardson print(*args, **kwargs) 97*af6a4c17SAlex Richardson 98*af6a4c17SAlex Richardson 99*af6a4c17SAlex Richardsondef is_make_var_set(var): 100*af6a4c17SAlex Richardson return any( 101*af6a4c17SAlex Richardson x.startswith(var + "=") or x == ("-D" + var) for x in sys.argv[1:]) 102*af6a4c17SAlex Richardson 103*af6a4c17SAlex Richardson 104*af6a4c17SAlex Richardsondef check_required_make_env_var(varname, binary_name, bindir): 105*af6a4c17SAlex Richardson global new_env_vars 106*af6a4c17SAlex Richardson if os.getenv(varname): 107*af6a4c17SAlex Richardson return 108*af6a4c17SAlex Richardson if not bindir: 109*af6a4c17SAlex Richardson sys.exit("Could not infer value for $" + varname + ". Either set $" + 110*af6a4c17SAlex Richardson varname + " or pass --cross-bindir=/cross/compiler/dir/bin") 111*af6a4c17SAlex Richardson # try to infer the path to the tool 112*af6a4c17SAlex Richardson guess = os.path.join(bindir, binary_name) 113*af6a4c17SAlex Richardson if not os.path.isfile(guess): 114*af6a4c17SAlex Richardson sys.exit("Could not infer value for $" + varname + ": " + guess + 115*af6a4c17SAlex Richardson " does not exist") 116*af6a4c17SAlex Richardson new_env_vars[varname] = guess 117*af6a4c17SAlex Richardson debug("Inferred", varname, "as", guess) 118*af6a4c17SAlex Richardson 119*af6a4c17SAlex Richardson 120*af6a4c17SAlex Richardsondef default_cross_toolchain(): 121*af6a4c17SAlex Richardson # default to homebrew-installed clang on MacOS if available 122*af6a4c17SAlex Richardson if sys.platform.startswith("darwin"): 123*af6a4c17SAlex Richardson if shutil.which("brew"): 124*af6a4c17SAlex Richardson llvm_dir = subprocess.getoutput("brew --prefix llvm") 125*af6a4c17SAlex Richardson if llvm_dir and Path(llvm_dir, "bin").exists(): 126*af6a4c17SAlex Richardson return str(Path(llvm_dir, "bin")) 127*af6a4c17SAlex Richardson return None 128*af6a4c17SAlex Richardson 129*af6a4c17SAlex Richardson 130*af6a4c17SAlex Richardsonif __name__ == "__main__": 131*af6a4c17SAlex Richardson parser = argparse.ArgumentParser( 132*af6a4c17SAlex Richardson formatter_class=argparse.ArgumentDefaultsHelpFormatter) 133*af6a4c17SAlex Richardson parser.add_argument("--host-bindir", 134*af6a4c17SAlex Richardson help="Directory to look for cc/c++/cpp/ld to build " 135*af6a4c17SAlex Richardson "host (" + sys.platform + ") binaries", 136*af6a4c17SAlex Richardson default="/usr/bin") 137*af6a4c17SAlex Richardson parser.add_argument("--cross-bindir", default=default_cross_toolchain(), 138*af6a4c17SAlex Richardson help="Directory to look for cc/c++/cpp/ld to build " 139*af6a4c17SAlex Richardson "target binaries (only needed if XCC/XCPP/XLD " 140*af6a4c17SAlex Richardson "are not set)") 141*af6a4c17SAlex Richardson parser.add_argument("--cross-compiler-type", choices=("clang", "gcc"), 142*af6a4c17SAlex Richardson default="clang", 143*af6a4c17SAlex Richardson help="Compiler type to find in --cross-bindir (only " 144*af6a4c17SAlex Richardson "needed if XCC/XCPP/XLD are not set)" 145*af6a4c17SAlex Richardson "Note: using CC is currently highly experimental") 146*af6a4c17SAlex Richardson parser.add_argument("--host-compiler-type", choices=("cc", "clang", "gcc"), 147*af6a4c17SAlex Richardson default="cc", 148*af6a4c17SAlex Richardson help="Compiler type to find in --host-bindir (only " 149*af6a4c17SAlex Richardson "needed if CC/CPP/CXX are not set). ") 150*af6a4c17SAlex Richardson parser.add_argument("--debug", action="store_true", 151*af6a4c17SAlex Richardson help="Print information on inferred env vars") 152*af6a4c17SAlex Richardson parser.add_argument("--clean", action="store_true", 153*af6a4c17SAlex Richardson help="Do a clean rebuild instead of building with " 154*af6a4c17SAlex Richardson "-DNO_CLEAN") 155*af6a4c17SAlex Richardson parser.add_argument("--no-clean", action="store_false", dest="clean", 156*af6a4c17SAlex Richardson help="Do a clean rebuild instead of building with " 157*af6a4c17SAlex Richardson "-DNO_CLEAN") 158*af6a4c17SAlex Richardson try: 159*af6a4c17SAlex Richardson import argcomplete # bash completion: 160*af6a4c17SAlex Richardson 161*af6a4c17SAlex Richardson argcomplete.autocomplete(parser) 162*af6a4c17SAlex Richardson except ImportError: 163*af6a4c17SAlex Richardson pass 164*af6a4c17SAlex Richardson parsed_args, bmake_args = parser.parse_known_args() 165*af6a4c17SAlex Richardson 166*af6a4c17SAlex Richardson MAKEOBJDIRPREFIX = os.getenv("MAKEOBJDIRPREFIX") 167*af6a4c17SAlex Richardson if not MAKEOBJDIRPREFIX: 168*af6a4c17SAlex Richardson sys.exit("MAKEOBJDIRPREFIX is not set, cannot continue!") 169*af6a4c17SAlex Richardson if not Path(MAKEOBJDIRPREFIX).is_dir(): 170*af6a4c17SAlex Richardson sys.exit( 171*af6a4c17SAlex Richardson "Chosen MAKEOBJDIRPREFIX=" + MAKEOBJDIRPREFIX + " doesn't exit!") 172*af6a4c17SAlex Richardson objdir_prefix = Path(MAKEOBJDIRPREFIX).absolute() 173*af6a4c17SAlex Richardson source_root = Path(__file__).absolute().parent.parent.parent 174*af6a4c17SAlex Richardson 175*af6a4c17SAlex Richardson new_env_vars = {} 176*af6a4c17SAlex Richardson if not sys.platform.startswith("freebsd"): 177*af6a4c17SAlex Richardson if not is_make_var_set("TARGET") or not is_make_var_set("TARGET_ARCH"): 178*af6a4c17SAlex Richardson if "universe" not in sys.argv and "tinderbox" not in sys.argv: 179*af6a4c17SAlex Richardson sys.exit("TARGET= and TARGET_ARCH= must be set explicitly " 180*af6a4c17SAlex Richardson "when building on non-FreeBSD") 181*af6a4c17SAlex Richardson # infer values for CC/CXX/CPP 182*af6a4c17SAlex Richardson 183*af6a4c17SAlex Richardson if sys.platform.startswith( 184*af6a4c17SAlex Richardson "linux") and parsed_args.host_compiler_type == "cc": 185*af6a4c17SAlex Richardson # FIXME: bsd.compiler.mk doesn't handle the output of GCC if it 186*af6a4c17SAlex Richardson # is /usr/bin/cc on Ubuntu since it doesn't contain the GCC string. 187*af6a4c17SAlex Richardson parsed_args.host_compiler_type = "gcc" 188*af6a4c17SAlex Richardson 189*af6a4c17SAlex Richardson if parsed_args.host_compiler_type == "gcc": 190*af6a4c17SAlex Richardson default_cc, default_cxx, default_cpp = ("gcc", "g++", "cpp") 191*af6a4c17SAlex Richardson elif parsed_args.host_compiler_type == "clang": 192*af6a4c17SAlex Richardson default_cc, default_cxx, default_cpp = ( 193*af6a4c17SAlex Richardson "clang", "clang++", "clang-cpp") 194*af6a4c17SAlex Richardson else: 195*af6a4c17SAlex Richardson default_cc, default_cxx, default_cpp = ("cc", "c++", "cpp") 196*af6a4c17SAlex Richardson 197*af6a4c17SAlex Richardson check_required_make_env_var("CC", default_cc, parsed_args.host_bindir) 198*af6a4c17SAlex Richardson check_required_make_env_var("CXX", default_cxx, 199*af6a4c17SAlex Richardson parsed_args.host_bindir) 200*af6a4c17SAlex Richardson check_required_make_env_var("CPP", default_cpp, 201*af6a4c17SAlex Richardson parsed_args.host_bindir) 202*af6a4c17SAlex Richardson # Using the default value for LD is fine (but not for XLD!) 203*af6a4c17SAlex Richardson 204*af6a4c17SAlex Richardson use_cross_gcc = parsed_args.cross_compiler_type == "gcc" 205*af6a4c17SAlex Richardson # On non-FreeBSD we need to explicitly pass XCC/XLD/X_COMPILER_TYPE 206*af6a4c17SAlex Richardson check_required_make_env_var("XCC", "gcc" if use_cross_gcc else "clang", 207*af6a4c17SAlex Richardson parsed_args.cross_bindir) 208*af6a4c17SAlex Richardson check_required_make_env_var("XCXX", 209*af6a4c17SAlex Richardson "g++" if use_cross_gcc else "clang++", 210*af6a4c17SAlex Richardson parsed_args.cross_bindir) 211*af6a4c17SAlex Richardson check_required_make_env_var("XCPP", 212*af6a4c17SAlex Richardson "cpp" if use_cross_gcc else "clang-cpp", 213*af6a4c17SAlex Richardson parsed_args.cross_bindir) 214*af6a4c17SAlex Richardson check_required_make_env_var("XLD", "ld" if use_cross_gcc else "ld.lld", 215*af6a4c17SAlex Richardson parsed_args.cross_bindir) 216*af6a4c17SAlex Richardson 217*af6a4c17SAlex Richardson bmake_binary = bootstrap_bmake(source_root, objdir_prefix) 218*af6a4c17SAlex Richardson # at -j1 cleandir+obj is unbearably slow. AUTO_OBJ helps a lot 219*af6a4c17SAlex Richardson debug("Adding -DWITH_AUTO_OBJ") 220*af6a4c17SAlex Richardson bmake_args.append("-DWITH_AUTO_OBJ") 221*af6a4c17SAlex Richardson if parsed_args.clean is False: 222*af6a4c17SAlex Richardson bmake_args.append("-DWITHOUT_CLEAN") 223*af6a4c17SAlex Richardson if (parsed_args.clean is None and not is_make_var_set("NO_CLEAN") 224*af6a4c17SAlex Richardson and not is_make_var_set("WITHOUT_CLEAN")): 225*af6a4c17SAlex Richardson # Avoid accidentally deleting all of the build tree and wasting lots of 226*af6a4c17SAlex Richardson # time cleaning directories instead of just doing a rm -rf ${.OBJDIR} 227*af6a4c17SAlex Richardson want_clean = input("You did not set -DNO_CLEAN/--clean/--no-clean." 228*af6a4c17SAlex Richardson " Did you really mean to do a clean build? y/[N] ") 229*af6a4c17SAlex Richardson if not want_clean.lower().startswith("y"): 230*af6a4c17SAlex Richardson bmake_args.append("-DNO_CLEAN") 231*af6a4c17SAlex Richardson 232*af6a4c17SAlex Richardson env_cmd_str = " ".join( 233*af6a4c17SAlex Richardson shlex.quote(k + "=" + v) for k, v in new_env_vars.items()) 234*af6a4c17SAlex Richardson make_cmd_str = " ".join( 235*af6a4c17SAlex Richardson shlex.quote(s) for s in [str(bmake_binary)] + bmake_args) 236*af6a4c17SAlex Richardson debug("Running `env ", env_cmd_str, " ", make_cmd_str, "`", sep="") 237*af6a4c17SAlex Richardson os.environ.update(new_env_vars) 238*af6a4c17SAlex Richardson if parsed_args.debug: 239*af6a4c17SAlex Richardson input("Press enter to continue...") 240*af6a4c17SAlex Richardson os.chdir(str(source_root)) 241*af6a4c17SAlex Richardson os.execv(str(bmake_binary), [str(bmake_binary)] + bmake_args) 242