Home | History | Annotate | Download | only in scons
      1 """custom
      2 
      3 Custom builders and methods.
      4 
      5 """
      6 
      7 #
      8 # Copyright 2008 VMware, Inc.
      9 # All Rights Reserved.
     10 #
     11 # Permission is hereby granted, free of charge, to any person obtaining a
     12 # copy of this software and associated documentation files (the
     13 # "Software"), to deal in the Software without restriction, including
     14 # without limitation the rights to use, copy, modify, merge, publish,
     15 # distribute, sub license, and/or sell copies of the Software, and to
     16 # permit persons to whom the Software is furnished to do so, subject to
     17 # the following conditions:
     18 #
     19 # The above copyright notice and this permission notice (including the
     20 # next paragraph) shall be included in all copies or substantial portions
     21 # of the Software.
     22 #
     23 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     24 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     25 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
     26 # IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
     27 # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     28 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     29 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     30 #
     31 
     32 
     33 import os.path
     34 import sys
     35 import subprocess
     36 import modulefinder
     37 
     38 import SCons.Action
     39 import SCons.Builder
     40 import SCons.Scanner
     41 
     42 import fixes
     43 
     44 import source_list
     45 
     46 # the get_implicit_deps() method changed between 2.4 and 2.5: now it expects
     47 # a callable that takes a scanner as argument and returns a path, rather than
     48 # a path directly. We want to support both, so we need to detect the SCons version,
     49 # for which no API is provided by SCons 8-P
     50 
     51 scons_version = tuple(map(int, SCons.__version__.split('.')))
     52 
     53 def quietCommandLines(env):
     54     # Quiet command lines
     55     # See also http://www.scons.org/wiki/HidingCommandLinesInOutput
     56     env['ASCOMSTR'] = "  Assembling $SOURCE ..."
     57     env['ASPPCOMSTR'] = "  Assembling $SOURCE ..."
     58     env['CCCOMSTR'] = "  Compiling $SOURCE ..."
     59     env['SHCCCOMSTR'] = "  Compiling $SOURCE ..."
     60     env['CXXCOMSTR'] = "  Compiling $SOURCE ..."
     61     env['SHCXXCOMSTR'] = "  Compiling $SOURCE ..."
     62     env['ARCOMSTR'] = "  Archiving $TARGET ..."
     63     env['RANLIBCOMSTR'] = "  Indexing $TARGET ..."
     64     env['LINKCOMSTR'] = "  Linking $TARGET ..."
     65     env['SHLINKCOMSTR'] = "  Linking $TARGET ..."
     66     env['LDMODULECOMSTR'] = "  Linking $TARGET ..."
     67     env['SWIGCOMSTR'] = "  Generating $TARGET ..."
     68     env['LEXCOMSTR'] = "  Generating $TARGET ..."
     69     env['YACCCOMSTR'] = "  Generating $TARGET ..."
     70     env['CODEGENCOMSTR'] = "  Generating $TARGET ..."
     71     env['INSTALLSTR'] = "  Installing $TARGET ..."
     72 
     73 
     74 def createConvenienceLibBuilder(env):
     75     """This is a utility function that creates the ConvenienceLibrary
     76     Builder in an Environment if it is not there already.
     77 
     78     If it is already there, we return the existing one.
     79 
     80     Based on the stock StaticLibrary and SharedLibrary builders.
     81     """
     82 
     83     try:
     84         convenience_lib = env['BUILDERS']['ConvenienceLibrary']
     85     except KeyError:
     86         action_list = [ SCons.Action.Action("$ARCOM", "$ARCOMSTR") ]
     87         if env.Detect('ranlib'):
     88             ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
     89             action_list.append(ranlib_action)
     90 
     91         convenience_lib = SCons.Builder.Builder(action = action_list,
     92                                   emitter = '$LIBEMITTER',
     93                                   prefix = '$LIBPREFIX',
     94                                   suffix = '$LIBSUFFIX',
     95                                   src_suffix = '$SHOBJSUFFIX',
     96                                   src_builder = 'SharedObject')
     97         env['BUILDERS']['ConvenienceLibrary'] = convenience_lib
     98 
     99     return convenience_lib
    100 
    101 
    102 def python_scan(node, env, path):
    103     # http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789
    104     # https://docs.python.org/2/library/modulefinder.html
    105     contents = node.get_contents()
    106 
    107     # Tell ModuleFinder to search dependencies in the script dir, and the glapi
    108     # dirs
    109     source_dir = node.get_dir().abspath
    110     GLAPI = env.Dir('#src/mapi/glapi/gen').abspath
    111     path = [source_dir, GLAPI] + sys.path
    112 
    113     finder = modulefinder.ModuleFinder(path=path)
    114     finder.run_script(node.abspath)
    115     results = []
    116     for name, mod in finder.modules.items():
    117         if mod.__file__ is None:
    118             continue
    119         assert os.path.exists(mod.__file__)
    120         results.append(env.File(mod.__file__))
    121     return results
    122 
    123 python_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py'])
    124 
    125 
    126 def code_generate(env, script, target, source, command):
    127     """Method to simplify code generation via python scripts.
    128 
    129     http://www.scons.org/wiki/UsingCodeGenerators
    130     http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html
    131     """
    132 
    133     # We're generating code using Python scripts, so we have to be
    134     # careful with our scons elements.  This entry represents
    135     # the generator file *in the source directory*.
    136     script_src = env.File(script).srcnode()
    137 
    138     # This command creates generated code *in the build directory*.
    139     command = command.replace('$SCRIPT', script_src.path)
    140     action = SCons.Action.Action(command, "$CODEGENCOMSTR")
    141     code = env.Command(target, source, action)
    142 
    143     # Explicitly mark that the generated code depends on the generator,
    144     # and on implicitly imported python modules
    145     path = (script_src.get_dir(),) if scons_version < (2, 5, 0) else lambda x: script_src
    146     deps = [script_src]
    147     deps += script_src.get_implicit_deps(env, python_scanner, path)
    148     env.Depends(code, deps)
    149 
    150     # Running the Python script causes .pyc files to be generated in the
    151     # source directory.  When we clean up, they should go too. So add side
    152     # effects for .pyc files
    153     for dep in deps:
    154         pyc = env.File(str(dep) + 'c')
    155         env.SideEffect(pyc, code)
    156 
    157     return code
    158 
    159 
    160 def createCodeGenerateMethod(env):
    161     env.Append(SCANNERS = python_scanner)
    162     env.AddMethod(code_generate, 'CodeGenerate')
    163 
    164 
    165 def _pkg_check_modules(env, name, modules):
    166     '''Simple wrapper for pkg-config.'''
    167 
    168     env['HAVE_' + name] = False
    169 
    170     # For backwards compatability
    171     env[name.lower()] = False
    172 
    173     if env['platform'] == 'windows':
    174         return
    175 
    176     if not env.Detect('pkg-config'):
    177         return
    178 
    179     if subprocess.call(["pkg-config", "--exists", ' '.join(modules)]) != 0:
    180         return
    181 
    182     # Strip version expressions from modules
    183     modules = [module.split(' ', 1)[0] for module in modules]
    184 
    185     # Other flags may affect the compilation of unrelated targets, so store
    186     # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc)
    187     try:
    188         flags = env.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules))
    189     except OSError:
    190         return
    191     prefix = name + '_'
    192     for flag_name, flag_value in flags.items():
    193         assert '_' not in flag_name
    194         env[prefix + flag_name] = flag_value
    195 
    196     env['HAVE_' + name] = True
    197 
    198 def pkg_check_modules(env, name, modules):
    199 
    200     sys.stdout.write('Checking for %s (%s)...' % (name, ' '.join(modules)))
    201     _pkg_check_modules(env, name, modules)
    202     result = env['HAVE_' + name]
    203     sys.stdout.write(' %s\n' % ['no', 'yes'][int(bool(result))])
    204 
    205     # XXX: For backwards compatability
    206     env[name.lower()] = result
    207 
    208 
    209 def pkg_use_modules(env, names):
    210     '''Search for all environment flags that match NAME_FOO and append them to
    211     the FOO environment variable.'''
    212 
    213     names = env.Flatten(names)
    214 
    215     for name in names:
    216         prefix = name + '_'
    217 
    218         if not 'HAVE_' + name in env:
    219             raise Exception('Attempt to use unknown module %s' % name)
    220 
    221         if not env['HAVE_' + name]:
    222             raise Exception('Attempt to use unavailable module %s' % name)
    223 
    224         flags = {}
    225         for flag_name, flag_value in env.Dictionary().items():
    226             if flag_name.startswith(prefix):
    227                 flag_name = flag_name[len(prefix):]
    228                 if '_' not in flag_name:
    229                     flags[flag_name] = flag_value
    230         if flags:
    231             env.MergeFlags(flags)
    232 
    233 
    234 def createPkgConfigMethods(env):
    235     env.AddMethod(pkg_check_modules, 'PkgCheckModules')
    236     env.AddMethod(pkg_use_modules, 'PkgUseModules')
    237 
    238 
    239 def parse_source_list(env, filename, names=None):
    240     # parse the source list file
    241     parser = source_list.SourceListParser()
    242     src = env.File(filename).srcnode()
    243 
    244     cur_srcdir = env.Dir('.').srcnode().abspath
    245     top_srcdir = env.Dir('#').abspath
    246     top_builddir = os.path.join(top_srcdir, env['build_dir'])
    247 
    248     # Normalize everything to / slashes
    249     cur_srcdir = cur_srcdir.replace('\\', '/')
    250     top_srcdir = top_srcdir.replace('\\', '/')
    251     top_builddir = top_builddir.replace('\\', '/')
    252 
    253     # Populate the symbol table of the Makefile parser.
    254     parser.add_symbol('top_srcdir', top_srcdir)
    255     parser.add_symbol('top_builddir', top_builddir)
    256 
    257     sym_table = parser.parse(src.abspath)
    258 
    259     if names:
    260         if isinstance(names, basestring):
    261             names = [names]
    262 
    263         symbols = names
    264     else:
    265         symbols = list(sym_table.keys())
    266 
    267     # convert the symbol table to source lists
    268     src_lists = {}
    269     for sym in symbols:
    270         val = sym_table[sym]
    271         srcs = []
    272         for f in val.split():
    273             if f:
    274                 # Process source paths
    275                 if f.startswith(top_builddir + '/src'):
    276                     # Automake puts build output on a `src` subdirectory, but
    277                     # SCons does not, so strip it here.
    278                     f = top_builddir + f[len(top_builddir + '/src'):]
    279                 if f.startswith(cur_srcdir + '/'):
    280                     # Prefer relative source paths, as absolute files tend to
    281                     # cause duplicate actions.
    282                     f = f[len(cur_srcdir + '/'):]
    283                 # do not include any headers
    284                 if f.endswith(tuple(['.h','.hpp','.inl'])):
    285                     continue
    286                 srcs.append(f)
    287 
    288         src_lists[sym] = srcs
    289 
    290     # if names are given, concatenate the lists
    291     if names:
    292         srcs = []
    293         for name in names:
    294             srcs.extend(src_lists[name])
    295 
    296         return srcs
    297     else:
    298         return src_lists
    299 
    300 def createParseSourceListMethod(env):
    301     env.AddMethod(parse_source_list, 'ParseSourceList')
    302 
    303 
    304 def generate(env):
    305     """Common environment generation code"""
    306 
    307     verbose = env.get('verbose', False) or not env.get('quiet', True)
    308     if not verbose:
    309         quietCommandLines(env)
    310 
    311     # Custom builders and methods
    312     createConvenienceLibBuilder(env)
    313     createCodeGenerateMethod(env)
    314     createPkgConfigMethods(env)
    315     createParseSourceListMethod(env)
    316 
    317     # for debugging
    318     #print env.Dump()
    319 
    320 
    321 def exists(env):
    322     return 1
    323