1848b8605Smrg"""custom
2848b8605Smrg
3848b8605SmrgCustom builders and methods.
4848b8605Smrg
5848b8605Smrg"""
6848b8605Smrg
7848b8605Smrg#
8848b8605Smrg# Copyright 2008 VMware, Inc.
9848b8605Smrg# All Rights Reserved.
10848b8605Smrg#
11848b8605Smrg# Permission is hereby granted, free of charge, to any person obtaining a
12848b8605Smrg# copy of this software and associated documentation files (the
13848b8605Smrg# "Software"), to deal in the Software without restriction, including
14848b8605Smrg# without limitation the rights to use, copy, modify, merge, publish,
15848b8605Smrg# distribute, sub license, and/or sell copies of the Software, and to
16848b8605Smrg# permit persons to whom the Software is furnished to do so, subject to
17848b8605Smrg# the following conditions:
18848b8605Smrg#
19848b8605Smrg# The above copyright notice and this permission notice (including the
20848b8605Smrg# next paragraph) shall be included in all copies or substantial portions
21848b8605Smrg# of the Software.
22848b8605Smrg#
23848b8605Smrg# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24848b8605Smrg# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25848b8605Smrg# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
26848b8605Smrg# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
27848b8605Smrg# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
28848b8605Smrg# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
29848b8605Smrg# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30848b8605Smrg#
31848b8605Smrg
32848b8605Smrg
33848b8605Smrgimport os.path
34848b8605Smrgimport sys
35848b8605Smrgimport subprocess
36b8e80941Smrgimport modulefinder
37848b8605Smrg
38848b8605Smrgimport SCons.Action
39848b8605Smrgimport SCons.Builder
40848b8605Smrgimport SCons.Scanner
41848b8605Smrg
42848b8605Smrgimport fixes
43848b8605Smrg
44848b8605Smrgimport source_list
45848b8605Smrg
46b8e80941Smrg# the get_implicit_deps() method changed between 2.4 and 2.5: now it expects
47b8e80941Smrg# a callable that takes a scanner as argument and returns a path, rather than
48b8e80941Smrg# a path directly. We want to support both, so we need to detect the SCons version,
49b8e80941Smrg# for which no API is provided by SCons 8-P
50b8e80941Smrg
51b8e80941Smrg# Scons version string has consistently been in this format:
52b8e80941Smrg# MajorVersion.MinorVersion.Patch[.alpha/beta.yyyymmdd]
53b8e80941Smrg# so this formula should cover all versions regardless of type
54b8e80941Smrg# stable, alpha or beta.
55b8e80941Smrg# For simplicity alpha and beta flags are removed.
56b8e80941Smrgscons_version = tuple(map(int, SCons.__version__.split('.')[:3]))
57b8e80941Smrg
58848b8605Smrgdef quietCommandLines(env):
59848b8605Smrg    # Quiet command lines
60848b8605Smrg    # See also http://www.scons.org/wiki/HidingCommandLinesInOutput
61848b8605Smrg    env['ASCOMSTR'] = "  Assembling $SOURCE ..."
62848b8605Smrg    env['ASPPCOMSTR'] = "  Assembling $SOURCE ..."
63848b8605Smrg    env['CCCOMSTR'] = "  Compiling $SOURCE ..."
64848b8605Smrg    env['SHCCCOMSTR'] = "  Compiling $SOURCE ..."
65848b8605Smrg    env['CXXCOMSTR'] = "  Compiling $SOURCE ..."
66848b8605Smrg    env['SHCXXCOMSTR'] = "  Compiling $SOURCE ..."
67848b8605Smrg    env['ARCOMSTR'] = "  Archiving $TARGET ..."
68848b8605Smrg    env['RANLIBCOMSTR'] = "  Indexing $TARGET ..."
69848b8605Smrg    env['LINKCOMSTR'] = "  Linking $TARGET ..."
70848b8605Smrg    env['SHLINKCOMSTR'] = "  Linking $TARGET ..."
71848b8605Smrg    env['LDMODULECOMSTR'] = "  Linking $TARGET ..."
72848b8605Smrg    env['SWIGCOMSTR'] = "  Generating $TARGET ..."
73848b8605Smrg    env['LEXCOMSTR'] = "  Generating $TARGET ..."
74848b8605Smrg    env['YACCCOMSTR'] = "  Generating $TARGET ..."
75848b8605Smrg    env['CODEGENCOMSTR'] = "  Generating $TARGET ..."
76848b8605Smrg    env['INSTALLSTR'] = "  Installing $TARGET ..."
77848b8605Smrg
78848b8605Smrg
79848b8605Smrgdef createConvenienceLibBuilder(env):
80848b8605Smrg    """This is a utility function that creates the ConvenienceLibrary
81848b8605Smrg    Builder in an Environment if it is not there already.
82848b8605Smrg
83848b8605Smrg    If it is already there, we return the existing one.
84848b8605Smrg
85848b8605Smrg    Based on the stock StaticLibrary and SharedLibrary builders.
86848b8605Smrg    """
87848b8605Smrg
88848b8605Smrg    try:
89848b8605Smrg        convenience_lib = env['BUILDERS']['ConvenienceLibrary']
90848b8605Smrg    except KeyError:
91848b8605Smrg        action_list = [ SCons.Action.Action("$ARCOM", "$ARCOMSTR") ]
92848b8605Smrg        if env.Detect('ranlib'):
93848b8605Smrg            ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
94848b8605Smrg            action_list.append(ranlib_action)
95848b8605Smrg
96848b8605Smrg        convenience_lib = SCons.Builder.Builder(action = action_list,
97848b8605Smrg                                  emitter = '$LIBEMITTER',
98848b8605Smrg                                  prefix = '$LIBPREFIX',
99848b8605Smrg                                  suffix = '$LIBSUFFIX',
100848b8605Smrg                                  src_suffix = '$SHOBJSUFFIX',
101848b8605Smrg                                  src_builder = 'SharedObject')
102848b8605Smrg        env['BUILDERS']['ConvenienceLibrary'] = convenience_lib
103848b8605Smrg
104848b8605Smrg    return convenience_lib
105848b8605Smrg
106848b8605Smrg
107848b8605Smrgdef python_scan(node, env, path):
108848b8605Smrg    # http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789
109b8e80941Smrg    # https://docs.python.org/2/library/modulefinder.html
110848b8605Smrg    contents = node.get_contents()
111b8e80941Smrg
112b8e80941Smrg    # Tell ModuleFinder to search dependencies in the script dir, and the glapi
113b8e80941Smrg    # dirs
114b8e80941Smrg    source_dir = node.get_dir().abspath
115b8e80941Smrg    GLAPI = env.Dir('#src/mapi/glapi/gen').abspath
116b8e80941Smrg    path = [source_dir, GLAPI] + sys.path
117b8e80941Smrg
118b8e80941Smrg    finder = modulefinder.ModuleFinder(path=path)
119b8e80941Smrg    finder.run_script(node.abspath)
120848b8605Smrg    results = []
121b8e80941Smrg    for name, mod in finder.modules.items():
122b8e80941Smrg        if mod.__file__ is None:
123b8e80941Smrg            continue
124b8e80941Smrg        assert os.path.exists(mod.__file__)
125b8e80941Smrg        results.append(env.File(mod.__file__))
126848b8605Smrg    return results
127848b8605Smrg
128848b8605Smrgpython_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py'])
129848b8605Smrg
130848b8605Smrg
131848b8605Smrgdef code_generate(env, script, target, source, command):
132848b8605Smrg    """Method to simplify code generation via python scripts.
133848b8605Smrg
134848b8605Smrg    http://www.scons.org/wiki/UsingCodeGenerators
135848b8605Smrg    http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html
136848b8605Smrg    """
137848b8605Smrg
138848b8605Smrg    # We're generating code using Python scripts, so we have to be
139848b8605Smrg    # careful with our scons elements.  This entry represents
140848b8605Smrg    # the generator file *in the source directory*.
141848b8605Smrg    script_src = env.File(script).srcnode()
142848b8605Smrg
143848b8605Smrg    # This command creates generated code *in the build directory*.
144848b8605Smrg    command = command.replace('$SCRIPT', script_src.path)
145848b8605Smrg    action = SCons.Action.Action(command, "$CODEGENCOMSTR")
146848b8605Smrg    code = env.Command(target, source, action)
147848b8605Smrg
148848b8605Smrg    # Explicitly mark that the generated code depends on the generator,
149848b8605Smrg    # and on implicitly imported python modules
150b8e80941Smrg    path = (script_src.get_dir(),) if scons_version < (2, 5, 0) else lambda x: script_src
151848b8605Smrg    deps = [script_src]
152848b8605Smrg    deps += script_src.get_implicit_deps(env, python_scanner, path)
153848b8605Smrg    env.Depends(code, deps)
154848b8605Smrg
155848b8605Smrg    # Running the Python script causes .pyc files to be generated in the
156848b8605Smrg    # source directory.  When we clean up, they should go too. So add side
157848b8605Smrg    # effects for .pyc files
158848b8605Smrg    for dep in deps:
159848b8605Smrg        pyc = env.File(str(dep) + 'c')
160848b8605Smrg        env.SideEffect(pyc, code)
161848b8605Smrg
162848b8605Smrg    return code
163848b8605Smrg
164848b8605Smrg
165848b8605Smrgdef createCodeGenerateMethod(env):
166848b8605Smrg    env.Append(SCANNERS = python_scanner)
167848b8605Smrg    env.AddMethod(code_generate, 'CodeGenerate')
168848b8605Smrg
169848b8605Smrg
170848b8605Smrgdef _pkg_check_modules(env, name, modules):
171848b8605Smrg    '''Simple wrapper for pkg-config.'''
172848b8605Smrg
173848b8605Smrg    env['HAVE_' + name] = False
174848b8605Smrg
175848b8605Smrg    # For backwards compatability
176848b8605Smrg    env[name.lower()] = False
177848b8605Smrg
178848b8605Smrg    if env['platform'] == 'windows':
179848b8605Smrg        return
180848b8605Smrg
181848b8605Smrg    if not env.Detect('pkg-config'):
182848b8605Smrg        return
183848b8605Smrg
184848b8605Smrg    if subprocess.call(["pkg-config", "--exists", ' '.join(modules)]) != 0:
185848b8605Smrg        return
186848b8605Smrg
187848b8605Smrg    # Strip version expressions from modules
188848b8605Smrg    modules = [module.split(' ', 1)[0] for module in modules]
189848b8605Smrg
190848b8605Smrg    # Other flags may affect the compilation of unrelated targets, so store
191848b8605Smrg    # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc)
192848b8605Smrg    try:
193848b8605Smrg        flags = env.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules))
194848b8605Smrg    except OSError:
195848b8605Smrg        return
196848b8605Smrg    prefix = name + '_'
197b8e80941Smrg    for flag_name, flag_value in flags.items():
198848b8605Smrg        assert '_' not in flag_name
199848b8605Smrg        env[prefix + flag_name] = flag_value
200848b8605Smrg
201848b8605Smrg    env['HAVE_' + name] = True
202848b8605Smrg
203848b8605Smrgdef pkg_check_modules(env, name, modules):
204848b8605Smrg
205848b8605Smrg    sys.stdout.write('Checking for %s (%s)...' % (name, ' '.join(modules)))
206848b8605Smrg    _pkg_check_modules(env, name, modules)
207848b8605Smrg    result = env['HAVE_' + name]
208848b8605Smrg    sys.stdout.write(' %s\n' % ['no', 'yes'][int(bool(result))])
209848b8605Smrg
210848b8605Smrg    # XXX: For backwards compatability
211848b8605Smrg    env[name.lower()] = result
212848b8605Smrg
213848b8605Smrg
214848b8605Smrgdef pkg_use_modules(env, names):
215848b8605Smrg    '''Search for all environment flags that match NAME_FOO and append them to
216848b8605Smrg    the FOO environment variable.'''
217848b8605Smrg
218848b8605Smrg    names = env.Flatten(names)
219848b8605Smrg
220848b8605Smrg    for name in names:
221848b8605Smrg        prefix = name + '_'
222848b8605Smrg
223848b8605Smrg        if not 'HAVE_' + name in env:
224848b8605Smrg            raise Exception('Attempt to use unknown module %s' % name)
225848b8605Smrg
226848b8605Smrg        if not env['HAVE_' + name]:
227848b8605Smrg            raise Exception('Attempt to use unavailable module %s' % name)
228848b8605Smrg
229848b8605Smrg        flags = {}
230b8e80941Smrg        for flag_name, flag_value in env.Dictionary().items():
231848b8605Smrg            if flag_name.startswith(prefix):
232848b8605Smrg                flag_name = flag_name[len(prefix):]
233848b8605Smrg                if '_' not in flag_name:
234848b8605Smrg                    flags[flag_name] = flag_value
235848b8605Smrg        if flags:
236848b8605Smrg            env.MergeFlags(flags)
237848b8605Smrg
238848b8605Smrg
239848b8605Smrgdef createPkgConfigMethods(env):
240848b8605Smrg    env.AddMethod(pkg_check_modules, 'PkgCheckModules')
241848b8605Smrg    env.AddMethod(pkg_use_modules, 'PkgUseModules')
242848b8605Smrg
243848b8605Smrg
244848b8605Smrgdef parse_source_list(env, filename, names=None):
245848b8605Smrg    # parse the source list file
246848b8605Smrg    parser = source_list.SourceListParser()
247848b8605Smrg    src = env.File(filename).srcnode()
248848b8605Smrg
249848b8605Smrg    cur_srcdir = env.Dir('.').srcnode().abspath
250848b8605Smrg    top_srcdir = env.Dir('#').abspath
251848b8605Smrg    top_builddir = os.path.join(top_srcdir, env['build_dir'])
252848b8605Smrg
253848b8605Smrg    # Normalize everything to / slashes
254848b8605Smrg    cur_srcdir = cur_srcdir.replace('\\', '/')
255848b8605Smrg    top_srcdir = top_srcdir.replace('\\', '/')
256848b8605Smrg    top_builddir = top_builddir.replace('\\', '/')
257848b8605Smrg
258848b8605Smrg    # Populate the symbol table of the Makefile parser.
259848b8605Smrg    parser.add_symbol('top_srcdir', top_srcdir)
260848b8605Smrg    parser.add_symbol('top_builddir', top_builddir)
261848b8605Smrg
262848b8605Smrg    sym_table = parser.parse(src.abspath)
263848b8605Smrg
264848b8605Smrg    if names:
265848b8605Smrg        if isinstance(names, basestring):
266848b8605Smrg            names = [names]
267848b8605Smrg
268848b8605Smrg        symbols = names
269848b8605Smrg    else:
270b8e80941Smrg        symbols = list(sym_table.keys())
271848b8605Smrg
272848b8605Smrg    # convert the symbol table to source lists
273848b8605Smrg    src_lists = {}
274848b8605Smrg    for sym in symbols:
275848b8605Smrg        val = sym_table[sym]
276848b8605Smrg        srcs = []
277848b8605Smrg        for f in val.split():
278848b8605Smrg            if f:
279848b8605Smrg                # Process source paths
280848b8605Smrg                if f.startswith(top_builddir + '/src'):
281848b8605Smrg                    # Automake puts build output on a `src` subdirectory, but
282848b8605Smrg                    # SCons does not, so strip it here.
283848b8605Smrg                    f = top_builddir + f[len(top_builddir + '/src'):]
284848b8605Smrg                if f.startswith(cur_srcdir + '/'):
285848b8605Smrg                    # Prefer relative source paths, as absolute files tend to
286848b8605Smrg                    # cause duplicate actions.
287848b8605Smrg                    f = f[len(cur_srcdir + '/'):]
288848b8605Smrg                # do not include any headers
289b8e80941Smrg                if f.endswith(tuple(['.h','.hpp','.inl'])):
290848b8605Smrg                    continue
291848b8605Smrg                srcs.append(f)
292848b8605Smrg
293848b8605Smrg        src_lists[sym] = srcs
294848b8605Smrg
295848b8605Smrg    # if names are given, concatenate the lists
296848b8605Smrg    if names:
297848b8605Smrg        srcs = []
298848b8605Smrg        for name in names:
299848b8605Smrg            srcs.extend(src_lists[name])
300848b8605Smrg
301848b8605Smrg        return srcs
302848b8605Smrg    else:
303848b8605Smrg        return src_lists
304848b8605Smrg
305848b8605Smrgdef createParseSourceListMethod(env):
306848b8605Smrg    env.AddMethod(parse_source_list, 'ParseSourceList')
307848b8605Smrg
308848b8605Smrg
309848b8605Smrgdef generate(env):
310848b8605Smrg    """Common environment generation code"""
311848b8605Smrg
312848b8605Smrg    verbose = env.get('verbose', False) or not env.get('quiet', True)
313848b8605Smrg    if not verbose:
314848b8605Smrg        quietCommandLines(env)
315848b8605Smrg
316848b8605Smrg    # Custom builders and methods
317848b8605Smrg    createConvenienceLibBuilder(env)
318848b8605Smrg    createCodeGenerateMethod(env)
319848b8605Smrg    createPkgConfigMethods(env)
320848b8605Smrg    createParseSourceListMethod(env)
321848b8605Smrg
322848b8605Smrg    # for debugging
323848b8605Smrg    #print env.Dump()
324848b8605Smrg
325848b8605Smrg
326848b8605Smrgdef exists(env):
327848b8605Smrg    return 1
328