1# compatibility layer for building with more recent waf versions
2
3import os, shlex, sys
4from waflib import Build, Configure, Node, Utils, Options, Logs, TaskGen
5from waflib import ConfigSet
6from waflib.TaskGen import feature, after
7from waflib.Configure import conf, ConfigurationContext
8
9from waflib.Tools.flex import decide_ext
10
11# This version of flexfun runs in tsk.get_cwd() as opposed to the
12# bld.variant_dir: since input paths adjusted against tsk.get_cwd(), we have to
13# use tsk.get_cwd() for the work directory as well.
14def flexfun(tsk):
15    env = tsk.env
16    bld = tsk.generator.bld
17    def to_list(xx):
18        if isinstance(xx, str):
19            return [xx]
20        return xx
21    tsk.last_cmd = lst = []
22    lst.extend(to_list(env.FLEX))
23    lst.extend(to_list(env.FLEXFLAGS))
24    inputs = [a.path_from(tsk.get_cwd()) for a in tsk.inputs]
25    if env.FLEX_MSYS:
26        inputs = [x.replace(os.sep, '/') for x in inputs]
27    lst.extend(inputs)
28    lst = [x for x in lst if x]
29    txt = bld.cmd_and_log(lst, cwd=tsk.get_cwd(), env=env.env or None, quiet=0)
30    tsk.outputs[0].write(txt.replace('\r\n', '\n').replace('\r', '\n')) # issue #1207
31
32TaskGen.declare_chain(
33    name = 'flex',
34    rule = flexfun, # issue #854
35    ext_in = '.l',
36    decider = decide_ext,
37)
38
39
40for y in (Build.BuildContext, Build.CleanContext, Build.InstallContext, Build.UninstallContext, Build.ListContext):
41    class tmp(y):
42        variant = 'default'
43
44def abspath(self, env=None):
45    if env and hasattr(self, 'children'):
46        return self.get_bld().abspath()
47    return self.old_abspath()
48Node.Node.old_abspath = Node.Node.abspath
49Node.Node.abspath = abspath
50
51def bldpath(self, env=None):
52    return self.abspath()
53    #return self.path_from(self.ctx.bldnode.parent)
54Node.Node.bldpath = bldpath
55
56def srcpath(self, env=None):
57    return self.abspath()
58    #return self.path_from(self.ctx.bldnode.parent)
59Node.Node.srcpath = srcpath
60
61def store_fast(self, filename):
62    file = open(filename, 'wb')
63    data = self.get_merged_dict()
64    try:
65        Build.cPickle.dump(data, file, -1)
66    finally:
67        file.close()
68ConfigSet.ConfigSet.store_fast = store_fast
69
70def load_fast(self, filename):
71    file = open(filename, 'rb')
72    try:
73        data = Build.cPickle.load(file)
74    finally:
75        file.close()
76    self.table.update(data)
77ConfigSet.ConfigSet.load_fast = load_fast
78
79@feature('c', 'cxx', 'd', 'asm', 'fc', 'includes')
80@after('propagate_uselib_vars', 'process_source')
81def apply_incpaths(self):
82    lst = self.to_incnodes(self.to_list(getattr(self, 'includes', [])) + self.env['INCLUDES'])
83    self.includes_nodes = lst
84    cwdx = getattr(self.bld, 'cwdx', self.bld.bldnode)
85    self.env['INCPATHS'] = [x.path_from(cwdx) for x in lst]
86
87@conf
88def define(self, key, val, quote=True, comment=None):
89   assert key and isinstance(key, str)
90
91   if val is None:
92       val = ()
93   elif isinstance(val, bool):
94       val = int(val)
95
96   # waf 1.5
97   self.env[key] = val
98
99   if isinstance(val, int) or isinstance(val, float):
100           s = '%s=%s'
101   else:
102           s = quote and '%s="%s"' or '%s=%s'
103   app = s % (key, str(val))
104
105   ban = key + '='
106   lst = self.env.DEFINES
107   for x in lst:
108           if x.startswith(ban):
109                   lst[lst.index(x)] = app
110                   break
111   else:
112           self.env.append_value('DEFINES', app)
113
114   self.env.append_unique('define_key', key)
115
116# compat15 removes this but we want to keep it
117@conf
118def undefine(self, key, from_env=True, comment=None):
119    assert key and isinstance(key, str)
120
121    ban = key + '='
122    self.env.DEFINES = [x for x in self.env.DEFINES if not x.startswith(ban)]
123    self.env.append_unique('define_key', key)
124    # waf 1.5
125    if from_env:
126        self.env[key] = ()
127
128class ConfigurationContext(Configure.ConfigurationContext):
129    def init_dirs(self):
130        self.setenv('default')
131        self.env.merge_config_header = True
132        return super(ConfigurationContext, self).init_dirs()
133
134def find_program_samba(self, *k, **kw):
135    kw['mandatory'] = False
136    ret = self.find_program_old(*k, **kw)
137    return ret
138Configure.ConfigurationContext.find_program_old = Configure.ConfigurationContext.find_program
139Configure.ConfigurationContext.find_program = find_program_samba
140
141Build.BuildContext.ENFORCE_GROUP_ORDERING = Utils.nada
142Build.BuildContext.AUTOCLEANUP_STALE_FILES = Utils.nada
143
144@conf
145def check(self, *k, **kw):
146    '''Override the waf defaults to inject --with-directory options'''
147
148    # match the configuration test with speficic options, for example:
149    # --with-libiconv -> Options.options.iconv_open -> "Checking for library iconv"
150    self.validate_c(kw)
151
152    additional_dirs = []
153    if 'msg' in kw:
154        msg = kw['msg']
155        for x in Options.OptionsContext.parser.parser.option_list:
156             if getattr(x, 'match', None) and msg in x.match:
157                 d = getattr(Options.options, x.dest, '')
158                 if d:
159                     additional_dirs.append(d)
160
161    # we add the additional dirs twice: once for the test data, and again if the compilation test suceeds below
162    def add_options_dir(dirs, env):
163        for x in dirs:
164             if not x in env.CPPPATH:
165                 env.CPPPATH = [os.path.join(x, 'include')] + env.CPPPATH
166             if not x in env.LIBPATH:
167                 env.LIBPATH = [os.path.join(x, 'lib')] + env.LIBPATH
168
169    add_options_dir(additional_dirs, kw['env'])
170
171    self.start_msg(kw['msg'], **kw)
172    ret = None
173    try:
174        ret = self.run_build(*k, **kw)
175    except self.errors.ConfigurationError:
176        self.end_msg(kw['errmsg'], 'YELLOW', **kw)
177        if Logs.verbose > 1:
178            raise
179        else:
180            self.fatal('The configuration failed')
181    else:
182        kw['success'] = ret
183        # success! time for brandy
184        add_options_dir(additional_dirs, self.env)
185
186    ret = self.post_check(*k, **kw)
187    if not ret:
188        self.end_msg(kw['errmsg'], 'YELLOW', **kw)
189        self.fatal('The configuration failed %r' % ret)
190    else:
191        self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
192    return ret
193
194@conf
195def CHECK_LIBRARY_SUPPORT(conf, rpath=False, version_script=False, msg=None):
196    '''see if the platform supports building libraries'''
197
198    if msg is None:
199        if rpath:
200            msg = "rpath library support"
201        else:
202            msg = "building library support"
203
204    def build(bld):
205        lib_node = bld.srcnode.make_node('libdir/liblc1.c')
206        lib_node.parent.mkdir()
207        lib_node.write('int lib_func(void) { return 42; }\n', 'w')
208        main_node = bld.srcnode.make_node('main.c')
209        main_node.write('int main(void) {return !(lib_func() == 42);}', 'w')
210        linkflags = []
211        if version_script:
212            script = bld.srcnode.make_node('ldscript')
213            script.write('TEST_1.0A2 { global: *; };\n', 'w')
214            linkflags.append('-Wl,--version-script=%s' % script.abspath())
215        bld(features='c cshlib', source=lib_node, target='lib1', linkflags=linkflags, name='lib1')
216        o = bld(features='c cprogram', source=main_node, target='prog1', uselib_local='lib1')
217        if rpath:
218            o.rpath = [lib_node.parent.abspath()]
219        def run_app(self):
220             args = conf.SAMBA_CROSS_ARGS(msg=msg)
221             env = dict(os.environ)
222             env['LD_LIBRARY_PATH'] = self.inputs[0].parent.abspath() + os.pathsep + env.get('LD_LIBRARY_PATH', '')
223             self.generator.bld.cmd_and_log([self.inputs[0].abspath()] + args, env=env)
224        o.post()
225        bld(rule=run_app, source=o.link_task.outputs[0])
226
227    # ok, so it builds
228    try:
229        conf.check(build_fun=build, msg='Checking for %s' % msg)
230    except conf.errors.ConfigurationError:
231        return False
232    return True
233
234@conf
235def CHECK_NEED_LC(conf, msg):
236    '''check if we need -lc'''
237    def build(bld):
238        lib_node = bld.srcnode.make_node('libdir/liblc1.c')
239        lib_node.parent.mkdir()
240        lib_node.write('#include <stdio.h>\nint lib_func(void) { FILE *f = fopen("foo", "r");}\n', 'w')
241        bld(features='c cshlib', source=[lib_node], linkflags=conf.env.EXTRA_LDFLAGS, target='liblc')
242    try:
243        conf.check(build_fun=build, msg=msg, okmsg='-lc is unnecessary', errmsg='-lc is necessary')
244    except conf.errors.ConfigurationError:
245        return False
246    return True
247
248# already implemented on "waf -v"
249def order(bld, tgt_list):
250    return True
251Build.BuildContext.check_group_ordering = order
252
253@conf
254def CHECK_CFG(self, *k, **kw):
255    if 'args' in kw:
256        kw['args'] = shlex.split(kw['args'])
257    if not 'mandatory' in kw:
258        kw['mandatory'] = False
259    kw['global_define'] = True
260    return self.check_cfg(*k, **kw)
261
262def cmd_output(cmd, **kw):
263
264    silent = False
265    if 'silent' in kw:
266        silent = kw['silent']
267        del(kw['silent'])
268
269    if 'e' in kw:
270        tmp = kw['e']
271        del(kw['e'])
272        kw['env'] = tmp
273
274    kw['shell'] = isinstance(cmd, str)
275    kw['stdout'] = Utils.subprocess.PIPE
276    if silent:
277        kw['stderr'] = Utils.subprocess.PIPE
278
279    try:
280        p = Utils.subprocess.Popen(cmd, **kw)
281        output = p.communicate()[0]
282    except OSError as e:
283        raise ValueError(str(e))
284
285    if p.returncode:
286        if not silent:
287            msg = "command execution failed: %s -> %r" % (cmd, str(output))
288            raise ValueError(msg)
289        output = ''
290    return output
291Utils.cmd_output = cmd_output
292
293
294@TaskGen.feature('c', 'cxx', 'd')
295@TaskGen.before('apply_incpaths', 'propagate_uselib_vars')
296@TaskGen.after('apply_link', 'process_source')
297def apply_uselib_local(self):
298    """
299    process the uselib_local attribute
300    execute after apply_link because of the execution order set on 'link_task'
301    """
302    env = self.env
303    from waflib.Tools.ccroot import stlink_task
304
305    # 1. the case of the libs defined in the project (visit ancestors first)
306    # the ancestors external libraries (uselib) will be prepended
307    self.uselib = self.to_list(getattr(self, 'uselib', []))
308    self.includes = self.to_list(getattr(self, 'includes', []))
309    names = self.to_list(getattr(self, 'uselib_local', []))
310    get = self.bld.get_tgen_by_name
311    seen = set()
312    seen_uselib = set()
313    tmp = Utils.deque(names) # consume a copy of the list of names
314    if tmp:
315        if Logs.verbose:
316            Logs.warn('compat: "uselib_local" is deprecated, replace by "use"')
317    while tmp:
318        lib_name = tmp.popleft()
319        # visit dependencies only once
320        if lib_name in seen:
321            continue
322
323        y = get(lib_name)
324        y.post()
325        seen.add(lib_name)
326
327        # object has ancestors to process (shared libraries): add them to the end of the list
328        if getattr(y, 'uselib_local', None):
329            for x in self.to_list(getattr(y, 'uselib_local', [])):
330                obj = get(x)
331                obj.post()
332                if getattr(obj, 'link_task', None):
333                    if not isinstance(obj.link_task, stlink_task):
334                        tmp.append(x)
335
336        # link task and flags
337        if getattr(y, 'link_task', None):
338
339            link_name = y.target[y.target.rfind(os.sep) + 1:]
340            if isinstance(y.link_task, stlink_task):
341                env.append_value('STLIB', [link_name])
342            else:
343                # some linkers can link against programs
344                env.append_value('LIB', [link_name])
345
346            # the order
347            self.link_task.set_run_after(y.link_task)
348
349            # for the recompilation
350            self.link_task.dep_nodes += y.link_task.outputs
351
352            # add the link path too
353            tmp_path = y.link_task.outputs[0].parent.bldpath()
354            if not tmp_path in env['LIBPATH']:
355                env.prepend_value('LIBPATH', [tmp_path])
356
357        # add ancestors uselib too - but only propagate those that have no staticlib defined
358        for v in self.to_list(getattr(y, 'uselib', [])):
359            if v not in seen_uselib:
360                seen_uselib.add(v)
361                if not env['STLIB_' + v]:
362                    if not v in self.uselib:
363                        self.uselib.insert(0, v)
364
365        # if the library task generator provides 'export_includes', add to the include path
366        # the export_includes must be a list of paths relative to the other library
367        if getattr(y, 'export_includes', None):
368            self.includes.extend(y.to_incnodes(y.export_includes))
369
370@TaskGen.feature('cprogram', 'cxxprogram', 'cstlib', 'cxxstlib', 'cshlib', 'cxxshlib', 'dprogram', 'dstlib', 'dshlib')
371@TaskGen.after('apply_link')
372def apply_objdeps(self):
373    "add the .o files produced by some other object files in the same manner as uselib_local"
374    names = getattr(self, 'add_objects', [])
375    if not names:
376        return
377    names = self.to_list(names)
378
379    get = self.bld.get_tgen_by_name
380    seen = []
381    while names:
382        x = names[0]
383
384        # visit dependencies only once
385        if x in seen:
386            names = names[1:]
387            continue
388
389        # object does not exist ?
390        y = get(x)
391
392        # object has ancestors to process first ? update the list of names
393        if getattr(y, 'add_objects', None):
394            added = 0
395            lst = y.to_list(y.add_objects)
396            lst.reverse()
397            for u in lst:
398                if u in seen:
399                    continue
400                added = 1
401                names = [u]+names
402            if added:
403                continue # list of names modified, loop
404
405        # safe to process the current object
406        y.post()
407        seen.append(x)
408
409        for t in getattr(y, 'compiled_tasks', []):
410            self.link_task.inputs.extend(t.outputs)
411
412@TaskGen.after('apply_link')
413def process_obj_files(self):
414    if not hasattr(self, 'obj_files'):
415        return
416    for x in self.obj_files:
417        node = self.path.find_resource(x)
418        self.link_task.inputs.append(node)
419
420@TaskGen.taskgen_method
421def add_obj_file(self, file):
422    """Small example on how to link object files as if they were source
423    obj = bld.create_obj('cc')
424    obj.add_obj_file('foo.o')"""
425    if not hasattr(self, 'obj_files'):
426        self.obj_files = []
427    if not 'process_obj_files' in self.meths:
428        self.meths.append('process_obj_files')
429    self.obj_files.append(file)
430