OSDN Git Service

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