3 # Carlos Rafael Giani, 2007 (dv)
4 # Thomas Nagy, 2007-2008 (ita)
6 import os, sys, re, optparse
7 import ccroot # <- leave this
8 import TaskGen, Utils, Task, Configure, Logs, Build
9 from Logs import debug, error
10 from TaskGen import taskgen, feature, after, before, extension
11 from Configure import conftest
13 EXT_D = ['.d', '.di', '.D']
14 D_METHS = ['apply_core', 'apply_vnum', 'apply_objdeps'] # additional d methods
25 import tango.stdc.stdio;
40 def filter_comments(filename):
41 txt = Utils.readf(filename)
48 if c == '"' or c == "'": # skip a string or character literal
49 buf.append(txt[begin:i])
55 elif c == '\\': # skip the character following backslash
60 elif c == '/': # try to replace a comment with whitespace
61 buf.append(txt[begin:i])
65 if c == '+': # eat nesting /+ +/ comment
72 if prev == '/' and c == '+':
75 elif prev == '+' and c == '/':
77 if nesting == 0: break
80 elif c == '*': # eat /* */ comment
86 if prev == '*' and c == '/': break
88 elif c == '/': # eat // comment
90 while i < max and txt[i] != '\n':
100 buf.append(txt[begin:])
103 class d_parser(object):
104 def __init__(self, env, incpaths):
111 self.re_module = re.compile("module\s+([^;]+)")
112 self.re_import = re.compile("import\s+([^;]+)")
113 self.re_import_bindings = re.compile("([^:]+):(.*)")
114 self.re_import_alias = re.compile("[^=]+=(.+)")
121 self.incpaths = incpaths
123 def tryfind(self, filename):
125 for n in self.incpaths:
126 found = n.find_resource(filename.replace('.', '/') + '.d')
128 self.nodes.append(found)
129 self.waiting.append(found)
132 if not filename in self.names:
133 self.names.append(filename)
135 def get_strings(self, code):
140 # get the module name (if present)
142 mod_name = self.re_module.search(code)
144 self.module = re.sub('\s+', '', mod_name.group(1)) # strip all whitespaces
146 # go through the code, have a look at all import occurrences
148 # first, lets look at anything beginning with "import" and ending with ";"
149 import_iterator = self.re_import.finditer(code)
151 for import_match in import_iterator:
152 import_match_str = re.sub('\s+', '', import_match.group(1)) # strip all whitespaces
154 # does this end with an import bindings declaration?
155 # (import bindings always terminate the list of imports)
156 bindings_match = self.re_import_bindings.match(import_match_str)
158 import_match_str = bindings_match.group(1)
159 # if so, extract the part before the ":" (since the module declaration(s) is/are located there)
161 # split the matching string into a bunch of strings, separated by a comma
162 matches = import_match_str.split(',')
164 for match in matches:
165 alias_match = self.re_import_alias.match(match)
167 # is this an alias declaration? (alias = module name) if so, extract the module name
168 match = alias_match.group(1)
173 def start(self, node):
174 self.waiting = [node]
175 # while the stack is not empty, add the dependencies
177 nd = self.waiting.pop(0)
180 def iter(self, node):
181 path = node.abspath(self.env) # obtain the absolute path
182 code = "".join(filter_comments(path)) # read the file and filter the comments
183 names = self.get_strings(code) # obtain the import strings
186 if x in self.allnames: continue
187 self.allnames.append(x)
189 # for each name, see if it is like a node or not
193 "look for .d/.di the .d source need"
195 gruik = d_parser(env, env['INC_PATHS'])
196 gruik.start(self.inputs[0])
199 debug('deps: nodes found for %s: %s %s' % (str(self.inputs[0]), str(gruik.nodes), str(gruik.names)))
200 #debug("deps found for %s: %s" % (str(node), str(gruik.deps)), 'deps')
201 return (gruik.nodes, gruik.names)
203 def get_target_name(self):
204 "for d programs and libs"
207 for x in self.features:
208 if x in ['dshlib', 'dstaticlib']:
210 return v['D_%s_PATTERN' % tp] % self.target
217 'generate_headers':False,
221 @before('apply_type_vars')
224 setattr(self, x, getattr(self, x, d_params[x]))
226 class d_taskgen(TaskGen.task_gen):
227 def __init__(self, *k, **kw):
228 TaskGen.task_gen.__init__(self, *k, **kw)
232 self.features.append('d' + k[1])
234 # okay, we borrow a few methods from ccroot
235 TaskGen.bind_feature('d', D_METHS)
238 @before('apply_d_libs')
240 Utils.def_attrs(self,
247 generate_headers=False, # set to true if you want .di files as well as .o
253 @after('apply_d_link', 'init_d')
254 @before('apply_vnum', 'apply_d_vars')
255 def apply_d_libs(self):
256 """after apply_link because of 'link_task'
257 after default_cc because of the attribute 'uselib'"""
260 # 1. the case of the libs defined in the project (visit ancestors first)
261 # the ancestors external libraries (uselib) will be prepended
262 self.uselib = self.to_list(self.uselib)
263 names = self.to_list(self.uselib_local)
266 tmp = Utils.deque(names) # consume a copy of the list of names
268 lib_name = tmp.popleft()
269 # visit dependencies only once
273 y = self.name_to_obj(lib_name)
275 raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name))
279 # object has ancestors to process (shared libraries): add them to the end of the list
280 if getattr(y, 'uselib_local', None):
281 lst = y.to_list(y.uselib_local)
282 if 'dshlib' in y.features or 'dprogram' in y.features:
283 lst = [x for x in lst if not 'dstaticlib' in self.name_to_obj(x).features]
286 # link task and flags
287 if getattr(y, 'link_task', None):
289 link_name = y.target[y.target.rfind(os.sep) + 1:]
290 if 'dstaticlib' in y.features or 'dshlib' in y.features:
291 env.append_unique('DLINKFLAGS', env.DLIB_ST % link_name)
292 env.append_unique('DLINKFLAGS', env.DLIBPATH_ST % y.link_task.outputs[0].parent.bldpath(env))
295 self.link_task.set_run_after(y.link_task)
297 # for the recompilation
298 dep_nodes = getattr(self.link_task, 'dep_nodes', [])
299 self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
301 # add ancestors uselib too - but only propagate those that have no staticlib
302 for v in self.to_list(y.uselib):
303 if not v in self.uselib:
304 self.uselib.insert(0, v)
306 # if the library task generator provides 'export_incdirs', add to the include path
307 # the export_incdirs must be a list of paths relative to the other library
308 if getattr(y, 'export_incdirs', None):
309 for x in self.to_list(y.export_incdirs):
310 node = y.path.find_dir(x)
312 raise Utils.WafError('object %r: invalid folder %r in export_incdirs' % (y.target, x))
313 self.env.append_unique('INC_PATHS', node)
315 @feature('dprogram', 'dshlib', 'dstaticlib')
317 def apply_d_link(self):
318 link = getattr(self, 'link', None)
320 if 'dstaticlib' in self.features: link = 'static_link'
321 else: link = 'd_link'
323 outputs = [t.outputs[0] for t in self.compiled_tasks]
324 self.link_task = self.create_task(link, outputs, self.path.find_or_declare(get_target_name(self)))
328 def apply_d_vars(self):
330 dpath_st = env['DPATH_ST']
331 lib_st = env['DLIB_ST']
332 libpath_st = env['DLIBPATH_ST']
334 importpaths = self.to_list(self.importpaths)
337 uselib = self.to_list(self.uselib)
340 if env['DFLAGS_' + i]:
341 env.append_unique('DFLAGS', env['DFLAGS_' + i])
343 for x in self.features:
344 if not x in ['dprogram', 'dstaticlib', 'dshlib']:
347 d_shlib_dflags = env['D_' + x + '_DFLAGS']
349 env.append_unique('DFLAGS', d_shlib_dflags)
353 if env['DPATH_' + i]:
354 for entry in self.to_list(env['DPATH_' + i]):
355 if not entry in importpaths:
356 importpaths.append(entry)
358 # now process the import paths
359 for path in importpaths:
360 if os.path.isabs(path):
361 env.append_unique('_DIMPORTFLAGS', dpath_st % path)
363 node = self.path.find_dir(path)
364 self.env.append_unique('INC_PATHS', node)
365 env.append_unique('_DIMPORTFLAGS', dpath_st % node.srcpath(env))
366 env.append_unique('_DIMPORTFLAGS', dpath_st % node.bldpath(env))
370 if env['LIBPATH_' + i]:
371 for entry in self.to_list(env['LIBPATH_' + i]):
372 if not entry in libpaths:
373 libpaths.append(entry)
374 libpaths = self.to_list(self.libpaths) + libpaths
376 # now process the library paths
377 # apply same path manipulation as used with import paths
378 for path in libpaths:
379 if not os.path.isabs(path):
380 node = self.path.find_resource(path)
382 raise Utils.WafError('could not find libpath %r from %r' % (path, self))
383 path = node.abspath(self.env)
385 env.append_unique('DLINKFLAGS', libpath_st % path)
390 for entry in self.to_list(env['LIB_' + i]):
391 if not entry in libs:
393 libs.extend(self.to_list(self.libs))
396 for flag in self.to_list(self.dflags):
397 env.append_unique('DFLAGS', flag)
399 # now process the libraries
401 env.append_unique('DLINKFLAGS', lib_st % lib)
405 dlinkflags = env['DLINKFLAGS_' + i]
407 for linkflag in dlinkflags:
408 env.append_unique('DLINKFLAGS', linkflag)
411 @after('apply_d_vars')
412 def add_shlib_d_flags(self):
413 for linkflag in self.env['D_shlib_LINKFLAGS']:
414 self.env.append_unique('DLINKFLAGS', linkflag)
417 def d_hook(self, node):
418 # create the compilation task: cpp or cc
419 task = self.create_task(self.generate_headers and 'd_with_header' or 'd')
420 try: obj_ext = self.obj_ext
421 except AttributeError: obj_ext = '_%d.o' % self.idx
424 task.outputs = [node.change_ext(obj_ext)]
425 self.compiled_tasks.append(task)
427 if self.generate_headers:
428 header_node = node.change_ext(self.env['DHEADER_ext'])
429 task.outputs += [header_node]
431 d_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} ${D_SRC_F}${SRC} ${D_TGT_F}${TGT}'
432 d_with_header_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} \
433 ${D_HDR_F}${TGT[1].bldpath(env)} \
435 ${D_TGT_F}${TGT[0].bldpath(env)}'
436 link_str = '${D_LINKER} ${DLNK_SRC_F}${SRC} ${DLNK_TGT_F}${TGT} ${DLINKFLAGS}'
438 def override_exec(cls):
439 """stupid dmd wants -of stuck to the file name"""
440 old_exec = cls.exec_command
441 def exec_command(self, *k, **kw):
442 if isinstance(k[0], list):
444 for i in xrange(len(lst)):
447 lst[i] = '-of' + lst[i]
449 return old_exec(self, *k, **kw)
450 cls.exec_command = exec_command
452 cls = Task.simple_task_type('d', d_str, 'GREEN', before='static_link d_link', shell=False)
456 cls = Task.simple_task_type('d_with_header', d_with_header_str, 'GREEN', before='static_link d_link', shell=False)
459 cls = Task.simple_task_type('d_link', link_str, color='YELLOW', shell=False)
462 # for feature request #104
464 def generate_header(self, filename, install_path):
465 if not hasattr(self, 'header_lst'): self.header_lst = []
466 self.meths.append('process_header')
467 self.header_lst.append([filename, install_path])
469 @before('apply_core')
470 def process_header(self):
472 for i in getattr(self, 'header_lst', []):
473 node = self.path.find_resource(i[0])
476 raise Utils.WafError('file not found on d obj '+i[0])
478 task = self.create_task('d_header')
479 task.set_inputs(node)
480 task.set_outputs(node.change_ext('.di'))
482 d_header_str = '${D_COMPILER} ${D_HEADER} ${SRC}'
483 Task.simple_task_type('d_header', d_header_str, color='BLUE', shell=False)
486 def d_platform_flags(conf):
488 binfmt = v.DEST_BINFMT or Utils.unversioned_sys_platform_to_binary_format(
489 v.DEST_OS or Utils.unversioned_sys_platform())
491 v['D_program_PATTERN'] = '%s.exe'
492 v['D_shlib_PATTERN'] = 'lib%s.dll'
493 v['D_staticlib_PATTERN'] = 'lib%s.a'
495 v['D_program_PATTERN'] = '%s'
496 v['D_shlib_PATTERN'] = 'lib%s.so'
497 v['D_staticlib_PATTERN'] = 'lib%s.a'
500 def check_dlibrary(conf):
501 ret = conf.check_cc(features='d dprogram', fragment=DLIB, mandatory=True, compile_filename='test.d', execute=True)
502 conf.env.DLIBRARY = ret.strip()
505 if __name__ == "__main__":
508 try: arg = sys.argv[1]
509 except IndexError: arg = "file.d"
511 print("".join(filter_comments(arg)))
518 #code = "".join(gruik.buf)
520 #print "we have found the following code"
524 #print "-------------------------------------------"
529 print "module: %s" % parser_.module
531 for imp in parser_.imports: