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