1##########################################################################
2# Copyright (c) 2009, 2010, 2011, ETH Zurich.
3# All rights reserved.
4#
5# This file is distributed under the terms in the attached LICENSE file.
6# If you do not find this file, copies can be found by writing to:
7# ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8##########################################################################
9
10import os, errno, re
11import siteconfig
12import debug
13
14MPSS_LINUX_PATH=':/opt/mpss/3.7.1/sysroots/x86_64-mpsssdk-linux/usr/bin:/opt/mpss/3.7.1/sysroots/x86_64-mpsssdk-linux/usr/bin/k1om-mpss-linux'
15
16
17class Build(object):
18    name = None # should be overriden by a subclass
19
20    def __init__(self, options):
21        self.build_dir = None
22        self.options = options
23
24    def _make_build_dir(self, build_dir=None):
25        if build_dir is None:
26            build_dir = os.path.join(self.options.buildbase, self.name.lower())
27        self.build_dir = build_dir
28        debug.verbose('creating build directory %s' % build_dir)
29        try:
30            os.makedirs(build_dir)
31        except OSError, e:
32            if e.errno == errno.EEXIST:
33                debug.log("reusing existing build in directory %s" % build_dir)
34            else:
35                raise
36
37    def configure(self, checkout):
38        raise NotImplementedError
39
40    def build(self, targets):
41        raise NotImplementedError
42
43    def install(self, targets, path):
44        """install to the given path"""
45        raise NotImplementedError
46
47
48class HakeBuildBase(Build):
49    def _run_hake(self, srcdir, archs):
50        # if srcdir is relative, adjust to be wrt build_dir
51        print archs
52        if not os.path.isabs(srcdir):
53            srcdir = os.path.relpath(srcdir, self.build_dir)
54        debug.checkcmd([os.path.join(srcdir, "hake", "hake.sh"), "--source-dir", srcdir],
55                       cwd=self.build_dir)
56
57    def _get_hake_conf(self, srcdir, archs):
58        default_config = {
59            "source_dir": "\"%s\"" % srcdir,
60            "architectures": "[" + ", ".join("\"%s\"" % a for a in archs) + "]",
61            "install_dir": "\".\"",
62            "toolroot": "Nothing",
63            "arm_toolspec": "Nothing",
64            "aarch64_toolspec": "Nothing",
65            "thumb_toolspec": "Nothing",
66            "armeb_toolspec": "Nothing",
67            "x86_toolspec": "Nothing",
68            "k1om_toolspec": "Nothing",
69            "cache_dir": "\"%s\"" % os.path.expanduser("~/.cache/barrelfish/"),
70            "hagfish_location" : "\"%s\"" % siteconfig.get('HAGFISH_LOCATION')
71        }
72        return default_config
73
74    def _write_hake_conf(self, srcdir, archs):
75        # create hake dir
76        hakedir = os.path.join(self.build_dir, 'hake')
77        if not os.path.isdir(hakedir):
78            os.mkdir(hakedir)
79
80        # read default config template
81        with open(os.path.join(srcdir, 'hake', 'Config.hs.template')) as fh:
82            conf_template = fh.readlines()
83
84        # if srcdir is relative, adjust to be wrt build_dir
85        if os.path.isabs(srcdir):
86            rel_srcdir = srcdir
87        else:
88            rel_srcdir = os.path.relpath(srcdir, self.build_dir)
89
90        # get custom configuration options as a dictionary
91        conf = self._get_hake_conf(rel_srcdir, archs)
92
93        # create a new config file: template and then local options
94        newconf = []
95        for line in conf_template:
96            # XXX: exclude options from the defaults that are set locally
97            # where is the haskell parsing library for python? :)
98            if any([line.startswith(k) and re.match(' +=', line[len(k):])
99                        for k in conf.keys()]):
100                line = '-- ' + line
101            newconf.append(line)
102        newconf.extend(['\n', '\n', '-- Added by test harness:\n'])
103        for item in conf.items():
104            newconf.append("%s = %s\n" % item)
105
106        # write it, only if it's different or the old one doesn't exist
107        try:
108            with open(os.path.join(hakedir, 'Config.hs'), 'r') as fh:
109                if fh.readlines() == newconf:
110                    return # identical files
111        except IOError:
112            pass
113
114        with open(os.path.join(hakedir, 'Config.hs'), 'w') as fh:
115            fh.writelines(newconf)
116
117    def configure(self, checkout, archs):
118        srcdir = checkout.get_base_dir()
119        environ = dict(os.environ)
120        if "k1om" in archs :
121            environ['PATH'] = environ['PATH'] + MPSS_LINUX_PATH
122        self._make_build_dir()
123        self._write_hake_conf(srcdir, archs)
124        self._run_hake(srcdir, archs)
125
126        # this should be a nop -- building it here causes us to stop early
127        # with any tool or dependency-generation errors before doing test setup
128        self.build(["Makefile"], env=environ)
129
130    @staticmethod
131    def split_env(e):
132        def split_reduce_env(state, c):
133            if not state[0] and c == '\\':
134                return True, state[1]
135            elif not state[0] and c.isspace():
136                state[1].append('')
137            elif state[0]:
138                ec = '\\'+c
139                s = ec.decode('string_escape')
140                if s == ec:
141                    # decode had no effect, just drop backslash
142                    s = c
143                state[1][-1] += s
144            else:
145                state[1][-1] += c
146            return False, state[1]
147
148        e = e.lstrip()
149        e = reduce(split_reduce_env, e, (False, ['']))[1]
150        e = filter(bool, e)
151        return e
152
153    def build(self, targets, **kwargs):
154        makeopts = self.split_env(os.environ.get('MAKEOPTS', ''))
155        debug.checkcmd(["make"] + makeopts + targets, cwd=self.build_dir, **kwargs)
156
157    def install(self, targets, path):
158        debug.checkcmd(["make", "install",
159                               "INSTALL_PREFIX=%s" % path,
160                               "MODULES=%s" % (" ".join(targets))],
161                       cwd=self.build_dir)
162
163
164class HakeReleaseBuild(HakeBuildBase):
165    """Release build (optimisations, no debug information)"""
166    name = 'release'
167
168    def _get_hake_conf(self, *args):
169        conf = super(HakeReleaseBuild, self)._get_hake_conf(*args)
170        conf["cOptFlags"] = "[\"-O2\", \"-DNDEBUG\", \"-Wno-unused-variable\"]"
171        return conf
172
173class HakeTestBuild(HakeBuildBase):
174    """Test build (optimisations, no debug symbols, but assertions enabled)"""
175    name = 'test'
176
177    def _get_hake_conf(self, *args):
178        conf = super(HakeTestBuild, self)._get_hake_conf(*args)
179        conf["cOptFlags"] = "[\"-O2\"]"
180        return conf
181
182class HakeReleaseTraceBuild(HakeBuildBase):
183    """optimisations, no debug information, and tracing """
184    name = 'release_trace'
185
186    def _get_hake_conf(self, *args):
187        conf = super(HakeReleaseBuild, self)._get_hake_conf(*args)
188        conf["cOptFlags"] = "[\"-O2\", \"-DNDEBUG\"]"
189        conf["trace"] = "True"
190        return conf
191
192class HakeTestMdbInvariantsBuild(HakeTestBuild):
193    """optimisations, no debug symbols, assertions and MDB invariant checking enabled"""
194    name = 'test_mdbinvariants'
195
196    def _get_hake_conf(self, *args):
197        conf = super(HakeTestMdbInvariantsBuild, self)._get_hake_conf(*args)
198        conf["mdb_check_invariants"] = "True"
199        return conf
200
201class HakeDebugBuild(HakeBuildBase):
202    """Default Hake build: debug symbols, optimisations, assertions"""
203    name = 'debug'
204
205    def _get_hake_conf(self, *args):
206        conf = super(HakeDebugBuild, self)._get_hake_conf(*args)
207        conf["cOptFlags"] = "[\"-O2\", \"-g\"]"
208        return conf
209
210class HakeDebugTraceBuild(HakeBuildBase):
211    """debug symbols, optimisations, assertions, and tracing"""
212    name = 'debug_trace'
213
214    def _get_hake_conf(self, *args):
215        conf = super(HakeDebugTraceBuild, self)._get_hake_conf(*args)
216        conf["cOptFlags"] = "[\"-O2\"]"
217        conf["trace"] = "True"
218        return conf
219
220
221all_builds = [HakeReleaseBuild, HakeTestBuild, HakeDebugBuild, HakeReleaseTraceBuild,
222              HakeTestMdbInvariantsBuild, HakeDebugTraceBuild]
223
224
225class ExistingBuild(HakeBuildBase):
226    '''Dummy build class for an existing Hake build dir.'''
227    name = 'existing'
228
229    def __init__(self, options, build_dir):
230        super(ExistingBuild, self).__init__(options)
231        debug.verbose('using existing build directory %s' % build_dir)
232        self.build_dir = build_dir
233
234    def configure(self, *args):
235        pass
236
237
238def existingbuild(*args):
239    """construct the build class for an existing build"""
240    return ExistingBuild(*args)
241