• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""custom
2
3Custom 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
33import os.path
34import sys
35import subprocess
36import modulefinder
37
38import SCons.Action
39import SCons.Builder
40import SCons.Scanner
41
42import fixes
43
44import 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 string has consistently been in this format:
52# MajorVersion.MinorVersion.Patch[.alpha/beta.yyyymmdd]
53# so this formula should cover all versions regardless of type
54# stable, alpha or beta.
55# For simplicity alpha and beta flags are removed.
56scons_version = tuple(map(int, SCons.__version__.split('.')[:3]))
57
58def quietCommandLines(env):
59    # Quiet command lines
60    # See also http://www.scons.org/wiki/HidingCommandLinesInOutput
61    env['ASCOMSTR'] = "  Assembling $SOURCE ..."
62    env['ASPPCOMSTR'] = "  Assembling $SOURCE ..."
63    env['CCCOMSTR'] = "  Compiling $SOURCE ..."
64    env['SHCCCOMSTR'] = "  Compiling $SOURCE ..."
65    env['CXXCOMSTR'] = "  Compiling $SOURCE ..."
66    env['SHCXXCOMSTR'] = "  Compiling $SOURCE ..."
67    env['ARCOMSTR'] = "  Archiving $TARGET ..."
68    env['RANLIBCOMSTR'] = "  Indexing $TARGET ..."
69    env['LINKCOMSTR'] = "  Linking $TARGET ..."
70    env['SHLINKCOMSTR'] = "  Linking $TARGET ..."
71    env['LDMODULECOMSTR'] = "  Linking $TARGET ..."
72    env['SWIGCOMSTR'] = "  Generating $TARGET ..."
73    env['LEXCOMSTR'] = "  Generating $TARGET ..."
74    env['YACCCOMSTR'] = "  Generating $TARGET ..."
75    env['CODEGENCOMSTR'] = "  Generating $TARGET ..."
76    env['INSTALLSTR'] = "  Installing $TARGET ..."
77
78
79def createConvenienceLibBuilder(env):
80    """This is a utility function that creates the ConvenienceLibrary
81    Builder in an Environment if it is not there already.
82
83    If it is already there, we return the existing one.
84
85    Based on the stock StaticLibrary and SharedLibrary builders.
86    """
87
88    try:
89        convenience_lib = env['BUILDERS']['ConvenienceLibrary']
90    except KeyError:
91        action_list = [ SCons.Action.Action("$ARCOM", "$ARCOMSTR") ]
92        if env.Detect('ranlib'):
93            ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
94            action_list.append(ranlib_action)
95
96        convenience_lib = SCons.Builder.Builder(action = action_list,
97                                  emitter = '$LIBEMITTER',
98                                  prefix = '$LIBPREFIX',
99                                  suffix = '$LIBSUFFIX',
100                                  src_suffix = '$SHOBJSUFFIX',
101                                  src_builder = 'SharedObject')
102        env['BUILDERS']['ConvenienceLibrary'] = convenience_lib
103
104    return convenience_lib
105
106
107def python_scan(node, env, path):
108    # http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789
109    # https://docs.python.org/2/library/modulefinder.html
110    contents = node.get_contents()
111
112    # Tell ModuleFinder to search dependencies in the script dir, and the glapi
113    # dirs
114    source_dir = node.get_dir().abspath
115    GLAPI = env.Dir('#src/mapi/glapi/gen').abspath
116    path = [source_dir, GLAPI] + sys.path
117
118    finder = modulefinder.ModuleFinder(path=path)
119    finder.run_script(node.abspath)
120    results = []
121    for name, mod in finder.modules.items():
122        if mod.__file__ is None:
123            continue
124        assert os.path.exists(mod.__file__)
125        results.append(env.File(mod.__file__))
126    return results
127
128python_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py'])
129
130
131def code_generate(env, script, target, source, command):
132    """Method to simplify code generation via python scripts.
133
134    http://www.scons.org/wiki/UsingCodeGenerators
135    http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html
136    """
137
138    # We're generating code using Python scripts, so we have to be
139    # careful with our scons elements.  This entry represents
140    # the generator file *in the source directory*.
141    script_src = env.File(script).srcnode()
142
143    # This command creates generated code *in the build directory*.
144    command = command.replace('$SCRIPT', script_src.path)
145    action = SCons.Action.Action(command, "$CODEGENCOMSTR")
146    code = env.Command(target, source, action)
147
148    # Explicitly mark that the generated code depends on the generator,
149    # and on implicitly imported python modules
150    path = (script_src.get_dir(),) if scons_version < (2, 5, 0) else lambda x: script_src
151    deps = [script_src]
152    deps += script_src.get_implicit_deps(env, python_scanner, path)
153    env.Depends(code, deps)
154
155    # Running the Python script causes .pyc files to be generated in the
156    # source directory.  When we clean up, they should go too. So add side
157    # effects for .pyc files
158    for dep in deps:
159        pyc = env.File(str(dep) + 'c')
160        env.SideEffect(pyc, code)
161
162    return code
163
164
165def createCodeGenerateMethod(env):
166    env.Append(SCANNERS = python_scanner)
167    env.AddMethod(code_generate, 'CodeGenerate')
168
169
170def _pkg_check_modules(env, name, modules):
171    '''Simple wrapper for pkg-config.'''
172
173    env['HAVE_' + name] = False
174
175    # For backwards compatability
176    env[name.lower()] = False
177
178    if env['platform'] == 'windows':
179        return
180
181    if not env.Detect('pkg-config'):
182        return
183
184    if subprocess.call(["pkg-config", "--exists", ' '.join(modules)]) != 0:
185        return
186
187    # Strip version expressions from modules
188    modules = [module.split(' ', 1)[0] for module in modules]
189
190    # Other flags may affect the compilation of unrelated targets, so store
191    # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc)
192    try:
193        flags = env.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules))
194    except OSError:
195        return
196    prefix = name + '_'
197    for flag_name, flag_value in flags.items():
198        assert '_' not in flag_name
199        env[prefix + flag_name] = flag_value
200
201    env['HAVE_' + name] = True
202
203def pkg_check_modules(env, name, modules):
204
205    sys.stdout.write('Checking for %s (%s)...' % (name, ' '.join(modules)))
206    _pkg_check_modules(env, name, modules)
207    result = env['HAVE_' + name]
208    sys.stdout.write(' %s\n' % ['no', 'yes'][int(bool(result))])
209
210    # XXX: For backwards compatability
211    env[name.lower()] = result
212
213
214def pkg_use_modules(env, names):
215    '''Search for all environment flags that match NAME_FOO and append them to
216    the FOO environment variable.'''
217
218    names = env.Flatten(names)
219
220    for name in names:
221        prefix = name + '_'
222
223        if not 'HAVE_' + name in env:
224            raise Exception('Attempt to use unknown module %s' % name)
225
226        if not env['HAVE_' + name]:
227            raise Exception('Attempt to use unavailable module %s' % name)
228
229        flags = {}
230        for flag_name, flag_value in env.Dictionary().items():
231            if flag_name.startswith(prefix):
232                flag_name = flag_name[len(prefix):]
233                if '_' not in flag_name:
234                    flags[flag_name] = flag_value
235        if flags:
236            env.MergeFlags(flags)
237
238
239def createPkgConfigMethods(env):
240    env.AddMethod(pkg_check_modules, 'PkgCheckModules')
241    env.AddMethod(pkg_use_modules, 'PkgUseModules')
242
243
244def parse_source_list(env, filename, names=None):
245    # parse the source list file
246    parser = source_list.SourceListParser()
247    src = env.File(filename).srcnode()
248
249    cur_srcdir = env.Dir('.').srcnode().abspath
250    top_srcdir = env.Dir('#').abspath
251    top_builddir = os.path.join(top_srcdir, env['build_dir'])
252
253    # Normalize everything to / slashes
254    cur_srcdir = cur_srcdir.replace('\\', '/')
255    top_srcdir = top_srcdir.replace('\\', '/')
256    top_builddir = top_builddir.replace('\\', '/')
257
258    # Populate the symbol table of the Makefile parser.
259    parser.add_symbol('top_srcdir', top_srcdir)
260    parser.add_symbol('top_builddir', top_builddir)
261
262    sym_table = parser.parse(src.abspath)
263
264    if names:
265        if sys.version_info[0] >= 3:
266            if isinstance(names, str):
267                names = [names]
268        else:
269            if isinstance(names, basestring):
270                names = [names]
271
272        symbols = names
273    else:
274        symbols = list(sym_table.keys())
275
276    # convert the symbol table to source lists
277    src_lists = {}
278    for sym in symbols:
279        val = sym_table[sym]
280        srcs = []
281        for f in val.split():
282            if f:
283                # Process source paths
284                if f.startswith(top_builddir + '/src'):
285                    # Automake puts build output on a `src` subdirectory, but
286                    # SCons does not, so strip it here.
287                    f = top_builddir + f[len(top_builddir + '/src'):]
288                if f.startswith(cur_srcdir + '/'):
289                    # Prefer relative source paths, as absolute files tend to
290                    # cause duplicate actions.
291                    f = f[len(cur_srcdir + '/'):]
292                # do not include any headers
293                if f.endswith(tuple(['.h','.hpp','.inl'])):
294                    continue
295                srcs.append(f)
296
297        src_lists[sym] = srcs
298
299    # if names are given, concatenate the lists
300    if names:
301        srcs = []
302        for name in names:
303            srcs.extend(src_lists[name])
304
305        return srcs
306    else:
307        return src_lists
308
309def createParseSourceListMethod(env):
310    env.AddMethod(parse_source_list, 'ParseSourceList')
311
312
313def generate(env):
314    """Common environment generation code"""
315
316    verbose = env.get('verbose', False) or not env.get('quiet', True)
317    if not verbose:
318        quietCommandLines(env)
319
320    # Custom builders and methods
321    createConvenienceLibBuilder(env)
322    createCodeGenerateMethod(env)
323    createPkgConfigMethods(env)
324    createParseSourceListMethod(env)
325
326    # for debugging
327    #print env.Dump()
328
329
330def exists(env):
331    return 1
332