Include waf as an extracted source directory, rather than as a one-in-a-file script.
[ira/wip.git] / buildtools / wafadmin / Tools / d.py
diff --git a/buildtools/wafadmin/Tools/d.py b/buildtools/wafadmin/Tools/d.py
new file mode 100644 (file)
index 0000000..1a22821
--- /dev/null
@@ -0,0 +1,535 @@
+#!/usr/bin/env python
+# encoding: utf-8
+# Carlos Rafael Giani, 2007 (dv)
+# Thomas Nagy, 2007-2008 (ita)
+
+import os, sys, re, optparse
+import ccroot # <- leave this
+import TaskGen, Utils, Task, Configure, Logs, Build
+from Logs import debug, error
+from TaskGen import taskgen, feature, after, before, extension
+from Configure import conftest
+
+EXT_D = ['.d', '.di', '.D']
+D_METHS = ['apply_core', 'apply_vnum', 'apply_objdeps'] # additional d methods
+
+DLIB = """
+version(D_Version2) {
+       import std.stdio;
+       int main() {
+               writefln("phobos2");
+               return 0;
+       }
+} else {
+       version(Tango) {
+               import tango.stdc.stdio;
+               int main() {
+                       printf("tango");
+                       return 0;
+               }
+       } else {
+               import std.stdio;
+               int main() {
+                       writefln("phobos1");
+                       return 0;
+               }
+       }
+}
+"""
+
+def filter_comments(filename):
+       txt = Utils.readf(filename)
+       i = 0
+       buf = []
+       max = len(txt)
+       begin = 0
+       while i < max:
+               c = txt[i]
+               if c == '"' or c == "'":  # skip a string or character literal
+                       buf.append(txt[begin:i])
+                       delim = c
+                       i += 1
+                       while i < max:
+                               c = txt[i]
+                               if c == delim: break
+                               elif c == '\\':  # skip the character following backslash
+                                       i += 1
+                               i += 1
+                       i += 1
+                       begin = i
+               elif c == '/':  # try to replace a comment with whitespace
+                       buf.append(txt[begin:i])
+                       i += 1
+                       if i == max: break
+                       c = txt[i]
+                       if c == '+':  # eat nesting /+ +/ comment
+                               i += 1
+                               nesting = 1
+                               c = None
+                               while i < max:
+                                       prev = c
+                                       c = txt[i]
+                                       if prev == '/' and c == '+':
+                                               nesting += 1
+                                               c = None
+                                       elif prev == '+' and c == '/':
+                                               nesting -= 1
+                                               if nesting == 0: break
+                                               c = None
+                                       i += 1
+                       elif c == '*':  # eat /* */ comment
+                               i += 1
+                               c = None
+                               while i < max:
+                                       prev = c
+                                       c = txt[i]
+                                       if prev == '*' and c == '/': break
+                                       i += 1
+                       elif c == '/':  # eat // comment
+                               i += 1
+                               while i < max and txt[i] != '\n':
+                                       i += 1
+                       else:  # no comment
+                               begin = i - 1
+                               continue
+                       i += 1
+                       begin = i
+                       buf.append(' ')
+               else:
+                       i += 1
+       buf.append(txt[begin:])
+       return buf
+
+class d_parser(object):
+       def __init__(self, env, incpaths):
+               #self.code = ''
+               #self.module = ''
+               #self.imports = []
+
+               self.allnames = []
+
+               self.re_module = re.compile("module\s+([^;]+)")
+               self.re_import = re.compile("import\s+([^;]+)")
+               self.re_import_bindings = re.compile("([^:]+):(.*)")
+               self.re_import_alias = re.compile("[^=]+=(.+)")
+
+               self.env = env
+
+               self.nodes = []
+               self.names = []
+
+               self.incpaths = incpaths
+
+       def tryfind(self, filename):
+               found = 0
+               for n in self.incpaths:
+                       found = n.find_resource(filename.replace('.', '/') + '.d')
+                       if found:
+                               self.nodes.append(found)
+                               self.waiting.append(found)
+                               break
+               if not found:
+                       if not filename in self.names:
+                               self.names.append(filename)
+
+       def get_strings(self, code):
+               #self.imports = []
+               self.module = ''
+               lst = []
+
+               # get the module name (if present)
+
+               mod_name = self.re_module.search(code)
+               if mod_name:
+                       self.module = re.sub('\s+', '', mod_name.group(1)) # strip all whitespaces
+
+               # go through the code, have a look at all import occurrences
+
+               # first, lets look at anything beginning with "import" and ending with ";"
+               import_iterator = self.re_import.finditer(code)
+               if import_iterator:
+                       for import_match in import_iterator:
+                               import_match_str = re.sub('\s+', '', import_match.group(1)) # strip all whitespaces
+
+                               # does this end with an import bindings declaration?
+                               # (import bindings always terminate the list of imports)
+                               bindings_match = self.re_import_bindings.match(import_match_str)
+                               if bindings_match:
+                                       import_match_str = bindings_match.group(1)
+                                       # if so, extract the part before the ":" (since the module declaration(s) is/are located there)
+
+                               # split the matching string into a bunch of strings, separated by a comma
+                               matches = import_match_str.split(',')
+
+                               for match in matches:
+                                       alias_match = self.re_import_alias.match(match)
+                                       if alias_match:
+                                               # is this an alias declaration? (alias = module name) if so, extract the module name
+                                               match = alias_match.group(1)
+
+                                       lst.append(match)
+               return lst
+
+       def start(self, node):
+               self.waiting = [node]
+               # while the stack is not empty, add the dependencies
+               while self.waiting:
+                       nd = self.waiting.pop(0)
+                       self.iter(nd)
+
+       def iter(self, node):
+               path = node.abspath(self.env) # obtain the absolute path
+               code = "".join(filter_comments(path)) # read the file and filter the comments
+               names = self.get_strings(code) # obtain the import strings
+               for x in names:
+                       # optimization
+                       if x in self.allnames: continue
+                       self.allnames.append(x)
+
+                       # for each name, see if it is like a node or not
+                       self.tryfind(x)
+
+def scan(self):
+       "look for .d/.di the .d source need"
+       env = self.env
+       gruik = d_parser(env, env['INC_PATHS'])
+       gruik.start(self.inputs[0])
+
+       if Logs.verbose:
+               debug('deps: nodes found for %s: %s %s' % (str(self.inputs[0]), str(gruik.nodes), str(gruik.names)))
+               #debug("deps found for %s: %s" % (str(node), str(gruik.deps)), 'deps')
+       return (gruik.nodes, gruik.names)
+
+def get_target_name(self):
+       "for d programs and libs"
+       v = self.env
+       tp = 'program'
+       for x in self.features:
+               if x in ['dshlib', 'dstaticlib']:
+                       tp = x.lstrip('d')
+       return v['D_%s_PATTERN' % tp] % self.target
+
+d_params = {
+'dflags': '',
+'importpaths':'',
+'libs':'',
+'libpaths':'',
+'generate_headers':False,
+}
+
+@feature('d')
+@before('apply_type_vars')
+def init_d(self):
+       for x in d_params:
+               setattr(self, x, getattr(self, x, d_params[x]))
+
+class d_taskgen(TaskGen.task_gen):
+       def __init__(self, *k, **kw):
+               TaskGen.task_gen.__init__(self, *k, **kw)
+
+               # COMPAT
+               if len(k) > 1:
+                       self.features.append('d' + k[1])
+
+# okay, we borrow a few methods from ccroot
+TaskGen.bind_feature('d', D_METHS)
+
+@feature('d')
+@before('apply_d_libs')
+def init_d(self):
+       Utils.def_attrs(self,
+               dflags='',
+               importpaths='',
+               libs='',
+               libpaths='',
+               uselib='',
+               uselib_local='',
+               generate_headers=False, # set to true if you want .di files as well as .o
+               compiled_tasks=[],
+               add_objects=[],
+               link_task=None)
+
+@feature('d')
+@after('apply_d_link', 'init_d')
+@before('apply_vnum', 'apply_d_vars')
+def apply_d_libs(self):
+       """after apply_link because of 'link_task'
+       after default_cc because of the attribute 'uselib'"""
+       env = self.env
+
+       # 1. the case of the libs defined in the project (visit ancestors first)
+       # the ancestors external libraries (uselib) will be prepended
+       self.uselib = self.to_list(self.uselib)
+       names = self.to_list(self.uselib_local)
+
+       seen = set([])
+       tmp = Utils.deque(names) # consume a copy of the list of names
+       while tmp:
+               lib_name = tmp.popleft()
+               # visit dependencies only once
+               if lib_name in seen:
+                       continue
+
+               y = self.name_to_obj(lib_name)
+               if not y:
+                       raise Utils.WafError('object %r was not found in uselib_local (required by %r)' % (lib_name, self.name))
+               y.post()
+               seen.add(lib_name)
+
+               # object has ancestors to process (shared libraries): add them to the end of the list
+               if getattr(y, 'uselib_local', None):
+                       lst = y.to_list(y.uselib_local)
+                       if 'dshlib' in y.features or 'dprogram' in y.features:
+                               lst = [x for x in lst if not 'dstaticlib' in self.name_to_obj(x).features]
+                       tmp.extend(lst)
+
+               # link task and flags
+               if getattr(y, 'link_task', None):
+
+                       link_name = y.target[y.target.rfind(os.sep) + 1:]
+                       if 'dstaticlib' in y.features or 'dshlib' in y.features:
+                               env.append_unique('DLINKFLAGS', env.DLIB_ST % link_name)
+                               env.append_unique('DLINKFLAGS', env.DLIBPATH_ST % y.link_task.outputs[0].parent.bldpath(env))
+
+                       # the order
+                       self.link_task.set_run_after(y.link_task)
+
+                       # for the recompilation
+                       dep_nodes = getattr(self.link_task, 'dep_nodes', [])
+                       self.link_task.dep_nodes = dep_nodes + y.link_task.outputs
+
+               # add ancestors uselib too - but only propagate those that have no staticlib
+               for v in self.to_list(y.uselib):
+                       if not v in self.uselib:
+                               self.uselib.insert(0, v)
+
+               # if the library task generator provides 'export_incdirs', add to the include path
+               # the export_incdirs must be a list of paths relative to the other library
+               if getattr(y, 'export_incdirs', None):
+                       for x in self.to_list(y.export_incdirs):
+                               node = y.path.find_dir(x)
+                               if not node:
+                                       raise Utils.WafError('object %r: invalid folder %r in export_incdirs' % (y.target, x))
+                               self.env.append_unique('INC_PATHS', node)
+
+@feature('dprogram', 'dshlib', 'dstaticlib')
+@after('apply_core')
+def apply_d_link(self):
+       link = getattr(self, 'link', None)
+       if not link:
+               if 'dstaticlib' in self.features: link = 'static_link'
+               else: link = 'd_link'
+
+       outputs = [t.outputs[0] for t in self.compiled_tasks]
+       self.link_task = self.create_task(link, outputs, self.path.find_or_declare(get_target_name(self)))
+
+@feature('d')
+@after('apply_core')
+def apply_d_vars(self):
+       env = self.env
+       dpath_st   = env['DPATH_ST']
+       lib_st   = env['DLIB_ST']
+       libpath_st = env['DLIBPATH_ST']
+
+       importpaths = self.to_list(self.importpaths)
+       libpaths = []
+       libs = []
+       uselib = self.to_list(self.uselib)
+
+       for i in uselib:
+               if env['DFLAGS_' + i]:
+                       env.append_unique('DFLAGS', env['DFLAGS_' + i])
+
+       for x in self.features:
+               if not x in ['dprogram', 'dstaticlib', 'dshlib']:
+                       continue
+               x.lstrip('d')
+               d_shlib_dflags = env['D_' + x + '_DFLAGS']
+               if d_shlib_dflags:
+                       env.append_unique('DFLAGS', d_shlib_dflags)
+
+       # add import paths
+       for i in uselib:
+               if env['DPATH_' + i]:
+                       for entry in self.to_list(env['DPATH_' + i]):
+                               if not entry in importpaths:
+                                       importpaths.append(entry)
+
+       # now process the import paths
+       for path in importpaths:
+               if os.path.isabs(path):
+                       env.append_unique('_DIMPORTFLAGS', dpath_st % path)
+               else:
+                       node = self.path.find_dir(path)
+                       self.env.append_unique('INC_PATHS', node)
+                       env.append_unique('_DIMPORTFLAGS', dpath_st % node.srcpath(env))
+                       env.append_unique('_DIMPORTFLAGS', dpath_st % node.bldpath(env))
+
+       # add library paths
+       for i in uselib:
+               if env['LIBPATH_' + i]:
+                       for entry in self.to_list(env['LIBPATH_' + i]):
+                               if not entry in libpaths:
+                                       libpaths.append(entry)
+       libpaths = self.to_list(self.libpaths) + libpaths
+
+       # now process the library paths
+       # apply same path manipulation as used with import paths
+       for path in libpaths:
+               if not os.path.isabs(path):
+                       node = self.path.find_resource(path)
+                       if not node:
+                               raise Utils.WafError('could not find libpath %r from %r' % (path, self))
+                       path = node.abspath(self.env)
+
+               env.append_unique('DLINKFLAGS', libpath_st % path)
+
+       # add libraries
+       for i in uselib:
+               if env['LIB_' + i]:
+                       for entry in self.to_list(env['LIB_' + i]):
+                               if not entry in libs:
+                                       libs.append(entry)
+       libs.extend(self.to_list(self.libs))
+
+       # process user flags
+       for flag in self.to_list(self.dflags):
+               env.append_unique('DFLAGS', flag)
+
+       # now process the libraries
+       for lib in libs:
+               env.append_unique('DLINKFLAGS', lib_st % lib)
+
+       # add linker flags
+       for i in uselib:
+               dlinkflags = env['DLINKFLAGS_' + i]
+               if dlinkflags:
+                       for linkflag in dlinkflags:
+                               env.append_unique('DLINKFLAGS', linkflag)
+
+@feature('dshlib')
+@after('apply_d_vars')
+def add_shlib_d_flags(self):
+       for linkflag in self.env['D_shlib_LINKFLAGS']:
+               self.env.append_unique('DLINKFLAGS', linkflag)
+
+@extension(EXT_D)
+def d_hook(self, node):
+       # create the compilation task: cpp or cc
+       task = self.create_task(self.generate_headers and 'd_with_header' or 'd')
+       try: obj_ext = self.obj_ext
+       except AttributeError: obj_ext = '_%d.o' % self.idx
+
+       task.inputs = [node]
+       task.outputs = [node.change_ext(obj_ext)]
+       self.compiled_tasks.append(task)
+
+       if self.generate_headers:
+               header_node = node.change_ext(self.env['DHEADER_ext'])
+               task.outputs += [header_node]
+
+d_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} ${D_SRC_F}${SRC} ${D_TGT_F}${TGT}'
+d_with_header_str = '${D_COMPILER} ${DFLAGS} ${_DIMPORTFLAGS} \
+${D_HDR_F}${TGT[1].bldpath(env)} \
+${D_SRC_F}${SRC} \
+${D_TGT_F}${TGT[0].bldpath(env)}'
+link_str = '${D_LINKER} ${DLNK_SRC_F}${SRC} ${DLNK_TGT_F}${TGT} ${DLINKFLAGS}'
+
+def override_exec(cls):
+       """stupid dmd wants -of stuck to the file name"""
+       old_exec = cls.exec_command
+       def exec_command(self, *k, **kw):
+               if isinstance(k[0], list):
+                       lst = k[0]
+                       for i in xrange(len(lst)):
+                               if lst[i] == '-of':
+                                       del lst[i]
+                                       lst[i] = '-of' + lst[i]
+                                       break
+               return old_exec(self, *k, **kw)
+       cls.exec_command = exec_command
+
+cls = Task.simple_task_type('d', d_str, 'GREEN', before='static_link d_link', shell=False)
+cls.scan = scan
+override_exec(cls)
+
+cls = Task.simple_task_type('d_with_header', d_with_header_str, 'GREEN', before='static_link d_link', shell=False)
+override_exec(cls)
+
+cls = Task.simple_task_type('d_link', link_str, color='YELLOW', shell=False)
+override_exec(cls)
+
+# for feature request #104
+@taskgen
+def generate_header(self, filename, install_path):
+       if not hasattr(self, 'header_lst'): self.header_lst = []
+       self.meths.append('process_header')
+       self.header_lst.append([filename, install_path])
+
+@before('apply_core')
+def process_header(self):
+       env = self.env
+       for i in getattr(self, 'header_lst', []):
+               node = self.path.find_resource(i[0])
+
+               if not node:
+                       raise Utils.WafError('file not found on d obj '+i[0])
+
+               task = self.create_task('d_header')
+               task.set_inputs(node)
+               task.set_outputs(node.change_ext('.di'))
+
+d_header_str = '${D_COMPILER} ${D_HEADER} ${SRC}'
+Task.simple_task_type('d_header', d_header_str, color='BLUE', shell=False)
+
+@conftest
+def d_platform_flags(conf):
+       v = conf.env
+       binfmt = v.DEST_BINFMT or Utils.unversioned_sys_platform_to_binary_format(
+               v.DEST_OS or Utils.unversioned_sys_platform())
+       if binfmt == 'pe':
+               v['D_program_PATTERN']   = '%s.exe'
+               v['D_shlib_PATTERN']     = 'lib%s.dll'
+               v['D_staticlib_PATTERN'] = 'lib%s.a'
+       else:
+               v['D_program_PATTERN']   = '%s'
+               v['D_shlib_PATTERN']     = 'lib%s.so'
+               v['D_staticlib_PATTERN'] = 'lib%s.a'
+
+@conftest
+def check_dlibrary(conf):
+       ret = conf.check_cc(features='d dprogram', fragment=DLIB, mandatory=True, compile_filename='test.d', execute=True)
+       conf.env.DLIBRARY = ret.strip()
+
+# quick test #
+if __name__ == "__main__":
+       #Logs.verbose = 2
+
+       try: arg = sys.argv[1]
+       except IndexError: arg = "file.d"
+
+       print("".join(filter_comments(arg)))
+       # TODO
+       paths = ['.']
+
+       #gruik = filter()
+       #gruik.start(arg)
+
+       #code = "".join(gruik.buf)
+
+       #print "we have found the following code"
+       #print code
+
+       #print "now parsing"
+       #print "-------------------------------------------"
+       """
+       parser_ = d_parser()
+       parser_.start(arg)
+
+       print "module: %s" % parser_.module
+       print "imports: ",
+       for imp in parser_.imports:
+               print imp + " ",
+       print
+"""
+