PageRenderTime 56ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/wafadmin/Tools/d.py

https://github.com/ekg/node
Python | 532 lines | 505 code | 13 blank | 14 comment | 35 complexity | 7d061d6984a3868c4a46fe322a700a82 MD5 | raw file
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Carlos Rafael Giani, 2007 (dv)
  4. # Thomas Nagy, 2007-2008 (ita)
  5. import os, sys, re, optparse
  6. import ccroot # <- leave this
  7. import TaskGen, Utils, Task, Configure, Logs, Build
  8. from Logs import debug, error
  9. from TaskGen import taskgen, feature, after, before, extension
  10. from Configure import conftest
  11. EXT_D = ['.d', '.di', '.D']
  12. D_METHS = ['apply_core', 'apply_vnum', 'apply_objdeps'] # additional d methods
  13. def filter_comments(filename):
  14. txt = Utils.readf(filename)
  15. buf = []
  16. i = 0
  17. max = len(txt)
  18. while i < max:
  19. c = txt[i]
  20. # skip a string
  21. if c == '"':
  22. i += 1
  23. c = ''
  24. while i < max:
  25. p = c
  26. c = txt[i]
  27. i += 1
  28. if i == max: return buf
  29. if c == '"':
  30. cnt = 0
  31. while i < cnt and i < max:
  32. #print "cntcnt = ", str(cnt), self.txt[self.i-2-cnt]
  33. if txt[i-2-cnt] == '\\': cnt+=1
  34. else: break
  35. #print "cnt is ", str(cnt)
  36. if (cnt%2)==0: break
  37. # i -= 1 # <- useless in practice
  38. # skip a char
  39. elif c == "'":
  40. i += 1
  41. if i == max: return buf
  42. c = txt[i]
  43. if c == '\\':
  44. i += 1
  45. if i == max: return buf
  46. c = txt[i]
  47. if c == 'x':
  48. i += 2 # skip two chars
  49. elif c == 'u':
  50. i += 4 # skip unicode chars
  51. i += 1
  52. if i == max: return buf
  53. c = txt[i]
  54. if c != '\'': error("uh-oh, invalid character")
  55. # skip a comment
  56. elif c == '/':
  57. if i == max: break
  58. c = txt[i+1]
  59. # eat /+ +/ comments
  60. if c == '+':
  61. i += 1
  62. nesting = 1
  63. prev = 0
  64. while i < max:
  65. c = txt[i]
  66. if c == '+':
  67. prev = 1
  68. elif c == '/':
  69. if prev:
  70. nesting -= 1
  71. if nesting == 0: break
  72. else:
  73. if i < max:
  74. i += 1
  75. c = txt[i]
  76. if c == '+':
  77. nesting += 1
  78. else:
  79. return buf
  80. else:
  81. prev = 0
  82. i += 1
  83. # eat /* */ comments
  84. elif c == '*':
  85. i += 1
  86. while i < max:
  87. c = txt[i]
  88. if c == '*':
  89. prev = 1
  90. elif c == '/':
  91. if prev: break
  92. else:
  93. prev = 0
  94. i += 1
  95. # eat // comments
  96. elif c == '/':
  97. i += 1
  98. c = txt[i]
  99. while i < max and c != '\n':
  100. i += 1
  101. c = txt[i]
  102. # a valid char, add it to the buffer
  103. else:
  104. buf.append(c)
  105. i += 1
  106. return buf
  107. class d_parser(object):
  108. def __init__(self, env, incpaths):
  109. #self.code = ''
  110. #self.module = ''
  111. #self.imports = []
  112. self.allnames = []
  113. self.re_module = re.compile("module\s+([^;]+)")
  114. self.re_import = re.compile("import\s+([^;]+)")
  115. self.re_import_bindings = re.compile("([^:]+):(.*)")
  116. self.re_import_alias = re.compile("[^=]+=(.+)")
  117. self.env = env
  118. self.nodes = []
  119. self.names = []
  120. self.incpaths = incpaths
  121. def tryfind(self, filename):
  122. found = 0
  123. for n in self.incpaths:
  124. found = n.find_resource(filename.replace('.', '/') + '.d')
  125. if found:
  126. self.nodes.append(found)
  127. self.waiting.append(found)
  128. break
  129. if not found:
  130. if not filename in self.names:
  131. self.names.append(filename)
  132. def get_strings(self, code):
  133. #self.imports = []
  134. self.module = ''
  135. lst = []
  136. # get the module name (if present)
  137. mod_name = self.re_module.search(code)
  138. if mod_name:
  139. self.module = re.sub('\s+', '', mod_name.group(1)) # strip all whitespaces
  140. # go through the code, have a look at all import occurrences
  141. # first, lets look at anything beginning with "import" and ending with ";"
  142. import_iterator = self.re_import.finditer(code)
  143. if import_iterator:
  144. for import_match in import_iterator:
  145. import_match_str = re.sub('\s+', '', import_match.group(1)) # strip all whitespaces
  146. # does this end with an import bindings declaration?
  147. # (import bindings always terminate the list of imports)
  148. bindings_match = self.re_import_bindings.match(import_match_str)
  149. if bindings_match:
  150. import_match_str = bindings_match.group(1)
  151. # if so, extract the part before the ":" (since the module declaration(s) is/are located there)
  152. # split the matching string into a bunch of strings, separated by a comma
  153. matches = import_match_str.split(',')
  154. for match in matches:
  155. alias_match = self.re_import_alias.match(match)
  156. if alias_match:
  157. # is this an alias declaration? (alias = module name) if so, extract the module name
  158. match = alias_match.group(1)
  159. lst.append(match)
  160. return lst
  161. def start(self, node):
  162. self.waiting = [node]
  163. # while the stack is not empty, add the dependencies
  164. while self.waiting:
  165. nd = self.waiting.pop(0)
  166. self.iter(nd)
  167. def iter(self, node):
  168. path = node.abspath(self.env) # obtain the absolute path
  169. code = "".join(filter_comments(path)) # read the file and filter the comments
  170. names = self.get_strings(code) # obtain the import strings
  171. for x in names:
  172. # optimization
  173. if x in self.allnames: continue
  174. self.allnames.append(x)
  175. # for each name, see if it is like a node or not
  176. self.tryfind(x)
  177. def scan(self):
  178. "look for .d/.di the .d source need"
  179. env = self.env
  180. gruik = d_parser(env, env['INC_PATHS'])
  181. gruik.start(self.inputs[0])
  182. if Logs.verbose:
  183. debug('deps: nodes found for %s: %s %s' % (str(self.inputs[0]), str(gruik.nodes), str(gruik.names)))
  184. #debug("deps found for %s: %s" % (str(node), str(gruik.deps)), 'deps')
  185. return (gruik.nodes, gruik.names)
  186. def get_target_name(self):
  187. "for d programs and libs"
  188. v = self.env
  189. tp = 'program'
  190. for x in self.features:
  191. if x in ['dshlib', 'dstaticlib']:
  192. tp = x.lstrip('d')
  193. return v['D_%s_PATTERN' % tp] % self.target
  194. d_params = {
  195. 'dflags': '',
  196. 'importpaths':'',
  197. 'libs':'',
  198. 'libpaths':'',
  199. 'generate_headers':False,
  200. }
  201. @feature('d')
  202. @before('apply_type_vars')
  203. def init_d(self):
  204. for x in d_params:
  205. setattr(self, x, getattr(self, x, d_params[x]))
  206. class d_taskgen(TaskGen.task_gen):
  207. def __init__(self, *k, **kw):
  208. TaskGen.task_gen.__init__(self, *k, **kw)
  209. # COMPAT
  210. if len(k) > 1:
  211. self.features.append('d' + k[1])
  212. # okay, we borrow a few methods from ccroot
  213. TaskGen.bind_feature('d', D_METHS)
  214. @feature('d')
  215. @before('apply_d_libs')
  216. def init_d(self):
  217. Utils.def_attrs(self,
  218. dflags='',
  219. importpaths='',
  220. libs='',
  221. libpaths='',
  222. uselib='',
  223. uselib_local='',
  224. generate_headers=False, # set to true if you want .di files as well as .o
  225. compiled_tasks=[],
  226. add_objects=[],
  227. link_task=None)
  228. @feature('d')
  229. @after('apply_d_link')
  230. @before('apply_vnum')
  231. def apply_d_libs(self):
  232. uselib = self.to_list(self.uselib)
  233. seen = []
  234. local_libs = self.to_list(self.uselib_local)
  235. libs = []
  236. libpaths = []
  237. env = self.env
  238. while local_libs:
  239. x = local_libs.pop()
  240. # visit dependencies only once
  241. if x in seen:
  242. continue
  243. else:
  244. seen.append(x)
  245. y = self.name_to_obj(x)
  246. if not y:
  247. raise Utils.WafError('object not found in uselib_local: obj %s uselib %s' % (self.name, x))
  248. # object has ancestors to process first ? update the list of names
  249. if y.uselib_local:
  250. added = 0
  251. lst = y.to_list(y.uselib_local)
  252. lst.reverse()
  253. for u in lst:
  254. if u in seen: continue
  255. added = 1
  256. local_libs = [u]+local_libs
  257. if added: continue # list of names modified, loop
  258. # safe to process the current object
  259. y.post()
  260. seen.append(x)
  261. libname = y.target[y.target.rfind(os.sep) + 1:]
  262. if 'dshlib' in y.features or 'dstaticlib' in y.features:
  263. #libs.append(y.target)
  264. env.append_unique('DLINKFLAGS', env['DLIBPATH_ST'] % y.link_task.outputs[0].parent.bldpath(env))
  265. env.append_unique('DLINKFLAGS', env['DLIB_ST'] % libname)
  266. # add the link path too
  267. tmp_path = y.path.bldpath(env)
  268. if not tmp_path in libpaths: libpaths = [tmp_path] + libpaths
  269. # set the dependency over the link task
  270. if y.link_task is not None:
  271. self.link_task.set_run_after(y.link_task)
  272. dep_nodes = getattr(self.link_task, 'dep_nodes', [])
  273. self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
  274. # add ancestors uselib too
  275. # TODO potential problems with static libraries ?
  276. morelibs = y.to_list(y.uselib)
  277. for v in morelibs:
  278. if v in uselib: continue
  279. uselib = [v]+uselib
  280. self.uselib = uselib
  281. @feature('dprogram', 'dshlib', 'dstaticlib')
  282. @after('apply_core')
  283. def apply_d_link(self):
  284. link = getattr(self, 'link', None)
  285. if not link:
  286. if 'dstaticlib' in self.features: link = 'static_link'
  287. else: link = 'd_link'
  288. linktask = self.create_task(link)
  289. outputs = [t.outputs[0] for t in self.compiled_tasks]
  290. linktask.set_inputs(outputs)
  291. linktask.set_outputs(self.path.find_or_declare(get_target_name(self)))
  292. self.link_task = linktask
  293. @feature('d')
  294. @after('apply_core')
  295. def apply_d_vars(self):
  296. env = self.env
  297. dpath_st = env['DPATH_ST']
  298. lib_st = env['DLIB_ST']
  299. libpath_st = env['DLIBPATH_ST']
  300. #dflags = []
  301. importpaths = self.to_list(self.importpaths)
  302. libpaths = []
  303. libs = []
  304. uselib = self.to_list(self.uselib)
  305. for i in uselib:
  306. if env['DFLAGS_' + i]:
  307. env.append_unique('DFLAGS', env['DFLAGS_' + i])
  308. for x in self.features:
  309. if not x in ['dprogram', 'dstaticlib', 'dshlib']:
  310. continue
  311. x.lstrip('d')
  312. d_shlib_dflags = env['D_' + x + '_DFLAGS']
  313. if d_shlib_dflags:
  314. env.append_unique('DFLAGS', d_shlib_dflags)
  315. # add import paths
  316. for i in uselib:
  317. if env['DPATH_' + i]:
  318. for entry in self.to_list(env['DPATH_' + i]):
  319. if not entry in importpaths:
  320. importpaths.append(entry)
  321. # now process the import paths
  322. for path in importpaths:
  323. if os.path.isabs(path):
  324. env.append_unique('_DIMPORTFLAGS', dpath_st % path)
  325. else:
  326. node = self.path.find_dir(path)
  327. self.env.append_unique('INC_PATHS', node)
  328. env.append_unique('_DIMPORTFLAGS', dpath_st % node.srcpath(env))
  329. env.append_unique('_DIMPORTFLAGS', dpath_st % node.bldpath(env))
  330. # add library paths
  331. for i in uselib:
  332. if env['LIBPATH_' + i]:
  333. for entry in self.to_list(env['LIBPATH_' + i]):
  334. if not entry in libpaths:
  335. libpaths += [entry]
  336. libpaths = self.to_list(self.libpaths) + libpaths
  337. # now process the library paths
  338. for path in libpaths:
  339. env.append_unique('DLINKFLAGS', libpath_st % path)
  340. # add libraries
  341. for i in uselib:
  342. if env['LIB_' + i]:
  343. for entry in self.to_list(env['LIB_' + i]):
  344. if not entry in libs:
  345. libs += [entry]
  346. libs = libs + self.to_list(self.libs)
  347. # now process the libraries
  348. for lib in libs:
  349. env.append_unique('DLINKFLAGS', lib_st % lib)
  350. # add linker flags
  351. for i in uselib:
  352. dlinkflags = env['DLINKFLAGS_' + i]
  353. if dlinkflags:
  354. for linkflag in dlinkflags:
  355. env.append_unique('DLINKFLAGS', linkflag)
  356. @feature('dshlib')
  357. @after('apply_d_vars')
  358. def add_shlib_d_flags(self):
  359. for linkflag in self.env['D_shlib_LINKFLAGS']:
  360. self.env.append_unique('DLINKFLAGS', linkflag)
  361. @extension(EXT_D)
  362. def d_hook(self, node):
  363. # create the compilation task: cpp or cc
  364. task = self.create_task(self.generate_headers and 'd_with_header' or 'd')
  365. try: obj_ext = self.obj_ext
  366. except AttributeError: obj_ext = '_%d.o' % self.idx
  367. task.inputs = [node]
  368. task.outputs = [node.change_ext(obj_ext)]
  369. self.compiled_tasks.append(task)
  370. if self.generate_headers:
  371. header_node = node.change_ext(self.env['DHEADER_ext'])
  372. task.outputs += [header_node]
  373. d_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} ${D_SRC_F}${SRC} ${D_TGT_F}${TGT}'
  374. d_with_header_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} \
  375. ${D_HDR_F}${TGT[1].bldpath(env)} \
  376. ${D_SRC_F}${SRC} \
  377. ${D_TGT_F}${TGT[0].bldpath(env)}'
  378. link_str = '${D_LINKER} ${DLNK_SRC_F}${SRC} ${DLNK_TGT_F}${TGT} ${DLINKFLAGS}'
  379. def override_exec(cls):
  380. """stupid dmd wants -of stuck to the file name"""
  381. old_exec = cls.exec_command
  382. def exec_command(self, *k, **kw):
  383. if isinstance(k[0], list):
  384. lst = k[0]
  385. for i in xrange(len(lst)):
  386. if lst[i] == '-of':
  387. del lst[i]
  388. lst[i] = '-of' + lst[i]
  389. break
  390. return old_exec(self, *k, **kw)
  391. cls.exec_command = exec_command
  392. cls = Task.simple_task_type('d', d_str, 'GREEN', before='static_link d_link', shell=False)
  393. cls.scan = scan
  394. override_exec(cls)
  395. cls = Task.simple_task_type('d_with_header', d_with_header_str, 'GREEN', before='static_link d_link', shell=False)
  396. override_exec(cls)
  397. cls = Task.simple_task_type('d_link', link_str, color='YELLOW', shell=False)
  398. override_exec(cls)
  399. # for feature request #104
  400. @taskgen
  401. def generate_header(self, filename, install_path):
  402. if not hasattr(self, 'header_lst'): self.header_lst = []
  403. self.meths.append('process_header')
  404. self.header_lst.append([filename, install_path])
  405. @before('apply_core')
  406. def process_header(self):
  407. env = self.env
  408. for i in getattr(self, 'header_lst', []):
  409. node = self.path.find_resource(i[0])
  410. if not node:
  411. raise Utils.WafError('file not found on d obj '+i[0])
  412. task = self.create_task('d_header')
  413. task.set_inputs(node)
  414. task.set_outputs(node.change_ext('.di'))
  415. d_header_str = '${D_COMPILER} ${D_HEADER} ${SRC}'
  416. Task.simple_task_type('d_header', d_header_str, color='BLUE', shell=False)
  417. @conftest
  418. def d_platform_flags(conf):
  419. binfmt = conf.env.DEST_BINFMT or Utils.unversioned_sys_platform_to_binary_format(
  420. conf.env.DEST_OS or Utils.unversioned_sys_platform())
  421. if binfmt == 'pe':
  422. v['D_program_PATTERN'] = '%s.exe'
  423. v['D_shlib_PATTERN'] = 'lib%s.dll'
  424. v['D_staticlib_PATTERN'] = 'lib%s.a'
  425. else:
  426. v['D_program_PATTERN'] = '%s'
  427. v['D_shlib_PATTERN'] = 'lib%s.so'
  428. v['D_staticlib_PATTERN'] = 'lib%s.a'
  429. # quick test #
  430. if __name__ == "__main__":
  431. #Logs.verbose = 2
  432. try: arg = sys.argv[1]
  433. except IndexError: arg = "file.d"
  434. print("".join(filter_comments(arg)))
  435. # TODO
  436. paths = ['.']
  437. #gruik = filter()
  438. #gruik.start(arg)
  439. #code = "".join(gruik.buf)
  440. #print "we have found the following code"
  441. #print code
  442. #print "now parsing"
  443. #print "-------------------------------------------"
  444. """
  445. parser_ = d_parser()
  446. parser_.start(arg)
  447. print "module: %s" % parser_.module
  448. print "imports: ",
  449. for imp in parser_.imports:
  450. print imp + " ",
  451. print
  452. """