boot 7.63 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 19 20 21 22 23 24 25 26 27 28 29
args = parser.parse_args()

def print_err(s):
    print(dedent(s), file=sys.stderr)

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

def check_for_url_rewrites():
    if os.path.isdir('.git') and \
       subprocess.check_output('git config remote.origin.url'.split()).find(b'github.com') != -1 and \
       subprocess.call(['git', 'config', '--get-regexp', '^url.*github.com/.*/packages-.insteadOf']) != 0:
30 31
        # If we cloned from github, make sure the url rewrites are set.
        # Otherwise 'git submodule update --init' prints confusing errors.
Ben Gamari's avatar
Ben Gamari committed
32 33 34 35 36
        die("""\
            It seems you cloned this repository from GitHub. But your git config files
            don't contain the url rewrites that are needed to make this work (GitHub
            doesn't support '/' in repository names, so we use a different naming scheme
            for the submodule repositories there).
37

Ben Gamari's avatar
Ben Gamari committed
38
            Please run the following commands first:
39

Ben Gamari's avatar
Ben Gamari committed
40 41 42 43 44
              git config --global url."git://github.com/ghc/packages-".insteadOf     git://github.com/ghc/packages/
              git config --global url."http://github.com/ghc/packages-".insteadOf    http://github.com/ghc/packages/
              git config --global url."https://github.com/ghc/packages-".insteadOf   https://github.com/ghc/packages/
              git config --global url."ssh://git\@github.com/ghc/packages-".insteadOf ssh://git\@github.com/ghc/packages/
              git config --global url."git\@github.com:/ghc/packages-".insteadOf      git\@github.com:/ghc/packages/
45

Ben Gamari's avatar
Ben Gamari committed
46
            And then:
47

Ben Gamari's avatar
Ben Gamari committed
48 49
              git submodule update --init
              ./boot
50

Ben Gamari's avatar
Ben Gamari committed
51
            Or start over, and clone the GHC repository from the haskell server:
52

Ben Gamari's avatar
Ben Gamari committed
53
              git clone --recursive git://git.haskell.org/ghc.git
54

Ben Gamari's avatar
Ben Gamari committed
55 56 57 58
            For more information, see:
              * https://ghc.haskell.org/trac/ghc/wiki/Newcomers or
              * https://ghc.haskell.org/trac/ghc/wiki/Building/GettingTheSources#CloningfromGitHub
        """)
59

Ben Gamari's avatar
Ben Gamari committed
60
def check_boot_packages():
Ian Lynagh's avatar
Ian Lynagh committed
61
    # Check that we have all boot packages.
Ben Gamari's avatar
Ben Gamari committed
62 63 64 65 66
    import re
    for l in open('packages', 'r'):
        if l.startswith('#'):
            continue

67
        parts = [part for part in l.split(' ') if part]
Ben Gamari's avatar
Ben Gamari committed
68 69 70 71 72 73
        if len(parts) != 4:
            die("Error: Bad line in packages file: " + l)

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

74
        # If tag is not "-" then it is an optional repository, so its
Ben Gamari's avatar
Ben Gamari committed
75
        # absence isn't an error.
76
        if tag == '-':
Ben Gamari's avatar
Ben Gamari committed
77 78 79 80 81 82 83
            # 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.
            license_path = os.path.join(dir_, 'LICENSE')
            if not os.path.isfile(license_path):
                die("""\
84
                    Error: %s doesn't exist
Ben Gamari's avatar
Ben Gamari committed
85
                    Maybe you haven't run 'git submodule update --init'?
86
                    """ % license_path)
87

Ian Lynagh's avatar
Ian Lynagh committed
88
# Create libraries/*/{ghc.mk,GNUmakefile}
Ben Gamari's avatar
Ben Gamari committed
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
def boot_pkgs():
    library_dirs = []

    for package in glob.glob("libraries/*/"):
        packages_file = os.path.join(package, 'ghc-packages')
        if os.path.isfile(packages_file):
            for subpkg in open(packages_file, 'r'):
                library_dirs.append(os.path.join(package, subpkg.strip()))
        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
118
                top = os.path.join(*['..'] * len(os.path.normpath(package).split(os.path.sep)))
Ben Gamari's avatar
Ben Gamari committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

                ghc_mk = os.path.join(package, 'ghc.mk')
                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_)))

134 135 136 137 138 139 140 141 142 143 144
                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
145 146 147 148 149 150 151 152

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.
153
        ac_local = os.getenv('ACLOCAL_PATH', '')
Ben Gamari's avatar
Ben Gamari committed
154 155 156 157 158 159 160 161 162 163 164
        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_)
165 166

    # Wait for all child processes to finish.
Ben Gamari's avatar
Ben Gamari committed
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    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:
                http://ghc.haskell.org/trac/ghc/wiki/Building/Using#Buildconfiguration
            """))

check_for_url_rewrites()
193
check_boot_packages()
194 195
if not args.hadrian:
    boot_pkgs()
Ben Gamari's avatar
Ben Gamari committed
196
autoreconf()
197 198
if not args.hadrian:
    check_build_mk()