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: