⛏️ index : haiku.git

#!/bin/env python3
#
# Haiku build configuration tool
# Copyright 2002-2018, Haiku, Inc. All rights reserved.
#

import argparse
import os
import subprocess
import sys
import errno
from pprint import pprint

parser = argparse.ArgumentParser(description='Configure a build of Haiku')
parser.add_argument('--target-arch', nargs=1,
    help='Target architectures. First provided is primary.', type=str, action='append',
    choices=('x86_gcc2', 'x86', 'x86_64', 'ppc', 'm68k', 'arm', 'arm64', 'riscv64'))
parser.add_argument('--bootstrap', nargs=3,
    help='Prepare for a bootstrap build. No pre-built packages will be used, instead they will be built from the sources (in several phases).',
    metavar=('<haikuporter>','<haikuports.cross>', '<haikuports>'))
parser.add_argument('--build-gcc-toolchain', nargs=1,
    help='Assume cross compilation. Build a gcc-based toolchain.',
    metavar=('<buildtools dir>'))
parser.add_argument('--use-gcc-toolchain', nargs=1,
    help='Assume cross compilation. Build using an existing gcc-based toolchain.',
    metavar=('<prefix>'))
parser.add_argument('--use-clang', default=False, action='store_true', help='Assume native clang build')
parser.add_argument('--distro-compatibility', nargs=1,
    help='The distribution\'s level of compatibility with the official Haiku distribution. The generated files will contain the respective trademarks accordingly.',
    choices=('official', 'compatible', 'default'), default='default')
args = vars(parser.parse_args())

### Global functions

def mkdir_p(path):
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

def bok(message):
    start_color = ""
    end_color = ""
    if sys.stdout.isatty():
        start_color = "\033[92m"
        end_color = "\033[0m"
    print(start_color + message + end_color)

def berror(message):
    start_color = ""
    end_color = ""
    if sys.stdout.isatty():
        start_color = "\033[91m"
        end_color = "\033[0m"
    print(start_color + message + end_color)

def binfo(message):
    start_color = ""
    end_color = ""
    if sys.stdout.isatty():
        start_color = "\033[94m"
        end_color = "\033[0m"
    print(start_color + message + end_color)

### Global Varables

(host_sysname, host_nodename, host_release, host_version, host_machine) = os.uname()
buildConfig = []

# TODO: Remove "../.." if this ever moves to the source root
sourceDir = os.path.realpath(os.path.dirname(os.path.realpath(__file__)) + "/../..")
outputDir = os.getcwd()

# If run in our source dir, assume generated
if outputDir == sourceDir:
    outputDir = sourceDir + "/generated"
    mkdir_p(outputDir)

### Helper Functions

# Run a command, collect stdout into a string
def cmdrun(cmd):
    return subprocess.check_output(cmd).decode(sys.stdout.encoding)

# Get a config key
def get_build_config(key):
    global buildConfig
    for i in buildConfig:
        if i["key"] == key:
            return i["value"]
    return None

# Delete a config key
def drop_build_config(key):
    global buildConfig
    value = get_build_config(key)
    if value != None:
        buildConfig.remove({"key": key, "value": value})

# Set a config key
def set_build_config(key, value):
    global buildConfig
    if get_build_config(key) != None:
        drop_build_config(key)
    buildConfig.append({"key": key, "value": value})

def write_build_config(filename):
    global buildConfig
    with open(filename, "w") as fh:
        fh.write("# -- WARNING --\n")
        fh.write("# This file was AUTOMATICALLY GENERATED by configure, and will be completely\n")
        fh.write("# overwritten the next time configure is run.\n\n")
        for i in buildConfig:
            fh.write(i["key"] + " ?= " + str(i["value"]) + " ;\n")

def triplet_lookup(arch):
    if arch == "x86_gcc2":
        return "i586-pc-haiku"
    elif arch == "x86":
        return "i586-pc-haiku"
    elif arch == "x86_64":
        return "x86_64-unknown-haiku"
    elif arch == "ppc":
        return "powerpc-apple-haiku"
    elif arch == "m68k":
        return "m68k-unknown-haiku"
    elif arch == "arm":
        return "arm-unknown-haiku"
    elif arch == "riscv64":
        return "riscv64-unknown-haiku"
    else:
        berror("Unsupported target architecture: " + arch)
        exit(1)

def platform_lookup(sysname):
    if sysname == "Darwin":
        return "darwin"
    elif sysname == "FreeBSD":
        return "freebsd"
    elif sysname == "Haiku":
        return "haiku_host"
    elif sysname == "Linux":
        return "linux"
    elif sysname == "OpenBSD":
        return "openbsd"
    elif sysname == "SunOS":
        return "sunos"
    else:
        berror("Unknown platform: " + sysname)
        exit(1)

def setup_bootstrap():
    if args["bootstrap"] == None:
        return
    set_build_config("HOST_HAIKU_PORTER", os.path.abspath(args["bootstrap"][0]))
    set_build_config("HAIKU_PORTS", os.path.abspath(args["bootstrap"][1]))
    set_build_config("HAIKU_PORTS_CROSS", os.path.abspath(args["bootstrap"][2]))

def setup_host_tools():
    set_build_config("HOST_SHA256", "sha256sum")
    set_build_config("HOST_EXTENDED_REGEX_SED", "sed -r")

def setup_host_compiler():
    cc = os.environ.get("CC")
    if cc == None:
        # We might want to step through each potential compiler here
        cc = "gcc"
    set_build_config("HOST_PLATFORM", platform_lookup(host_sysname))
    set_build_config("HOST_CC", cc)
    set_build_config("HOST_CC_LD", cmdrun([cc, "-print-prog-name=ld"]).strip())
    set_build_config("HOST_CC_OBJCOPY", cmdrun([cc, "-print-prog-name=objcopy"]).strip())
    set_build_config("HOST_GCC_MACHINE", cmdrun([cc, "-dumpmachine"]).strip())
    set_build_config("HOST_GCC_RAW_VERSION", cmdrun([cc, "-dumpversion"]).strip())

def setup_target_compiler(arch):
    cc = get_build_config("HOST_CC")
    triplet = triplet_lookup(arch)
    set_build_config("HAIKU_GCC_RAW_VERSION_" + arch, cmdrun([cc, "-dumpversion"]).strip())
    set_build_config("HAIKU_GCC_MACHINE_" + arch, triplet)
    set_build_config("HAIKU_CPU_" + arch, arch)
    if args["use_clang"]:
        set_build_config("HAIKU_CC_" + arch, "clang -target " + triplet + " -B llvm-")

def build_gcc_toolchain(buildtools_dir, arch):
    bok(arch + " toolchain build complete!")

### Workflow

umask = os.umask(0)
os.umask(umask)
if umask > 22:
    berror("Your umask is too restrictive (should be <= 0022; is actually " + str(umask) + ")")
    print()
    berror("Additionally, if the source tree was cloned with a too-restrictive umask,")
    berror("you will need to run \"git checkout\" again to fix this.")
    exit(1)

if args["target_arch"] == None:
    berror("You need to specify at least one target architecture via --target-arch")
    exit(1)

if args["use_clang"] == False and args["build_gcc_toolchain"] == None and args["use_gcc_toolchain"] == None:
    berror("You need to pick a toolchain via --build-gcc-toolchain, --use-gcc-toolchain, or --use-clang")
    exit(1)
elif args["use_clang"] == True:
    bok("Using the host's clang toolchain with a haiku target.")
elif args["build_gcc_toolchain"] != None:
    bok("Building a gcc cross-compiler.")
elif args["use_gcc_toolchain"] != None:
    bok("Using the existing gcc toolchain at " + args["use_gcc_toolchain"][0])

mkdir_p(outputDir + "/build")

# Some Defaults
set_build_config("TARGET_PLATFORM", "haiku")
set_build_config("HAIKU_INCLUDE_SOURCES", 0)
set_build_config("HAIKU_USE_GCC_PIPE", 0)
set_build_config("HAIKU_HOST_USE_32BIT", 0)
set_build_config("HAIKU_HOST_USE_XATTR", "")
set_build_config("HAIKU_HOST_USE_XATTR_REF", "")
set_build_config("HAIKU_DISTRO_COMPATIBILITY", args["distro_compatibility"])

setup_bootstrap()
setup_host_tools()
setup_host_compiler()

binfo("Configuring a Haiku build at " + outputDir)

for arch in args["target_arch"]:
    binfo("Configuring " + arch[0] + " architecture...")
    setup_target_compiler(arch[0])

    if args["build_gcc_toolchain"] != None:
        build_gcc_toolchain(args["build_gcc_toolchain"][0], arch[0])

write_build_config(outputDir + "/build/BuildConfig")

# Write out an entry Jamfile in our build directory
with open(outputDir + "/Jamfile", "w") as fh:
    fh.write("# -- WARNING --\n")
    fh.write("# This file was AUTOMATICALLY GENERATED by configure, and will be completely\n")
    fh.write("# overwritten the next time configure is run.\n\n")
    fh.write("HAIKU_TOP           = " + os.path.relpath(sourceDir, outputDir) + " ;\n")
    fh.write("HAIKU_OUTPUT_DIR    = " + os.path.relpath(outputDir, os.getcwd()) + " ;\n\n")
    fh.write("include [ FDirName $(HAIKU_TOP) Jamfile ] ;\n")

bok("Configuration complete!")