"""
#
-# Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
+# Copyright 2008 VMware, Inc.
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
-# IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
+# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
-import os
import os.path
-import re
+import sys
+import subprocess
+import modulefinder
import SCons.Action
import SCons.Builder
import fixes
+import source_list
+
+# the get_implicit_deps() method changed between 2.4 and 2.5: now it expects
+# a callable that takes a scanner as argument and returns a path, rather than
+# a path directly. We want to support both, so we need to detect the SCons version,
+# for which no API is provided by SCons 8-P
+
+# Scons version string has consistently been in this format:
+# MajorVersion.MinorVersion.Patch[.alpha/beta.yyyymmdd]
+# so this formula should cover all versions regardless of type
+# stable, alpha or beta.
+# For simplicity alpha and beta flags are removed.
+scons_version = tuple(map(int, SCons.__version__.split('.')[:3]))
def quietCommandLines(env):
# Quiet command lines
env['SHLINKCOMSTR'] = " Linking $TARGET ..."
env['LDMODULECOMSTR'] = " Linking $TARGET ..."
env['SWIGCOMSTR'] = " Generating $TARGET ..."
+ env['LEXCOMSTR'] = " Generating $TARGET ..."
+ env['YACCCOMSTR'] = " Generating $TARGET ..."
+ env['CODEGENCOMSTR'] = " Generating $TARGET ..."
+ env['INSTALLSTR'] = " Installing $TARGET ..."
def createConvenienceLibBuilder(env):
return convenience_lib
-# TODO: handle import statements with multiple modules
-# TODO: handle from import statements
-import_re = re.compile(r'^import\s+(\S+)$', re.M)
-
def python_scan(node, env, path):
# http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789
+ # https://docs.python.org/2/library/modulefinder.html
contents = node.get_contents()
- source_dir = node.get_dir()
- imports = import_re.findall(contents)
+
+ # Tell ModuleFinder to search dependencies in the script dir, and the glapi
+ # dirs
+ source_dir = node.get_dir().abspath
+ GLAPI = env.Dir('#src/mapi/glapi/gen').abspath
+ path = [source_dir, GLAPI] + sys.path
+
+ finder = modulefinder.ModuleFinder(path=path)
+ finder.run_script(node.abspath)
results = []
- for imp in imports:
- for dir in path:
- file = os.path.join(str(dir), imp.replace('.', os.sep) + '.py')
- if os.path.exists(file):
- results.append(env.File(file))
- break
- file = os.path.join(str(dir), imp.replace('.', os.sep), '__init__.py')
- if os.path.exists(file):
- results.append(env.File(file))
- break
+ for name, mod in finder.modules.items():
+ if mod.__file__ is None:
+ continue
+ assert os.path.exists(mod.__file__)
+ results.append(env.File(mod.__file__))
return results
python_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py'])
# This command creates generated code *in the build directory*.
command = command.replace('$SCRIPT', script_src.path)
- code = env.Command(target, source, command)
+ action = SCons.Action.Action(command, "$CODEGENCOMSTR")
+ code = env.Command(target, source, action)
# Explicitly mark that the generated code depends on the generator,
# and on implicitly imported python modules
- path = (script_src.get_dir(),)
+ path = (script_src.get_dir(),) if scons_version < (2, 5, 0) else lambda x: script_src
deps = [script_src]
deps += script_src.get_implicit_deps(env, python_scanner, path)
env.Depends(code, deps)
env.AddMethod(code_generate, 'CodeGenerate')
+def _pkg_check_modules(env, name, modules):
+ '''Simple wrapper for pkg-config.'''
+
+ env['HAVE_' + name] = False
+
+ # For backwards compatability
+ env[name.lower()] = False
+
+ if env['platform'] == 'windows':
+ return
+
+ if not env.Detect('pkg-config'):
+ return
+
+ if subprocess.call(["pkg-config", "--exists", ' '.join(modules)]) != 0:
+ return
+
+ # Strip version expressions from modules
+ modules = [module.split(' ', 1)[0] for module in modules]
+
+ # Other flags may affect the compilation of unrelated targets, so store
+ # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc)
+ try:
+ flags = env.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules))
+ except OSError:
+ return
+ prefix = name + '_'
+ for flag_name, flag_value in flags.items():
+ assert '_' not in flag_name
+ env[prefix + flag_name] = flag_value
+
+ env['HAVE_' + name] = True
+
+def pkg_check_modules(env, name, modules):
+
+ sys.stdout.write('Checking for %s (%s)...' % (name, ' '.join(modules)))
+ _pkg_check_modules(env, name, modules)
+ result = env['HAVE_' + name]
+ sys.stdout.write(' %s\n' % ['no', 'yes'][int(bool(result))])
+
+ # XXX: For backwards compatability
+ env[name.lower()] = result
+
+
+def pkg_use_modules(env, names):
+ '''Search for all environment flags that match NAME_FOO and append them to
+ the FOO environment variable.'''
+
+ names = env.Flatten(names)
+
+ for name in names:
+ prefix = name + '_'
+
+ if not 'HAVE_' + name in env:
+ raise Exception('Attempt to use unknown module %s' % name)
+
+ if not env['HAVE_' + name]:
+ raise Exception('Attempt to use unavailable module %s' % name)
+
+ flags = {}
+ for flag_name, flag_value in env.Dictionary().items():
+ if flag_name.startswith(prefix):
+ flag_name = flag_name[len(prefix):]
+ if '_' not in flag_name:
+ flags[flag_name] = flag_value
+ if flags:
+ env.MergeFlags(flags)
+
+
+def createPkgConfigMethods(env):
+ env.AddMethod(pkg_check_modules, 'PkgCheckModules')
+ env.AddMethod(pkg_use_modules, 'PkgUseModules')
+
+
+def parse_source_list(env, filename, names=None):
+ # parse the source list file
+ parser = source_list.SourceListParser()
+ src = env.File(filename).srcnode()
+
+ cur_srcdir = env.Dir('.').srcnode().abspath
+ top_srcdir = env.Dir('#').abspath
+ top_builddir = os.path.join(top_srcdir, env['build_dir'])
+
+ # Normalize everything to / slashes
+ cur_srcdir = cur_srcdir.replace('\\', '/')
+ top_srcdir = top_srcdir.replace('\\', '/')
+ top_builddir = top_builddir.replace('\\', '/')
+
+ # Populate the symbol table of the Makefile parser.
+ parser.add_symbol('top_srcdir', top_srcdir)
+ parser.add_symbol('top_builddir', top_builddir)
+
+ sym_table = parser.parse(src.abspath)
+
+ if names:
+ if sys.version_info[0] >= 3:
+ if isinstance(names, str):
+ names = [names]
+ else:
+ if isinstance(names, basestring):
+ names = [names]
+
+ symbols = names
+ else:
+ symbols = list(sym_table.keys())
+
+ # convert the symbol table to source lists
+ src_lists = {}
+ for sym in symbols:
+ val = sym_table[sym]
+ srcs = []
+ for f in val.split():
+ if f:
+ # Process source paths
+ if f.startswith(top_builddir + '/src'):
+ # Automake puts build output on a `src` subdirectory, but
+ # SCons does not, so strip it here.
+ f = top_builddir + f[len(top_builddir + '/src'):]
+ if f.startswith(cur_srcdir + '/'):
+ # Prefer relative source paths, as absolute files tend to
+ # cause duplicate actions.
+ f = f[len(cur_srcdir + '/'):]
+ # do not include any headers
+ if f.endswith(tuple(['.h','.hpp','.inl'])):
+ continue
+ srcs.append(f)
+
+ src_lists[sym] = srcs
+
+ # if names are given, concatenate the lists
+ if names:
+ srcs = []
+ for name in names:
+ srcs.extend(src_lists[name])
+
+ return srcs
+ else:
+ return src_lists
+
+def createParseSourceListMethod(env):
+ env.AddMethod(parse_source_list, 'ParseSourceList')
+
+
def generate(env):
"""Common environment generation code"""
- if env.get('quiet', True):
+ verbose = env.get('verbose', False) or not env.get('quiet', True)
+ if not verbose:
quietCommandLines(env)
# Custom builders and methods
createConvenienceLibBuilder(env)
createCodeGenerateMethod(env)
+ createPkgConfigMethods(env)
+ createParseSourceListMethod(env)
# for debugging
#print env.Dump()