3 Custom builders and methods.
8 # Copyright 2008 VMware, Inc.
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:
19 # The above copyright notice and this permission notice (including the
20 # next paragraph) shall be included in all copies or substantial portions
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.
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
51 scons_version
= tuple(map(int, SCons
.__version
__.split('.')))
53 def quietCommandLines(env
):
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 ..."
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.
78 If it is already there, we return the existing one.
80 Based on the stock StaticLibrary and SharedLibrary builders.
84 convenience_lib
= env
['BUILDERS']['ConvenienceLibrary']
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
)
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
99 return convenience_lib
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()
107 # Tell ModuleFinder to search dependencies in the script dir, and the glapi
109 source_dir
= node
.get_dir().abspath
110 GLAPI
= env
.Dir('#src/mapi/glapi/gen').abspath
111 path
= [source_dir
, GLAPI
] + sys
.path
113 finder
= modulefinder
.ModuleFinder(path
=path
)
114 finder
.run_script(node
.abspath
)
116 for name
, mod
in finder
.modules
.items():
117 if mod
.__file
__ is None:
119 assert os
.path
.exists(mod
.__file
__)
120 results
.append(env
.File(mod
.__file
__))
123 python_scanner
= SCons
.Scanner
.Scanner(function
= python_scan
, skeys
= ['.py'])
126 def code_generate(env
, script
, target
, source
, command
):
127 """Method to simplify code generation via python scripts.
129 http://www.scons.org/wiki/UsingCodeGenerators
130 http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html
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()
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
)
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
147 deps
+= script_src
.get_implicit_deps(env
, python_scanner
, path
)
148 env
.Depends(code
, deps
)
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
154 pyc
= env
.File(str(dep
) + 'c')
155 env
.SideEffect(pyc
, code
)
160 def createCodeGenerateMethod(env
):
161 env
.Append(SCANNERS
= python_scanner
)
162 env
.AddMethod(code_generate
, 'CodeGenerate')
165 def _pkg_check_modules(env
, name
, modules
):
166 '''Simple wrapper for pkg-config.'''
168 env
['HAVE_' + name
] = False
170 # For backwards compatability
171 env
[name
.lower()] = False
173 if env
['platform'] == 'windows':
176 if not env
.Detect('pkg-config'):
179 if subprocess
.call(["pkg-config", "--exists", ' '.join(modules
)]) != 0:
182 # Strip version expressions from modules
183 modules
= [module
.split(' ', 1)[0] for module
in modules
]
185 # Other flags may affect the compilation of unrelated targets, so store
186 # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc)
188 flags
= env
.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules
))
192 for flag_name
, flag_value
in flags
.items():
193 assert '_' not in flag_name
194 env
[prefix
+ flag_name
] = flag_value
196 env
['HAVE_' + name
] = True
198 def pkg_check_modules(env
, name
, modules
):
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
))])
205 # XXX: For backwards compatability
206 env
[name
.lower()] = result
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.'''
213 names
= env
.Flatten(names
)
218 if not 'HAVE_' + name
in env
:
219 raise Exception('Attempt to use unknown module %s' % name
)
221 if not env
['HAVE_' + name
]:
222 raise Exception('Attempt to use unavailable module %s' % name
)
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
231 env
.MergeFlags(flags
)
234 def createPkgConfigMethods(env
):
235 env
.AddMethod(pkg_check_modules
, 'PkgCheckModules')
236 env
.AddMethod(pkg_use_modules
, 'PkgUseModules')
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()
244 cur_srcdir
= env
.Dir('.').srcnode().abspath
245 top_srcdir
= env
.Dir('#').abspath
246 top_builddir
= os
.path
.join(top_srcdir
, env
['build_dir'])
248 # Normalize everything to / slashes
249 cur_srcdir
= cur_srcdir
.replace('\\', '/')
250 top_srcdir
= top_srcdir
.replace('\\', '/')
251 top_builddir
= top_builddir
.replace('\\', '/')
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
)
257 sym_table
= parser
.parse(src
.abspath
)
260 if isinstance(names
, basestring
):
265 symbols
= list(sym_table
.keys())
267 # convert the symbol table to source lists
272 for f
in val
.split():
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'])):
288 src_lists
[sym
] = srcs
290 # if names are given, concatenate the lists
294 srcs
.extend(src_lists
[name
])
300 def createParseSourceListMethod(env
):
301 env
.AddMethod(parse_source_list
, 'ParseSourceList')
305 """Common environment generation code"""
307 verbose
= env
.get('verbose', False) or not env
.get('quiet', True)
309 quietCommandLines(env
)
311 # Custom builders and methods
312 createConvenienceLibBuilder(env
)
313 createCodeGenerateMethod(env
)
314 createPkgConfigMethods(env
)
315 createParseSourceListMethod(env
)