From 910a8c5c7ce20d39bb3111a5847396716286c778 Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Fri, 7 Nov 2003 04:46:59 +0000 Subject: Add BUILD_TARGETS, COMMAND_LINE_TARGETS and DEFAULT_TARGETS variables. --- doc/man/scons.1 | 148 +++++++++++++++++++++ doc/scons.mod | 3 + doc/user/command-line.in | 233 ++++++++++++++++++++++++++++++++- doc/user/command-line.sgml | 237 +++++++++++++++++++++++++++++++++- src/CHANGES.txt | 2 + src/engine/SCons/Environment.py | 13 -- src/engine/SCons/EnvironmentTests.py | 19 --- src/engine/SCons/Script/SConscript.py | 94 ++++++++++---- src/engine/SCons/Script/__init__.py | 20 +-- test/TARGETS.py | 109 ++++++++++++++++ 10 files changed, 800 insertions(+), 78 deletions(-) create mode 100644 test/TARGETS.py diff --git a/doc/man/scons.1 b/doc/man/scons.1 index ff2864c..453ff66 100644 --- a/doc/man/scons.1 +++ b/doc/man/scons.1 @@ -2374,6 +2374,13 @@ Later calls to will add to the (now empty) default-target list like normal. +The current list of targets added using the +.BR Default () +function or method is available in the +.B DEFAULT_TARGETS +list; +see below. + '\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .TP .RI DefaultEnvironment([ args ]) @@ -3567,6 +3574,147 @@ or the user's current PATHEXT (os.environ['PATHEXT']) by default. +.SS SConscript Variables +In addition to the global functions and methods, +.B scons +supports a number of Python variables +that can be used in SConscript files +to affect how you want the build to be performed. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +ARGUMENTS +A dictionary of all the +.IR keyword = value +arguments specified on the command line. +The dictionary is not in order, +and if a given keyword has +more than one value assigned to it +on the command line, +the last (right-most) value is +the one in the +.B ARGUMENTS +dictionary. + +.ES +if ARGUMENTS.get('debug', 0): + env = Environment(CCFLAGS = '-g') +else: + env = Environment() +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +BUILD_TARGETS +A list of the targets which +.B scons +will actually try to build, +regardless of whether they were specified on +the command line or via the +.BR Default () +function or method. +The elements of this list may be strings +.I or +nodes, so you should run the list through the Python +.B str +function to make sure any Node path names +are converted to strings. + +Because this list may be taken from the +list of targets specified using the +.BR Default () +function or method, +the contents of the list may change +on each successive call to +.BR Default (). +See the +.B DEFAULT_TARGETS +list, below, +for additional information. + +.ES +if 'foo' in BUILD_TARGETS: + print "Don't forget to test the `foo' program!" +if 'special/program' in BUILD_TARGETS: + SConscript('special') +.EE +.IP +Note that the +.B BUILD_TARGETS +list only contains targets expected listed +on the command line or via calls to the +.BR Default () +function or method. +It does +.I not +contain all dependent targets that will be built as +a result of making the sure the explicitly-specified +targets are up to date. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +COMMAND_LINE_TARGETS +A list of the targets explicitly specified on +the command line. +If there are no targets specified on the command line, +the list is empty. +This can be used, for example, +to take specific actions only +when a certain target or targets +is explicitly being built: + +.ES +if 'foo' in COMMAND_LINE_TARGETS: + print "Don't forget to test the `foo' program!" +if 'special/program' in COMMAND_LINE_TARGETS: + SConscript('special') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +DEFAULT_TARGETS +A list of the target +.I nodes +that have been specified using the +.BR Default () +function or method. +The elements of the list are nodes, +so you need to run them through the Python +.B str +function to get at the path name for each Node. + +.ES +print str(DEFAULT_TARGETS[0]) +if 'foo' in map(str, DEFAULT_TARGETS): + print "Don't forget to test the `foo' program!" +.EE +.IP +The contents of the +.B DEFAULT_TARGETS +list change on on each successive call to the +.BR Default () +function: + +.ES +print map(str, DEFAULT_TARGETS) # originally [] +Default('foo') +print map(str, DEFAULT_TARGETS) # now a node ['foo'] +Default('bar') +print map(str, DEFAULT_TARGETS) # now a node ['foo', 'bar'] +Default(None) +print map(str, DEFAULT_TARGETS) # back to [] +.EE +.IP +Consequently, be sure to use +.B DEFAULT_TARGETS +only after you've made all of your +.BR Default () +calls, +or else simply be careful of the order +of these statements in your SConscript files +so that you don't look for a specific +default target before it's actually been added to the list. + .SS Construction Variables .\" XXX From Gary Ruben, 23 April 2002: .\" I think it would be good to have an example with each construction diff --git a/doc/scons.mod b/doc/scons.mod index d9b955f..443be67 100644 --- a/doc/scons.mod +++ b/doc/scons.mod @@ -174,6 +174,9 @@ --> ARGUMENTS"> +BUILD_TARGETS"> +COMMAND_LINE_TARGETS"> +DEFAULT_TARGETS"> diff --git a/doc/user/command-line.in b/doc/user/command-line.in index 09ee8ce..9367c09 100644 --- a/doc/user/command-line.in +++ b/doc/user/command-line.in @@ -73,12 +73,71 @@
+ Getting at Command-Line Targets + + + + &SCons; supports a &COMMAND_LINE_TARGETS; variable + that lets you get at the list of targets that the + user specified on the command line. + You can use the targets to manipulate the + build in any way you wish. + As a simple example, + suppose that you want to print a reminder + to the user whenever a specific program is built. + You can do this by checking for the + target in the &COMMAND_LINE_TARGETS; list: + + + + + + if 'bar' in COMMAND_LINE_TARGETS: + print "Don't forget to copy `bar' to the archive!" + Default(Program('foo.c')) + Program('bar.c') + + + foo.c + + + foo.c + + + + + + Then, running &SCons; with the default target + works as it always does, + but explicity specifying the &bar; target + on the command line generates the warning message: + + + + + scons -Q + scons -Q bar + + + + + Another practical use for the &COMMAND_LINE_TARGETS; variable + might be to speed up a build + by only reading certain subsidiary &SConscript; + files if a specific target is requested. + + + +
+ +
Controlling the Default Targets One of the most basic things you can control - is which targets &SCons; will build by default. + is which targets &SCons; will build by default--that is, + when there are no targets specified on the command line. As mentioned previously, &SCons; will normally build every target in or below the current directory @@ -276,22 +335,182 @@ scons -Q . -
+
+ Getting at the List of Default Targets + + + + &SCons; supports a &DEFAULT_TARGETS; variable + that lets you get at the current list of default targets. + The &DEFAULT_TARGETS variable has + two important differences from the &COMMAND_LINE_TARGETS; variable. + First, the &DEFAULT_TARGETS; variable is a list of + internal &SCons; nodes, + so you need to convert the list elements to strings + if you want to print them or look for a specific target name. + Fortunately, you can do this easily + by using the Python map function + to run the list through str: - + + + &SCons;, however, provides a convenient + &BUILD_TARGETS; variable + that eliminates the need for this by-hand manipulation. + Essentially, the &BUILD_TARGETS; variable + contains a list of the command-line targets, + if any were specified, + and if no command-line targets were specified, + it contains a list of the targets specified + via the &Default; method or function. + + + + + + Because &BUILD_TARGETS; may contain a list of &SCons; nodes, + you must convert the list elements to strings + if you want to print them or look for a specific target name, + just like the &DEFAULT_TARGETS; list: + + + + + + prog1 = Program('prog1.c') + Program('prog2.c') + Default(prog1) + print "BUILD_TARGETS is", map(str, BUILD_TARGETS) + + + prog1.c + + + prog2.c + + + + + + Notice how the value of &BUILD_TARGETS; + changes depending on whether a target is + specified on the command line: + + + + + scons -Q + scons -Q prog2 + scons -Q -c . + + +
Command-Line <varname>variable</varname>=<varname>value</varname> Build Options diff --git a/doc/user/command-line.sgml b/doc/user/command-line.sgml index 3377b69..f6f219c 100644 --- a/doc/user/command-line.sgml +++ b/doc/user/command-line.sgml @@ -72,12 +72,68 @@
+ Getting at Command-Line Targets + + + + &SCons; supports a &COMMAND_LINE_TARGETS; variable + that lets you get at the list of targets that the + user specified on the command line. + You can use the targets to manipulate the + build in any way you wish. + As a simple example, + suppose that you want to print a reminder + to the user whenever a specific program is built. + You can do this by checking for the + target in the &COMMAND_LINE_TARGETS; list: + + + + + if 'bar' in COMMAND_LINE_TARGETS: + print "Don't forget to copy `bar' to the archive!" + Default(Program('foo.c')) + Program('bar.c') + + + + + Then, running &SCons; with the default target + works as it always does, + but explicity specifying the &bar; target + on the command line generates the warning message: + + + + + % scons -Q + cc -c -o foo.o foo.c + cc -o foo foo.o + % scons -Q bar + Don't forget to copy `bar' to the archive! + cc -c -o bar.o bar.c + cc -o bar bar.o + + + + + Another practical use for the &COMMAND_LINE_TARGETS; variable + might be to speed up a build + by only reading certain subsidiary &SConscript; + files if a specific target is requested. + + + +
+ +
Controlling the Default Targets One of the most basic things you can control - is which targets &SCons; will build by default. + is which targets &SCons; will build by default--that is, + when there are no targets specified on the command line. As mentioned previously, &SCons; will normally build every target in or below the current directory @@ -259,22 +315,189 @@ cc -o prog2 prog2.o -
+
+ Getting at the List of Default Targets + + + + &SCons; supports a &DEFAULT_TARGETS; variable + that lets you get at the current list of default targets. + The &DEFAULT_TARGETS variable has + two important differences from the &COMMAND_LINE_TARGETS; variable. + First, the &DEFAULT_TARGETS; variable is a list of + internal &SCons; nodes, + so you need to convert the list elements to strings + if you want to print them or look for a specific target name. + Fortunately, you can do this easily + by using the Python map function + to run the list through str: + + + + + prog1 = Program('prog1.c') + Default(prog1) + print "DEFAULT_TARGETS is", map(str, DEFAULT_TARGETS) + - +
Command-Line <varname>variable</varname>=<varname>value</varname> Build Options diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 2905339..8629589 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -25,6 +25,8 @@ RELEASE X.XX - XXX, XX XXX XXXX XX:XX:XX -XXXX - Fix subclassing the Environment and Scanner classes. + - Add BUILD_TARGETS, COMMAND_LINE_TARGETS and DEFAULT_TARGETS variables. + From Steve Leblanc: - SGI fixes: Fix C++ compilation, add a separate Tool/sgic++.py module. diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 6c6ad8a..26be483 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -64,7 +64,6 @@ class _Null: _null = _Null -DefaultTargets = None CleanTargets = {} CalculatorArgs = {} @@ -811,18 +810,6 @@ class Base: source_factory=self.fs.Entry) return bld(self, target, source) - def Default(self, *targets): - global DefaultTargets - if DefaultTargets is None: - DefaultTargets = [] - for t in targets: - if t is None: - DefaultTargets = [] - elif isinstance(t, SCons.Node.Node): - DefaultTargets.append(t) - else: - DefaultTargets.extend(self.arg2nodes(t, self.fs.Entry)) - def Depends(self, target, dependency): """Explicity specify that 'target's depend on 'dependency'.""" tlist = self.arg2nodes(target, self.fs.Entry) diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py index 2861f85..e6fd5c9 100644 --- a/src/engine/SCons/EnvironmentTests.py +++ b/src/engine/SCons/EnvironmentTests.py @@ -1484,25 +1484,6 @@ class EnvironmentTestCase(unittest.TestCase): finally: os.chdir(save) - def test_Default(self): - """Test the Default() method""" - env = Environment(FOO = 'fff', BAR = 'bbb') - - t = env.Default(None) - assert SCons.Environment.DefaultTargets == [] - - t = env.Default('xyz') - d = map(str, SCons.Environment.DefaultTargets) - assert d == ['xyz'], d - - t = env.Default('$FOO') - d = map(str, SCons.Environment.DefaultTargets) - assert d == ['xyz', 'fff'], d - - t = env.Default(None, '$BAR', 'another_file') - d = map(str, SCons.Environment.DefaultTargets) - assert d == ['bbb', 'another_file'], d - def test_Depends(self): """Test the explicit Depends method.""" env = Environment(FOO = 'xxx', BAR='yyy') diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py index fff1869..8894df7 100644 --- a/src/engine/SCons/Script/SConscript.py +++ b/src/engine/SCons/Script/SConscript.py @@ -53,13 +53,27 @@ import string import sys import traceback import types +import UserList + +launch_dir = os.path.abspath(os.curdir) def do_nothing(text): pass HelpFunction = do_nothing -arguments = {} +Arguments = {} +CommandLineTargets = [] +DefaultCalled = None +DefaultTargets = [] GlobalDict = {} -launch_dir = os.path.abspath(os.curdir) + +class TargetList(UserList.UserList): + def _do_nothing(self, *args, **kw): + pass + def _add_Default(self, list): + self.extend(list) + def _clear(self): + del self[:] +BuildTargets = TargetList() # global exports set by Export(): global_exports = {} @@ -71,10 +85,16 @@ sconscript_chdir = 1 sconscript_reading = 0 def _scons_add_args(alist): - global arguments for arg in alist: a, b = string.split(arg, '=', 2) - arguments[a] = b + Arguments[a] = b + +def _scons_add_targets(tlist): + if tlist: + CommandLineTargets.extend(tlist) + BuildTargets.extend(tlist) + BuildTargets._add_Default = BuildTargets._do_nothing + BuildTargets._clear = BuildTargets._do_nothing def get_calling_namespaces(): """Return the locals and globals for the function that called @@ -387,6 +407,26 @@ class SConsEnvironment(SCons.Environment.Base): # as global functions. # + def Default(self, *targets): + global DefaultCalled + global DefaultTargets + DefaultCalled = 1 + for t in targets: + if t is None: + # Delete the elements from the list in-place, don't + # reassign an empty list to DefaultTargets, so that the + # DEFAULT_TARGETS variable will still point to the + # same object we point to. + del DefaultTargets[:] + BuildTargets._clear() + elif isinstance(t, SCons.Node.Node): + DefaultTargets.append(t) + BuildTargets._add_Default([t]) + else: + nodes = self.arg2nodes(t, self.fs.Entry) + DefaultTargets.extend(nodes) + BuildTargets._add_Default(nodes) + def EnsureSConsVersion(self, major, minor): """Exit abnormally if the SCons version is not late enough.""" if not self._check_version(major,minor,SCons.__version__): @@ -460,7 +500,7 @@ class SConsEnvironment(SCons.Environment.Base): # SCons.Environment.Environment = SConsEnvironment -def Options(files=None, args=arguments): +def Options(files=None, args=Arguments): return SCons.Options.Options(files, args) def SetBuildSignatureType(type): @@ -545,6 +585,7 @@ class DefaultEnvironmentCall: # DefaultEnvironment(). GlobalDefaultEnvironmentFunctions = [ # Methods from the SConsEnvironment class, above. + 'Default', 'EnsurePythonVersion', 'EnsureSConsVersion', 'Exit', @@ -566,7 +607,6 @@ GlobalDefaultEnvironmentFunctions = [ 'CacheDir', 'Clean', 'Command', - 'Default', 'Depends', 'Dir', 'File', @@ -624,23 +664,31 @@ def BuildDefaultGlobals(): SConstruct and SConscript files. """ - globals = {} - globals['Action'] = SCons.Action.Action - globals['ARGUMENTS'] = arguments - globals['BoolOption'] = SCons.Options.BoolOption - globals['Builder'] = SCons.Builder.Builder - globals['Configure'] = SCons.SConf.SConf - globals['EnumOption'] = SCons.Options.EnumOption - globals['Environment'] = SCons.Environment.Environment - globals['ListOption'] = SCons.Options.ListOption - globals['Options'] = Options - globals['PackageOption'] = SCons.Options.PackageOption - globals['PathOption'] = SCons.Options.PathOption - globals['Platform'] = SCons.Platform.Platform - globals['Return'] = Return - globals['Scanner'] = SCons.Scanner.Base - globals['Tool'] = SCons.Tool.Tool - globals['WhereIs'] = SCons.Util.WhereIs + globals = { + # Global functions that don't get executed through the + # default Environment. + 'Action' : SCons.Action.Action, + 'BoolOption' : SCons.Options.BoolOption, + 'Builder' : SCons.Builder.Builder, + 'Configure' : SCons.SConf.SConf, + 'EnumOption' : SCons.Options.EnumOption, + 'Environment' : SCons.Environment.Environment, + 'ListOption' : SCons.Options.ListOption, + 'Options' : Options, + 'PackageOption' : SCons.Options.PackageOption, + 'PathOption' : SCons.Options.PathOption, + 'Platform' : SCons.Platform.Platform, + 'Return' : Return, + 'Scanner' : SCons.Scanner.Base, + 'Tool' : SCons.Tool.Tool, + 'WhereIs' : SCons.Util.WhereIs, + + # Other variables we provide. + 'ARGUMENTS' : Arguments, + 'BUILD_TARGETS' : BuildTargets, + 'COMMAND_LINE_TARGETS' : CommandLineTargets, + 'DEFAULT_TARGETS' : DefaultTargets, + } # Functions we might still convert to Environment methods. globals['CScan'] = SCons.Defaults.CScan diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py index a533c29..71ed2be 100644 --- a/src/engine/SCons/Script/__init__.py +++ b/src/engine/SCons/Script/__init__.py @@ -753,6 +753,7 @@ def _main(args, parser): else: targets.append(a) SCons.Script.SConscript._scons_add_args(xmit_args) + SCons.Script.SConscript._scons_add_targets(targets) target_top = None if options.climb_up: @@ -865,6 +866,7 @@ def _main(args, parser): # There are no targets specified on the command line, # so if they used -u, -U or -D, we may have to restrict # what actually gets built. + d = None if target_top: if options.climb_up == 1: # -u, local directory and below @@ -886,18 +888,18 @@ def _main(args, parser): # or not a file, so go ahead and keep it as a default # target and let the engine sort it out: return 1 - default_targets = SCons.Environment.DefaultTargets - if default_targets is None: - default_targets = [] - else: - default_targets = filter(check_dir, default_targets) - SCons.Environment.DefaultTargets = default_targets + d = filter(check_dir, SCons.Script.SConscript.DefaultTargets) + SCons.Script.SConscript.DefaultTargets[:] = d target_top = None lookup_top = None - targets = SCons.Environment.DefaultTargets - if targets is None: - targets = [fs.Dir('.')] + if SCons.Script.SConscript.DefaultCalled: + targets = SCons.Script.SConscript.DefaultTargets + else: + if d is None: + d = [fs.Dir('.')] + targets = d + if not targets: sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n") diff --git a/test/TARGETS.py b/test/TARGETS.py new file mode 100644 index 0000000..5c73d81 --- /dev/null +++ b/test/TARGETS.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +""" +Test use of the COMMAND_LINE_TARGETS and DEFAULT_TARGETS variables. +""" + +import TestSCons + +test = TestSCons.TestSCons() + +test.write('SConstruct', """ +print COMMAND_LINE_TARGETS +print map(str, BUILD_TARGETS) +Default('.') +print COMMAND_LINE_TARGETS +print map(str, BUILD_TARGETS) +""") + +test.write('aaa', 'aaa\n') +test.write('bbb', 'bbb\n') + +expect = test.wrap_stdout(read_str = "[]\n[]\n[]\n['.']\n", + build_str = "scons: `.' is up to date.\n") +test.run(stdout = expect) + +expect = test.wrap_stdout(read_str = "['.']\n['.']\n['.']\n['.']\n", + build_str = "scons: `.' is up to date.\n") +test.run(arguments = '.', stdout = expect) + +expect = test.wrap_stdout(read_str = "['aaa']\n['aaa']\n['aaa']\n['aaa']\n", + build_str = "scons: Nothing to be done for `aaa'.\n") +test.run(arguments = 'aaa', stdout = expect) + +expect = test.wrap_stdout(read_str = "['bbb', 'aaa']\n['bbb', 'aaa']\n['bbb', 'aaa']\n['bbb', 'aaa']\n", + build_str = """\ +scons: Nothing to be done for `bbb'. +scons: Nothing to be done for `aaa'. +""") +test.run(arguments = 'bbb ccc=xyz -n aaa', stdout = expect) + +test.write('SConstruct', """ +env = Environment() +print map(str, DEFAULT_TARGETS) +print map(str, BUILD_TARGETS) +Default('aaa') +print map(str, DEFAULT_TARGETS) +print map(str, BUILD_TARGETS) +env.Default('bbb') +print map(str, DEFAULT_TARGETS) +print map(str, BUILD_TARGETS) +env.Default(None) +print map(str, DEFAULT_TARGETS) +print map(str, BUILD_TARGETS) +env.Default('ccc') +""") + +test.write('ccc', "ccc\n") + +expect = test.wrap_stdout(build_str = "scons: Nothing to be done for `ccc'.\n", + read_str = """\ +[] +[] +['aaa'] +['aaa'] +['aaa', 'bbb'] +['aaa', 'bbb'] +[] +[] +""") +test.run(stdout = expect) + +expect = test.wrap_stdout(build_str = "scons: `.' is up to date.\n", + read_str = """\ +[] +['.'] +['aaa'] +['.'] +['aaa', 'bbb'] +['.'] +[] +['.'] +""") +test.run(arguments = '.', stdout = expect) + +test.pass_test() -- cgit v0.12