| Home | Trees | Indices | Help |
|
|---|
|
|
1 """SCons.Script.SConscript
2
3 This module defines the Python API provided to SConscript and SConstruct
4 files.
5
6 """
7
8 #
9 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation
10 #
11 # Permission is hereby granted, free of charge, to any person obtaining
12 # a 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, sublicense, 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 shall be included
20 # in all copies or substantial portions of the Software.
21 #
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
23 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
24 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 #
30
31 __revision__ = "src/engine/SCons/Script/SConscript.py 3795 2008/11/25 22:04:43 scons"
32
33 import SCons
34 import SCons.Action
35 import SCons.Builder
36 import SCons.Defaults
37 import SCons.Environment
38 import SCons.Errors
39 import SCons.Node
40 import SCons.Node.Alias
41 import SCons.Node.FS
42 import SCons.Platform
43 import SCons.SConf
44 import SCons.Script.Main
45 import SCons.Tool
46 import SCons.Util
47
48 import os
49 import os.path
50 import re
51 import string
52 import sys
53 import traceback
54 import types
55 import UserList
56
57 # The following variables used to live in this module. Some
58 # SConscript files out there may have referred to them directly as
59 # SCons.Script.SConscript.*. This is now supported by some special
60 # handling towards the bottom of the SConscript.__init__.py module.
61 #Arguments = {}
62 #ArgList = []
63 #BuildTargets = TargetList()
64 #CommandLineTargets = []
65 #DefaultTargets = []
66
69
70 launch_dir = os.path.abspath(os.curdir)
71
72 GlobalDict = None
73
74 # global exports set by Export():
75 global_exports = {}
76
77 # chdir flag
78 sconscript_chdir = 1
79
81 """Return the locals and globals for the function that called
82 into this module in the current call stack."""
83 try: 1/0
84 except ZeroDivisionError:
85 # Don't start iterating with the current stack-frame to
86 # prevent creating reference cycles (f_back is safe).
87 frame = sys.exc_info()[2].tb_frame.f_back
88
89 # Find the first frame that *isn't* from this file. This means
90 # that we expect all of the SCons frames that implement an Export()
91 # or SConscript() call to be in this file, so that we can identify
92 # the first non-Script.SConscript frame as the user's local calling
93 # environment, and the locals and globals dictionaries from that
94 # frame as the calling namespaces. See the comment below preceding
95 # the DefaultEnvironmentCall block for even more explanation.
96 while frame.f_globals.get("__name__") == __name__:
97 frame = frame.f_back
98
99 return frame.f_locals, frame.f_globals
100
101
103 """Compute a dictionary of exports given one of the parameters
104 to the Export() function or the exports argument to SConscript()."""
105
106 loc, glob = get_calling_namespaces()
107
108 retval = {}
109 try:
110 for export in exports:
111 if SCons.Util.is_Dict(export):
112 retval.update(export)
113 else:
114 try:
115 retval[export] = loc[export]
116 except KeyError:
117 retval[export] = glob[export]
118 except KeyError, x:
119 raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x
120
121 return retval
122
124 """A frame on the SConstruct/SConscript call stack"""
126 self.globals = BuildDefaultGlobals()
127 self.retval = None
128 self.prev_dir = fs.getcwd()
129 self.exports = compute_exports(exports) # exports from the calling SConscript
130 # make sure the sconscript attr is a Node.
131 if isinstance(sconscript, SCons.Node.Node):
132 self.sconscript = sconscript
133 elif sconscript == '-':
134 self.sconscript = None
135 else:
136 self.sconscript = fs.File(str(sconscript))
137
138 # the SConstruct/SConscript call stack:
139 call_stack = []
140
141 # For documentation on the methods in this file, see the scons man-page
142
144 retval = []
145 try:
146 fvars = SCons.Util.flatten(vars)
147 for var in fvars:
148 for v in string.split(var):
149 retval.append(call_stack[-1].globals[v])
150 except KeyError, x:
151 raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x
152
153 if len(retval) == 1:
154 call_stack[-1].retval = retval[0]
155 else:
156 call_stack[-1].retval = tuple(retval)
157
158 stop = kw.get('stop', True)
159
160 if stop:
161 raise SConscriptReturn
162
163
164 stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :)
165
167 top = fs.Top
168 sd = fs.SConstruct_dir.rdir()
169 exports = kw.get('exports', [])
170
171 # evaluate each SConscript file
172 results = []
173 for fn in files:
174 call_stack.append(Frame(fs, exports, fn))
175 old_sys_path = sys.path
176 try:
177 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1
178 if fn == "-":
179 exec sys.stdin in call_stack[-1].globals
180 else:
181 if isinstance(fn, SCons.Node.Node):
182 f = fn
183 else:
184 f = fs.File(str(fn))
185 _file_ = None
186
187 # Change directory to the top of the source
188 # tree to make sure the os's cwd and the cwd of
189 # fs match so we can open the SConscript.
190 fs.chdir(top, change_os_dir=1)
191 if f.rexists():
192 _file_ = open(f.rfile().get_abspath(), "r")
193 elif f.has_src_builder():
194 # The SConscript file apparently exists in a source
195 # code management system. Build it, but then clear
196 # the builder so that it doesn't get built *again*
197 # during the actual build phase.
198 f.build()
199 f.built()
200 f.builder_set(None)
201 if f.exists():
202 _file_ = open(f.get_abspath(), "r")
203 if _file_:
204 # Chdir to the SConscript directory. Use a path
205 # name relative to the SConstruct file so that if
206 # we're using the -f option, we're essentially
207 # creating a parallel SConscript directory structure
208 # in our local directory tree.
209 #
210 # XXX This is broken for multiple-repository cases
211 # where the SConstruct and SConscript files might be
212 # in different Repositories. For now, cross that
213 # bridge when someone comes to it.
214 try:
215 src_dir = kw['src_dir']
216 except KeyError:
217 ldir = fs.Dir(f.dir.get_path(sd))
218 else:
219 ldir = fs.Dir(src_dir)
220 if not ldir.is_under(f.dir):
221 # They specified a source directory, but
222 # it's above the SConscript directory.
223 # Do the sensible thing and just use the
224 # SConcript directory.
225 ldir = fs.Dir(f.dir.get_path(sd))
226 try:
227 fs.chdir(ldir, change_os_dir=sconscript_chdir)
228 except OSError:
229 # There was no local directory, so we should be
230 # able to chdir to the Repository directory.
231 # Note that we do this directly, not through
232 # fs.chdir(), because we still need to
233 # interpret the stuff within the SConscript file
234 # relative to where we are logically.
235 fs.chdir(ldir, change_os_dir=0)
236 # TODO Not sure how to handle src_dir here
237 os.chdir(f.rfile().dir.get_abspath())
238
239 # Append the SConscript directory to the beginning
240 # of sys.path so Python modules in the SConscript
241 # directory can be easily imported.
242 sys.path = [ f.dir.get_abspath() ] + sys.path
243
244 # This is the magic line that actually reads up
245 # and executes the stuff in the SConscript file.
246 # The locals for this frame contain the special
247 # bottom-of-the-stack marker so that any
248 # exceptions that occur when processing this
249 # SConscript can base the printed frames at this
250 # level and not show SCons internals as well.
251 call_stack[-1].globals.update({stack_bottom:1})
252 old_file = call_stack[-1].globals.get('__file__')
253 try:
254 del call_stack[-1].globals['__file__']
255 except KeyError:
256 pass
257 try:
258 try:
259 exec _file_ in call_stack[-1].globals
260 except SConscriptReturn:
261 pass
262 finally:
263 if old_file is not None:
264 call_stack[-1].globals.update({__file__:old_file})
265 else:
266 SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning,
267 "Ignoring missing SConscript '%s'" % f.path)
268
269 finally:
270 SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1
271 sys.path = old_sys_path
272 frame = call_stack.pop()
273 try:
274 fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir)
275 except OSError:
276 # There was no local directory, so chdir to the
277 # Repository directory. Like above, we do this
278 # directly.
279 fs.chdir(frame.prev_dir, change_os_dir=0)
280 rdir = frame.prev_dir.rdir()
281 rdir._create() # Make sure there's a directory there.
282 try:
283 os.chdir(rdir.get_abspath())
284 except OSError, e:
285 # We still couldn't chdir there, so raise the error,
286 # but only if actions are being executed.
287 #
288 # If the -n option was used, the directory would *not*
289 # have been created and we should just carry on and
290 # let things muddle through. This isn't guaranteed
291 # to work if the SConscript files are reading things
292 # from disk (for example), but it should work well
293 # enough for most configurations.
294 if SCons.Action.execute_actions:
295 raise e
296
297 results.append(frame.retval)
298
299 # if we only have one script, don't return a tuple
300 if len(results) == 1:
301 return results[0]
302 else:
303 return tuple(results)
304
306 """Print an exception stack trace just for the SConscript file(s).
307 This will show users who have Python errors where the problem is,
308 without cluttering the output with all of the internal calls leading
309 up to where we exec the SConscript."""
310 exc_type, exc_value, exc_tb = sys.exc_info()
311 tb = exc_tb
312 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
313 tb = tb.tb_next
314 if not tb:
315 # We did not find our exec statement, so this was actually a bug
316 # in SCons itself. Show the whole stack.
317 tb = exc_tb
318 stack = traceback.extract_tb(tb)
319 try:
320 type = exc_type.__name__
321 except AttributeError:
322 type = str(exc_type)
323 if type[:11] == "exceptions.":
324 type = type[11:]
325 file.write('%s: %s:\n' % (type, exc_value))
326 for fname, line, func, text in stack:
327 file.write(' File "%s", line %d:\n' % (fname, line))
328 file.write(' %s\n' % text)
329
331 """Annotate a node with the stack frame describing the
332 SConscript file and line number that created it."""
333 tb = sys.exc_info()[2]
334 while tb and not tb.tb_frame.f_locals.has_key(stack_bottom):
335 tb = tb.tb_next
336 if not tb:
337 # We did not find any exec of an SConscript file: what?!
338 raise SCons.Errors.InternalError, "could not find SConscript stack frame"
339 node.creator = traceback.extract_stack(tb)[0]
340
341 # The following line would cause each Node to be annotated using the
342 # above function. Unfortunately, this is a *huge* performance hit, so
343 # leave this disabled until we find a more efficient mechanism.
344 #SCons.Node.Annotate = annotate
345
347 """An Environment subclass that contains all of the methods that
348 are particular to the wrapper SCons interface and which aren't
349 (or shouldn't be) part of the build engine itself.
350
351 Note that not all of the methods of this class have corresponding
352 global functions, there are some private methods.
<