1#!/bin/env python3
2#
3# Haiku build configuration tool
4# Copyright 2002-2018, Haiku, Inc. All rights reserved.
5#
6
7import argparse
8import os
9import subprocess
10import sys
11import errno
12from pprint import pprint
13
14parser = argparse.ArgumentParser(description='Configure a build of Haiku')
15parser.add_argument('--target-arch', nargs=1,
16    help='Target architectures. First provided is primary.', type=str, action='append',
17    choices=('x86_gcc2', 'x86', 'x86_64', 'ppc', 'm68k', 'arm', 'arm64', 'riscv64'))
18parser.add_argument('--bootstrap', nargs=3,
19    help='Prepare for a bootstrap build. No pre-built packages will be used, instead they will be built from the sources (in several phases).',
20    metavar=('<haikuporter>','<haikuports.cross>', '<haikuports>'))
21parser.add_argument('--build-gcc-toolchain', nargs=1,
22    help='Assume cross compilation. Build a gcc-based toolchain.',
23    metavar=('<buildtools dir>'))
24parser.add_argument('--use-gcc-toolchain', nargs=1,
25    help='Assume cross compilation. Build using an existing gcc-based toolchain.',
26    metavar=('<prefix>'))
27parser.add_argument('--use-clang', default=False, action='store_true', help='Assume native clang build')
28parser.add_argument('--distro-compatibility', nargs=1,
29    help='The distribution\'s level of compatibility with the official Haiku distribution. The generated files will contain the respective trademarks accordingly.',
30    choices=('official', 'compatible', 'default'), default='default')
31args = vars(parser.parse_args())
32
33### Global functions
34
35def mkdir_p(path):
36    try:
37        os.makedirs(path)
38    except OSError as exc:
39        if exc.errno == errno.EEXIST and os.path.isdir(path):
40            pass
41        else:
42            raise
43
44def bok(message):
45    start_color = ""
46    end_color = ""
47    if sys.stdout.isatty():
48        start_color = "\033[92m"
49        end_color = "\033[0m"
50    print(start_color + message + end_color)
51
52def berror(message):
53    start_color = ""
54    end_color = ""
55    if sys.stdout.isatty():
56        start_color = "\033[91m"
57        end_color = "\033[0m"
58    print(start_color + message + end_color)
59
60def binfo(message):
61    start_color = ""
62    end_color = ""
63    if sys.stdout.isatty():
64        start_color = "\033[94m"
65        end_color = "\033[0m"
66    print(start_color + message + end_color)
67
68### Global Varables
69
70(host_sysname, host_nodename, host_release, host_version, host_machine) = os.uname()
71buildConfig = []
72
73# TODO: Remove "../.." if this ever moves to the source root
74sourceDir = os.path.realpath(os.path.dirname(os.path.realpath(__file__)) + "/../..")
75outputDir = os.getcwd()
76
77# If run in our source dir, assume generated
78if outputDir == sourceDir:
79    outputDir = sourceDir + "/generated"
80    mkdir_p(outputDir)
81
82### Helper Functions
83
84# Run a command, collect stdout into a string
85def cmdrun(cmd):
86    return subprocess.check_output(cmd).decode(sys.stdout.encoding)
87
88# Get a config key
89def get_build_config(key):
90    global buildConfig
91    for i in buildConfig:
92        if i["key"] == key:
93            return i["value"]
94    return None
95
96# Delete a config key
97def drop_build_config(key):
98    global buildConfig
99    value = get_build_config(key)
100    if value != None:
101        buildConfig.remove({"key": key, "value": value})
102
103# Set a config key
104def set_build_config(key, value):
105    global buildConfig
106    if get_build_config(key) != None:
107        drop_build_config(key)
108    buildConfig.append({"key": key, "value": value})
109
110def write_build_config(filename):
111    global buildConfig
112    with open(filename, "w") as fh:
113        fh.write("# -- WARNING --\n")
114        fh.write("# This file was AUTOMATICALLY GENERATED by configure, and will be completely\n")
115        fh.write("# overwritten the next time configure is run.\n\n")
116        for i in buildConfig:
117            fh.write(i["key"] + " ?= " + str(i["value"]) + " ;\n")
118
119def triplet_lookup(arch):
120    if arch == "x86_gcc2":
121        return "i586-pc-haiku"
122    elif arch == "x86":
123        return "i586-pc-haiku"
124    elif arch == "x86_64":
125        return "x86_64-unknown-haiku"
126    elif arch == "ppc":
127        return "powerpc-apple-haiku"
128    elif arch == "m68k":
129        return "m68k-unknown-haiku"
130    elif arch == "arm":
131        return "arm-unknown-haiku"
132    elif arch == "riscv64":
133        return "riscv64-unknown-haiku"
134    else:
135        berror("Unsupported target architecture: " + arch)
136        exit(1)
137
138def platform_lookup(sysname):
139    if sysname == "Darwin":
140        return "darwin"
141    elif sysname == "FreeBSD":
142        return "freebsd"
143    elif sysname == "Haiku":
144        return "haiku_host"
145    elif sysname == "Linux":
146        return "linux"
147    elif sysname == "OpenBSD":
148        return "openbsd"
149    elif sysname == "SunOS":
150        return "sunos"
151    else:
152        berror("Unknown platform: " + sysname)
153        exit(1)
154
155def setup_bootstrap():
156    if args["bootstrap"] == None:
157        return
158    set_build_config("HOST_HAIKU_PORTER", os.path.abspath(args["bootstrap"][0]))
159    set_build_config("HAIKU_PORTS", os.path.abspath(args["bootstrap"][1]))
160    set_build_config("HAIKU_PORTS_CROSS", os.path.abspath(args["bootstrap"][2]))
161
162def setup_host_tools():
163    set_build_config("HOST_SHA256", "sha256sum")
164    set_build_config("HOST_EXTENDED_REGEX_SED", "sed -r")
165
166def setup_host_compiler():
167    cc = os.environ.get("CC")
168    if cc == None:
169        # We might want to step through each potential compiler here
170        cc = "gcc"
171    set_build_config("HOST_PLATFORM", platform_lookup(host_sysname))
172    set_build_config("HOST_CC", cc)
173    set_build_config("HOST_CC_LD", cmdrun([cc, "-print-prog-name=ld"]).strip())
174    set_build_config("HOST_CC_OBJCOPY", cmdrun([cc, "-print-prog-name=objcopy"]).strip())
175    set_build_config("HOST_GCC_MACHINE", cmdrun([cc, "-dumpmachine"]).strip())
176    set_build_config("HOST_GCC_RAW_VERSION", cmdrun([cc, "-dumpversion"]).strip())
177
178def setup_target_compiler(arch):
179    cc = get_build_config("HOST_CC")
180    triplet = triplet_lookup(arch)
181    set_build_config("HAIKU_GCC_RAW_VERSION_" + arch, cmdrun([cc, "-dumpversion"]).strip())
182    set_build_config("HAIKU_GCC_MACHINE_" + arch, triplet)
183    set_build_config("HAIKU_CPU_" + arch, arch)
184    if args["use_clang"]:
185        set_build_config("HAIKU_CC_" + arch, "clang -target " + triplet + " -B llvm-")
186
187def build_gcc_toolchain(buildtools_dir, arch):
188    bok(arch + " toolchain build complete!")
189
190### Workflow
191
192umask = os.umask(0)
193os.umask(umask)
194if umask > 22:
195    berror("Your umask is too restrictive (should be <= 0022; is actually " + str(umask) + ")")
196    print()
197    berror("Additionally, if the source tree was cloned with a too-restrictive umask,")
198    berror("you will need to run \"git checkout\" again to fix this.")
199    exit(1)
200
201if args["target_arch"] == None:
202    berror("You need to specify at least one target architecture via --target-arch")
203    exit(1)
204
205if args["use_clang"] == False and args["build_gcc_toolchain"] == None and args["use_gcc_toolchain"] == None:
206    berror("You need to pick a toolchain via --build-gcc-toolchain, --use-gcc-toolchain, or --use-clang")
207    exit(1)
208elif args["use_clang"] == True:
209    bok("Using the host's clang toolchain with a haiku target.")
210elif args["build_gcc_toolchain"] != None:
211    bok("Building a gcc cross-compiler.")
212elif args["use_gcc_toolchain"] != None:
213    bok("Using the existing gcc toolchain at " + args["use_gcc_toolchain"][0])
214
215mkdir_p(outputDir + "/build")
216
217# Some Defaults
218set_build_config("TARGET_PLATFORM", "haiku")
219set_build_config("HAIKU_INCLUDE_SOURCES", 0)
220set_build_config("HAIKU_USE_GCC_PIPE", 0)
221set_build_config("HAIKU_HOST_USE_32BIT", 0)
222set_build_config("HAIKU_HOST_USE_XATTR", "")
223set_build_config("HAIKU_HOST_USE_XATTR_REF", "")
224set_build_config("HAIKU_DISTRO_COMPATIBILITY", args["distro_compatibility"])
225
226setup_bootstrap()
227setup_host_tools()
228setup_host_compiler()
229
230binfo("Configuring a Haiku build at " + outputDir)
231
232for arch in args["target_arch"]:
233    binfo("Configuring " + arch[0] + " architecture...")
234    setup_target_compiler(arch[0])
235
236    if args["build_gcc_toolchain"] != None:
237        build_gcc_toolchain(args["build_gcc_toolchain"][0], arch[0])
238
239write_build_config(outputDir + "/build/BuildConfig")
240
241# Write out an entry Jamfile in our build directory
242with open(outputDir + "/Jamfile", "w") as fh:
243    fh.write("# -- WARNING --\n")
244    fh.write("# This file was AUTOMATICALLY GENERATED by configure, and will be completely\n")
245    fh.write("# overwritten the next time configure is run.\n\n")
246    fh.write("HAIKU_TOP           = " + os.path.relpath(sourceDir, outputDir) + " ;\n")
247    fh.write("HAIKU_OUTPUT_DIR    = " + os.path.relpath(outputDir, os.getcwd()) + " ;\n\n")
248    fh.write("include [ FDirName $(HAIKU_TOP) Jamfile ] ;\n")
249
250bok("Configuration complete!")
251