boot 6.2 KB
Newer Older
Ben Gamari's avatar
Ben Gamari committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#!/usr/bin/env python3

import glob
import os
import os.path
import sys
import argparse
from textwrap import dedent
import subprocess
import re

cwd = os.getcwd()

parser = argparse.ArgumentParser()
parser.add_argument('--validate', action='store_true', help='Run in validate mode')
16
parser.add_argument('--hadrian', action='store_true', help='Do not assume the make base build system')
Ben Gamari's avatar
Ben Gamari committed
17 18
args = parser.parse_args()

19 20 21 22 23
# Packages whose libraries aren't in the submodule root
EXCEPTIONS = {
    'libraries/containers/': 'libraries/containers/containers/'
}

Ben Gamari's avatar
Ben Gamari committed
24 25 26 27 28 29 30 31
def print_err(s):
    print(dedent(s), file=sys.stderr)

def die(mesg):
    print_err(mesg)
    sys.exit(1)

def check_boot_packages():
Ian Lynagh's avatar
Ian Lynagh committed
32
    # Check that we have all boot packages.
Ben Gamari's avatar
Ben Gamari committed
33 34 35 36
    for l in open('packages', 'r'):
        if l.startswith('#'):
            continue

37
        parts = [part for part in l.split(' ') if part]
Ben Gamari's avatar
Ben Gamari committed
38 39 40 41 42 43
        if len(parts) != 4:
            die("Error: Bad line in packages file: " + l)

        dir_ = parts[0]
        tag = parts[1]

44
        # If tag is not "-" then it is an optional repository, so its
Ben Gamari's avatar
Ben Gamari committed
45
        # absence isn't an error.
46
        if tag == '-':
Ben Gamari's avatar
Ben Gamari committed
47 48 49 50
            # We would like to just check for a .git directory here,
            # but in an lndir tree we avoid making .git directories,
            # so it doesn't exist. We therefore require that every repo
            # has a LICENSE file instead.
51
            license_path = os.path.join(EXCEPTIONS.get(dir_+'/', dir_), 'LICENSE')
Ben Gamari's avatar
Ben Gamari committed
52 53
            if not os.path.isfile(license_path):
                die("""\
54
                    Error: %s doesn't exist
Ben Gamari's avatar
Ben Gamari committed
55
                    Maybe you haven't run 'git submodule update --init'?
56
                    """ % license_path)
57

Ian Lynagh's avatar
Ian Lynagh committed
58
# Create libraries/*/{ghc.mk,GNUmakefile}
Ben Gamari's avatar
Ben Gamari committed
59 60 61 62 63
def boot_pkgs():
    library_dirs = []

    for package in glob.glob("libraries/*/"):
        packages_file = os.path.join(package, 'ghc-packages')
64
        print(package)
Ben Gamari's avatar
Ben Gamari committed
65 66 67
        if os.path.isfile(packages_file):
            for subpkg in open(packages_file, 'r'):
                library_dirs.append(os.path.join(package, subpkg.strip()))
68 69
        elif package in EXCEPTIONS:
            library_dirs.append(EXCEPTIONS[package])
Ben Gamari's avatar
Ben Gamari committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
        else:
            library_dirs.append(package)

    for package in library_dirs:
        if package[-1] == '/':
            # drop trailing '/'
            package = package[:-1]

        dir_ = os.path.relpath(package, 'libraries')
        cabals = glob.glob(os.path.join(package, '*.cabal.in'))
        if len(cabals) == 0:
            cabals = glob.glob(os.path.join(package, '*.cabal'))

        if len(cabals) > 1:
            die('Too many .cabal files in %s' % package)
        elif len(cabals) == 1:
            cabal = cabals[0]

            if os.path.isfile(cabal):
                # strip both .cabal and .in
                pkg = os.path.splitext(os.path.splitext(os.path.basename(cabal))[0])[0]
Ben Gamari's avatar
Ben Gamari committed
91
                top = os.path.join(*['..'] * len(os.path.normpath(package).split(os.path.sep)))
Ben Gamari's avatar
Ben Gamari committed
92 93

                ghc_mk = os.path.join(package, 'ghc.mk')
94 95 96
                if os.path.exists(ghc_mk):
                    print('Skipping %s which already exists' % ghc_mk)
                    continue
Ben Gamari's avatar
Ben Gamari committed
97 98 99 100 101 102 103 104 105 106 107 108 109
                print('Creating %s' % ghc_mk)
                with open(ghc_mk, 'w') as f:
                    f.write(dedent(
                        """\
                        {package}_PACKAGE = {pkg}
                        {package}_dist-install_GROUP = libraries
                        $(if $(filter {dir},$(PACKAGES_STAGE0)),$(eval $(call build-package,{package},dist-boot,0)))
                        $(if $(filter {dir},$(PACKAGES_STAGE1)),$(eval $(call build-package,{package},dist-install,1)))
                        $(if $(filter {dir},$(PACKAGES_STAGE2)),$(eval $(call build-package,{package},dist-install,2)))
                        """.format(package = package,
                                pkg = pkg,
                                dir = dir_)))

110 111 112 113 114 115 116 117 118 119 120
                makefile = os.path.join(package, 'GNUmakefile')
                with open(makefile, 'w') as f:
                    f.write(dedent(
                        """\
                        dir = {package}
                        TOP = {top}
                        include $(TOP)/mk/sub-makefile.mk
                        FAST_MAKE_OPTS += stage=0
                        """.format(package = package, top = top)
                    ))

Ben Gamari's avatar
Ben Gamari committed
121 122 123 124 125 126 127 128

def autoreconf():
    # Run autoreconf on everything that needs it.
    processes = {}
    if os.name == 'nt':
        # Get the normalized ACLOCAL_PATH for Windows
        # This is necessary since on Windows this will be a Windows
        # path, which autoreconf doesn't know doesn't know how to handle.
129
        ac_local = os.getenv('ACLOCAL_PATH', '')
Ben Gamari's avatar
Ben Gamari committed
130 131 132 133 134 135 136 137 138 139 140
        ac_local_arg = re.sub(r';', r':', ac_local)
        ac_local_arg = re.sub(r'\\', r'/', ac_local_arg)
        ac_local_arg = re.sub(r'(\w):/', r'/\1/', ac_local_arg)
        reconf_cmd = 'ACLOCAL_PATH=%s autoreconf' % ac_local_arg
    else:
        reconf_cmd = 'autoreconf'

    for dir_ in ['.'] + glob.glob('libraries/*/'):
        if os.path.isfile(os.path.join(dir_, 'configure.ac')):
            print("Booting %s" % dir_)
            processes[dir_] = subprocess.Popen(['sh', '-c', reconf_cmd], cwd=dir_)
141 142

    # Wait for all child processes to finish.
Ben Gamari's avatar
Ben Gamari committed
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    fail = False
    for k,v in processes.items():
        code = v.wait()
        if code != 0:
            print_err('autoreconf in %s failed with exit code %d' % (k, code))
            fail = True

    if fail:
        sys.exit(1)

def check_build_mk():
    if not args.validate and not os.path.isfile("mk/build.mk"):
        print(dedent(
            """
            WARNING: You don't have a mk/build.mk file.

            By default a standard GHC build will be done, which uses optimisation
            and builds the profiling libraries. This will take a long time, so may
            not be what you want if you are developing GHC or the libraries, rather
            than simply building it to use it.

            For information on creating a mk/build.mk file, please see:
165
                https://gitlab.haskell.org/ghc/ghc/wikis/building/using#build-configuration
Ben Gamari's avatar
Ben Gamari committed
166 167
            """))

168
check_boot_packages()
169 170
if not args.hadrian:
    boot_pkgs()
Ben Gamari's avatar
Ben Gamari committed
171
autoreconf()
172 173
if not args.hadrian:
    check_build_mk()