diff --git a/CHANGES.txt b/CHANGES.txt
index 47f95709d1..59d6d35fde 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -45,6 +45,14 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
- Fix Appveyor scripting to install unavailable python versions when needed and use them
for testing.
+ From Dillan Mills:
+ - Fix handling of AddOption for option-arguments with spaces
+ (when omitting the = sign or when an option takes multiple
+ option-arguments). Arguments unknown at the time the first pass
+ splits the command line into arguments and targets would put such
+ arguments into targets, and they remained there even after the
+ AddOption calls were seen. Closes #2748, #2805, #2977.
+
From Mats Wichmann:
- Introduce some unit tests for the file locking utility routines
- More clarifications in manpage Builder Methods section.
@@ -81,6 +89,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
on a one-time uuid to make a path to the file.
- Clarify VariantDir behavior when switching to not duplicate sources
and tweak wording a bit.
+ - Complete the work from PR #3799 on AddOption handling (original work
+ credited to Dillan Mills)
- Update documentation of the three file-finding functions in the API
(FindFile, FindInstalledFiles, FindSourceFiles) as well as FindPathDirs.
Also add a test for FindFile to be sure it locates non-existing
diff --git a/RELEASE.txt b/RELEASE.txt
index f0d983f00b..2d47a2fae7 100644
--- a/RELEASE.txt
+++ b/RELEASE.txt
@@ -51,6 +51,13 @@ FIXES
directory for each writer before doing the move, instead of depending
on a one-time uuid to make a path to the file.
+- Fix handling of AddOption for option-arguments with spaces (when omitting
+ the = sign or when an option takes multiple option-arguments). Arguments
+ unknown at the time the first pass splits the command line into arguments
+ and targets would put such arguments into targets, and they remained
+ there even after the AddOption calls were seen.
+
+
IMPROVEMENTS
------------
diff --git a/SCons/Script/Main.xml b/SCons/Script/Main.xml
index 21f121d408..e9b1f81ef7 100644
--- a/SCons/Script/Main.xml
+++ b/SCons/Script/Main.xml
@@ -172,22 +172,17 @@ see the &f-Help; documentation for details.
-As an artifact of the internal implementation,
-the behavior of options added by &AddOption;
-which take option arguments is undefined
-if whitespace
-(rather than an = sign) is used as
-the separator on the command line.
-Users should avoid such usage; it is recommended
-to add a note to this effect to project documentation
-if the situation is likely to arise.
-In addition, if the nargs
-keyword is used to specify more than one following
-option argument (that is, with a value of 2
-or greater), such arguments would necessarily
-be whitespace separated, triggering the issue.
-Developers should not use &AddOption; this way.
-Future versions of &SCons; will likely forbid such usage.
+Prior to version NEXT_RELEASE,
+the behavior when options added by &AddOption;
+are specified with whitespace was undefined and discouraged.
+Such usage covers both the use of a space as the separator
+(--opt arg vs
+--opt=arg),
+and where the number of option arguments
+(nargs) is specified as greater than one.
+While this issue has been corrected,
+developers should be aware of it in case the
+project is built with an older &SCons; version.
diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py
index 52d7fca52e..3ed89b51fd 100644
--- a/SCons/Script/SConsOptions.py
+++ b/SCons/Script/SConsOptions.py
@@ -405,9 +405,18 @@ def _process_long_opt(self, rargs, values) -> None:
% (opt, nargs))
elif nargs == 1:
value = rargs.pop(0)
+ if not had_explicit_value:
+ SCons.Script._Remove_Target(value)
+ if '=' in value:
+ SCons.Script._Remove_Argument(value)
else:
value = tuple(rargs[0:nargs])
del rargs[0:nargs]
+ for i in range(len(value)):
+ if not had_explicit_value or i > 0:
+ SCons.Script._Remove_Target(value[i])
+ if '=' in value[i]:
+ SCons.Script._Remove_Argument(value[i])
elif had_explicit_value:
self.error(_("%s option does not take a value") % opt)
@@ -447,11 +456,13 @@ def _process_short_opts(self, rargs, values) -> None:
raise
if option.takes_value():
+ had_explicit_value = False
# Any characters left in arg? Pretend they're the
# next arg, and stop consuming characters of arg.
if i < len(arg):
rargs.insert(0, arg[i:])
stop = True
+ had_explicit_value = True
nargs = option.nargs
if len(rargs) < nargs:
@@ -462,9 +473,19 @@ def _process_short_opts(self, rargs, values) -> None:
% (opt, nargs))
elif nargs == 1:
value = rargs.pop(0)
+ if not had_explicit_value:
+ SCons.Script._Remove_Target(value)
+ if '=' in value:
+ SCons.Script._Remove_Argument(value)
else:
value = tuple(rargs[0:nargs])
del rargs[0:nargs]
+ for i in range(len(value)):
+ if not had_explicit_value or i > 0:
+ SCons.Script._Remove_Target(value[i])
+ if '=' in value[i]:
+ SCons.Script._Remove_Argument(value[i])
+
else: # option doesn't take a value
value = None
@@ -475,67 +496,6 @@ def _process_short_opts(self, rargs, values) -> None:
break
- def reparse_local_options(self) -> None:
- """Re-parse the leftover command-line options.
-
- Leftover options are stored in ``self.largs``, so that any value
- overridden on the command line is immediately available
- if the user turns around and does a :func:`~SCons.Script.Main.GetOption`
- right away.
-
- We mimic the processing of the single args
- in the original OptionParser :func:`_process_args`, but here we
- allow exact matches for long-opts only (no partial argument names!).
- Otherwise there could be problems in :meth:`add_local_option`
- below. When called from there, we try to reparse the
- command-line arguments that haven't been processed so far
- (``self.largs``), but are possibly not added to the options list yet.
-
- So, when we only have a value for ``--myargument`` so far,
- a command-line argument of ``--myarg=test`` would set it,
- per the behaviour of :func:`_match_long_opt`,
- which allows for partial matches of the option name,
- as long as the common prefix appears to be unique.
- This would lead to further confusion, because we might want
- to add another option ``--myarg`` later on (see issue #2929).
- """
- rargs = []
- largs_restore = []
- # Loop over all remaining arguments
- skip = False
- for larg in self.largs:
- if skip:
- # Accept all remaining arguments as they are
- largs_restore.append(larg)
- else:
- if len(larg) > 2 and larg[0:2] == "--":
- # Check long option
- lopt = [larg]
- if "=" in larg:
- # Split into option and value
- lopt = larg.split("=", 1)
-
- if lopt[0] in self._long_opt:
- # Argument is already known
- rargs.append('='.join(lopt))
- else:
- # Not known yet, so reject for now
- largs_restore.append('='.join(lopt))
- else:
- if larg in("--", "-"):
- # Stop normal processing and don't
- # process the rest of the command-line opts
- largs_restore.append(larg)
- skip = True
- else:
- rargs.append(larg)
-
- # Parse the filtered list
- self.parse_args(rargs, self.values)
- # Restore the list of leftover arguments for the
- # next call of AddOption/add_local_option...
- self.largs = self.largs + largs_restore
-
def add_local_option(self, *args, **kw) -> SConsOption:
"""Add a local option to the parser.
@@ -574,7 +534,7 @@ def add_local_option(self, *args, **kw) -> SConsOption:
# right away.
# TODO: what if dest is None?
setattr(self.values.__defaults__, result.dest, result.default)
- self.reparse_local_options()
+ self.parse_args(self.largs, self.values)
if result.settable:
SConsValues.settable.append(result.dest)
diff --git a/SCons/__init__.py b/SCons/__init__.py
index 9d0773be07..232476509d 100644
--- a/SCons/__init__.py
+++ b/SCons/__init__.py
@@ -1,9 +1,9 @@
__version__="4.10.2"
__copyright__="Copyright (c) 2001 - 2026 The SCons Foundation"
-__developer__="bdbaddog"
-__date__="Sat, 03 Jan 2026 14:09:24 -0700"
-__buildsys__="M1Dog2021"
-__revision__="9573362eaf4dadc6368ad27a75bc2790a3e5e813"
-__build__="9573362eaf4dadc6368ad27a75bc2790a3e5e813"
+__developer__="mats"
+__date__="Wed, 15 Apr 2026 13:01:10 -0600"
+__buildsys__="boulder"
+__revision__="359b2fbd043519b0609acdc2deffc609d31095b3"
+__build__="359b2fbd043519b0609acdc2deffc609d31095b3"
# make sure compatibility is always in place
import SCons.compat # noqa
\ No newline at end of file
diff --git a/doc/user/command-line.xml b/doc/user/command-line.xml
index 8e0e0a9de1..e61a8bdec2 100644
--- a/doc/user/command-line.xml
+++ b/doc/user/command-line.xml
@@ -741,51 +741,34 @@ foo.in
+
- The optparse parser which &SCons; uses
- allows option-arguments to follow their options after either
- an = or space separator,
- however the latter form does not work well in &SCons; for
- added options and should be avoided.
- &SCons; does not place an ordering constraint on the
- types of command-line arguments,
- so while is unambiguous,
- for
- it is not possible to tell without instructions whether
- ARG is an argument belonging to the
- input option or a standalone word.
- &SCons; considers words on the command line which do not
- begin with hyphen as either command-line build variables
- or command-line targets,
- both of which are made available for use in an &SConscript;
- (see the immediately following sections for details).
- Thus, they must be collected before &SConscript; processing
- takes place. &AddOption; calls do provide the
- necessary instructions to resolve the ambiguity,
- but as they appear in &SConscript; files,
- &SCons; does not have the information early enough,
- and unexpected things may happen,
- such as option-arguments appearing in the list of targets,
- and processing exceptions due to missing option-arguments.
-
-
- As a result,
- this usage style should be avoided when invoking &scons;.
- For single-argument options,
- tell your users to use the
- form on the command line.
- For multiple-argument options
- (nargs value greater than one),
- set nargs to one in the
- &AddOption; call and either: combine the option-arguments into one word
- with a separator, and parse the result in your own code
- (see the built-in option, which
- allows specifying multiple arguments as a single comma-separated
- word, for an example of such usage); or allow the option to
- be specified multiple times by setting
- action='append'. Both methods can be
- supported at the same time.
+ Prior to version NEXT_RELEASE,
+ the behavior when options added by &AddOption;
+ are specified on the command line with spaces was undefined,
+ and such usage was discouraged.
+ This applies to both using a space as the separator
+ (--opt arg vs
+ --opt=arg),
+ and when more than one option arguments is required.
+ In case your project may need to be built with an older
+ &SCons; version, you can take steps to avoid problems.
+ You cannot programmatically avoid the former case,
+ since it is allowed by the underlying &Python; module
+ that performs the command-line parsing,
+ but you can tell your users not to call that way.
+ For the latter case,
+ you can set the nargs to 1,
+ tell users to supply the arguments in comma-separated form
+ (--opt=arg1,arg2 instead of
+ --opt arg1 arg2),
+ and validate/parse that in the build script,
+ or alternatively,
+ allow the option to be specified multiple times by setting
+ action='append'.
+ Both methods can be supported at the same time.
+
diff --git a/test/AddOption/.exclude_tests b/test/AddOption/.exclude_tests
deleted file mode 100644
index acc32f50ed..0000000000
--- a/test/AddOption/.exclude_tests
+++ /dev/null
@@ -1,4 +0,0 @@
-# for now, the tests showing problems with processing space-separated
-# arguments are excluded, pending an implementation that doesn't fail.
-args-and-targets.py
-multi-arg.py
diff --git a/test/AddOption/args-and-targets.py b/test/AddOption/args-and-targets.py
index 900a42f89b..6581989191 100644
--- a/test/AddOption/args-and-targets.py
+++ b/test/AddOption/args-and-targets.py
@@ -30,14 +30,12 @@
import TestSCons
-test = TestSCons.TestSCons()
+test = TestSCons.TestSCons(match = TestSCons.match_re_dotall)
-test.write(
- 'SConstruct',
- """\
+test.write('SConstruct', """\
DefaultEnvironment(tools=[])
-env = Environment(tools=[])
AddOption(
+ '-x',
'--extra',
nargs=1,
dest='extra',
@@ -53,8 +51,391 @@
)
# arg using =
-test.run('-Q -q --extra=A TARG', status=1, stdout="A\n['TARG']\n")
+test.run('-Q -q --extra=A TARG', status=1, stdout=r"A\n\['TARG'\]\n")
# arg not using =
-test.run('-Q -q --extra A TARG', status=1, stdout="A\n['TARG']\n")
+test.run('-Q -q --extra A TARG', status=1, stdout=r"A\n\['TARG'\]\n")
+# short arg with space
+test.run('-Q -q -x A TARG', status=1, stdout=r"A\n\['TARG'\]\n")
+# short arg with no space
+test.run('-Q -q -xA TARG', status=1, stdout=r"A\n\['TARG'\]\n")
+
+test.write('SConstruct1', """\
+DefaultEnvironment(tools=[])
+AddOption(
+ '-x',
+ '--extra',
+ nargs=2,
+ dest='extra',
+ action='append',
+ type='string',
+ metavar='ARG1',
+ default=[],
+ help='An argument to the option',
+)
+print(str(GetOption('extra')))
+print(COMMAND_LINE_TARGETS)
+""",
+)
+
+# many args and opts
+test.run(
+ '-f SConstruct1 -Q -q --extra=A B TARG1 -x C D TARG2 -xE F TARG3 --extra G H TARG4',
+ status=1,
+ stdout=r"\[\('A', 'B'\), \('C', 'D'\), \('E', 'F'\), \('G', 'H'\)\]\n\['TARG1', 'TARG2', 'TARG3', 'TARG4'\]\n",
+)
+
+test.write('SConstruct2', """\
+DefaultEnvironment(tools=[])
+AddOption('-x', '--extra',
+ nargs=1,
+ dest='extra',
+ action='store',
+ type='string',
+ metavar='ARG1',
+ default=(),
+ help='An argument to the option')
+print(str(GetOption('extra')))
+print(COMMAND_LINE_TARGETS)
+print(ARGUMENTS.get('A', None))
+""",
+)
+
+# opt value and target are same name
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra=TARG1 TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra TARG1 TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -xTARG1 TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -x TARG1 TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+
+# target first
+test.run(
+ arguments='-f SConstruct2 -Q -q TARG1 --extra=TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q TARG1 --extra TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q TARG1 -xTARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q TARG1 -x TARG1',
+ status=1,
+ stdout=r"TARG1\n\['TARG1'\]\nNone\n",
+)
+
+# equals in opt value
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra=A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -xA=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nNone\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -x A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nNone\n",
+)
+
+# equals in opt value and a different argument
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra=A=B A=C TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra A=B A=C TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -xA=B A=C TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -x A=B A=C TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+
+# different argument first
+test.run(
+ arguments='-f SConstruct2 -Q -q A=C --extra=A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q A=C --extra A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q A=C -xA=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q A=C -x A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nC\n",
+)
+
+# equals in opt value and the same as an argument
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra=A=B A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q --extra A=B A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -xA=B A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q -x A=B A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+
+# same argument first
+test.run(
+ arguments='-f SConstruct2 -Q -q A=B --extra=A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q A=B --extra A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q A=B -xA=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+test.run(
+ arguments='-f SConstruct2 -Q -q A=B -x A=B TARG1',
+ status=1,
+ stdout=r"A=B\n\['TARG1'\]\nB\n",
+)
+
+test.write('SConstruct3', """\
+DefaultEnvironment(tools=[])
+AddOption(
+ '-x',
+ '--extra',
+ nargs=1,
+ dest='extra',
+ action='store',
+ type='string',
+ metavar='ARG1',
+ default=(),
+ help='An argument to the option',
+)
+if 'A' in BUILD_TARGETS:
+ BUILD_TARGETS.append('B')
+print(str(GetOption('extra')))
+print(BUILD_TARGETS)
+""",
+)
+
+# Nested target
+test.run(
+ arguments='-f SConstruct3 -Q -q -x A TARG1', status=1, stdout=r"A\n\['TARG1'\]\n"
+)
+test.run(
+ arguments='-f SConstruct3 -Q -q -x A A TARG1',
+ status=1,
+ stdout=r"A\n\['A', 'TARG1', 'B'\]\n",
+)
+test.run(
+ arguments='-f SConstruct3 -Q -q A -x A TARG1',
+ status=1,
+ stdout=r"A\n\['A', 'TARG1', 'B'\]\n",
+)
+
+test.write('SConstruct4', """\
+DefaultEnvironment(tools=[])
+AddOption(
+ '-x',
+ '--extra',
+ nargs=1,
+ dest='extra',
+ action='store',
+ type='string',
+ metavar='ARG1',
+ default=(),
+ help='An argument to the option',
+)
+if 'A' in BUILD_TARGETS:
+ AddOption(
+ '--foo',
+ nargs=1,
+ dest='foo',
+ action='store',
+ type='string',
+ metavar='FOO1',
+ default=(),
+ help='An argument to the option',
+ )
+print(str(GetOption('extra')))
+print(str(GetOption('foo')))
+print(COMMAND_LINE_TARGETS)
+""",
+)
+
+# nested option
+expect = r"""AttributeError: 'Values' object has no attribute 'foo':
+ File ".+SConstruct4", line \d+:
+ print\(str\(GetOption\('foo'\)\)\)
+ File "[^"]+Main.py", line \d+:
+ return getattr\(OptionsParser.values, name\)
+ File "[^"]+SConsOptions.py", line \d+:
+ return getattr\(self.__dict__\['__defaults__'\], attr\)
+"""
+test.run(
+ arguments='-f SConstruct4 -Q -q -x A --foo=C TARG1',
+ status=2,
+ stdout=r"A\n",
+ stderr=expect,
+)
+test.run(
+ arguments='-f SConstruct4 -Q -q -x A A --foo=C TARG1',
+ status=1,
+ stdout=r"A\nC\n\['A', 'TARG1'\]\n",
+)
+test.run(
+ arguments='-f SConstruct4 -Q -q A -x A --foo=C TARG1',
+ status=1,
+ stdout=r"A\nC\n\['A', 'TARG1'\]\n",
+)
+
+############################################
+################ Fail Case #################
+############################################
+
+test.write('SConstruct5', """\
+DefaultEnvironment(tools=[])
+AddOption(
+ '-x',
+ '--extra',
+ nargs=1,
+ dest='extra',
+ action='store',
+ type='string',
+ metavar='ARG1',
+ default=(),
+ help='An argument to the option',
+)
+if 'A' in BUILD_TARGETS:
+ BUILD_TARGETS.append('B')
+print(str(GetOption('extra')))
+print(BUILD_TARGETS)
+""")
+
+# Nested target
+test.run(
+ arguments='-f SConstruct5 -Q -q -x A TARG1', status=1, stdout=r"A\n\['TARG1'\]\n"
+)
+test.run(
+ arguments='-f SConstruct5 -Q -q -x A A TARG1',
+ status=1,
+ stdout=r"A\n\['A', 'TARG1', 'B'\]\n",
+)
+test.run(
+ arguments='-f SConstruct5 -Q -q A -x A TARG1',
+ status=1,
+ stdout=r"A\n\['A', 'TARG1', 'B'\]\n",
+)
+
+test.write('SConstruct6', """\
+DefaultEnvironment(tools=[])
+AddOption(
+ '-x',
+ '--extra',
+ nargs=1,
+ dest='extra',
+ action='store',
+ type='string',
+ metavar='ARG1',
+ default=(),
+ help='An argument to the option',
+)
+if 'A' in BUILD_TARGETS:
+ AddOption(
+ '--foo',
+ nargs=1,
+ dest='foo',
+ action='store',
+ type='string',
+ metavar='FOO1',
+ default=(),
+ help='An argument to the option',
+ )
+print(str(GetOption('extra')))
+print(str(GetOption('foo')))
+print(COMMAND_LINE_TARGETS)
+""")
+
+# nested option
+expect=r"""AttributeError: 'Values' object has no attribute 'foo':
+ File ".+SConstruct6", line \d+:
+ print\(str\(GetOption\('foo'\)\)\)
+ File "[^"]+Main.py", line \d+:
+ return getattr\(OptionsParser.values, name\)
+ File "[^"]+SConsOptions.py", line \d+:
+ return getattr\(self.__dict__\['__defaults__'\], attr\)
+"""
+test.run(
+ arguments='-f SConstruct6 -Q -q -x A --foo=C TARG1',
+ status=2,
+ stdout=r"A\n",
+ stderr=expect,
+)
+test.run(
+ arguments='-f SConstruct6 -Q -q -x A A --foo=C TARG1',
+ status=1,
+ stdout=r"A\nC\n\['A', 'TARG1'\]\n",
+)
+test.run(
+ arguments='-f SConstruct6 -Q -q A -x A --foo=C TARG1',
+ status=1,
+ stdout=r"A\nC\n\['A', 'TARG1'\]\n",
+)
test.pass_test()
diff --git a/test/AddOption/multi-arg.py b/test/AddOption/multi-arg.py
index f625277a18..26d18fba2e 100644
--- a/test/AddOption/multi-arg.py
+++ b/test/AddOption/multi-arg.py
@@ -33,46 +33,50 @@
test = TestSCons.TestSCons()
# First, test an option with nargs=2 and no others:
-test.write(
- 'SConstruct',
- """\
+test.write('SConstruct', """\
DefaultEnvironment(tools=[])
-AddOption('--extras',
- nargs=2,
- dest='extras',
- action='store',
- type='string',
- metavar='FILE1 FILE2',
- default=(),
- help='two extra files to install')
-print(str(GetOption('extras')))
-""",
+AddOption(
+ '-x',
+ '--extras',
+ nargs=2,
+ dest='extras',
+ action='store',
+ type='string',
+ metavar='FILE1 FILE2',
+ default=(),
+ help='two extra files to install',
)
+print(str(GetOption('extras')))
+print(COMMAND_LINE_TARGETS)
+""")
# no args
-test.run('-Q -q .', stdout="()\n")
+test.run('-Q -q .', stdout="()\n['.']\n")
# one arg, should fail
-test.run(
- '-Q -q . --extras A',
- status=2,
- stderr="""\
+test.run('-Q -q . --extras A', status=2, stderr="""\
usage: scons [OPTIONS] [VARIABLES] [TARGETS]
SCons Error: --extras option requires 2 arguments
-""",
-)
+""")
+#one arg, short option
+test.run('-Q -q . -x A', status=2, stderr="""\
+usage: scons [OPTIONS] [VARIABLES] [TARGETS]
+
+SCons Error: -x option requires 2 arguments
+""")
# two args
-test.run('-Q -q . --extras A B', status=1, stdout="('A', 'B')\n")
+test.run('-Q -q . --extras A B', stdout="('A', 'B')\n['.']\n")
+# two args, short option
+test.run('-Q -q . -x A B', stdout="('A', 'B')\n['.']\n")
# -- means the rest are not processed as args
-test.run('-Q -q . -- --extras A B', status=1, stdout="()\n")
+test.run('-Q -q . -- --extras A B', status=1, stdout="()\n['.', '--extras', 'A', 'B']\n")
# Now test what has been a bug: another option is
# also defined, this impacts the collection of args for the nargs>1 opt
-test.write(
- 'SConstruct',
- """\
+test.write('SConstruct', """\
DefaultEnvironment(tools=[])
AddOption(
+ '-P',
'--prefix',
nargs=1,
dest='prefix',
@@ -82,6 +86,7 @@
help='installation prefix',
)
AddOption(
+ '-x',
'--extras',
nargs=2,
dest='extras',
@@ -93,26 +98,38 @@
)
print(str(GetOption('prefix')))
print(str(GetOption('extras')))
-""",
-)
-
-# no options
-test.run('-Q -q .', stdout="None\n()\n")
-# one single-arg option
-test.run('-Q -q . --prefix=/home/foo', stdout="/home/foo\n()\n")
-# one two-arg option
-test.run('-Q -q . --extras A B', status=2, stdout="None\n('A', 'B')\n")
-# single-arg option followed by two-arg option
+print(COMMAND_LINE_TARGETS)
+""")
+# no opts
+test.run('-Q -q .', stdout="None\n()\n['.']\n")
+# first opt long, one arg
+test.run('-Q -q . --prefix=/home/foo', stdout="/home/foo\n()\n['.']\n")
+test.run('-Q -q . --prefix /home/foo', stdout="/home/foo\n()\n['.']\n")
+# first opt short, one arg
+test.run('-Q -q . -P/home/foo', stdout="/home/foo\n()\n['.']\n")
+test.run('-Q -q . -P /home/foo', stdout="/home/foo\n()\n['.']\n")
+# second opt long, two args
+test.run('-Q -q . --extras=A B', stdout="None\n('A', 'B')\n['.']\n")
+test.run('-Q -q . --extras A B', stdout="None\n('A', 'B')\n['.']\n")
+# second opt short, two args
+test.run('-Q -q . -xA B', stdout="None\n('A', 'B')\n['.']\n")
+test.run('-Q -q . -x A B', stdout="None\n('A', 'B')\n['.']\n")
+# both opts long
+test.run('-Q -q . --prefix=/home/foo --extras=A B', stdout="/home/foo\n('A', 'B')\n['.']\n")
+test.run('-Q -q . --prefix /home/foo --extras A B', stdout="/home/foo\n('A', 'B')\n['.']\n")
+# both opts short
+test.run('-Q -q . -P/home/foo -xA B', stdout="/home/foo\n('A', 'B')\n['.']\n")
+test.run('-Q -q . -P /home/foo -x A B', stdout="/home/foo\n('A', 'B')\n['.']\n")
+# don't process
test.run(
- '-Q -q . --prefix=/home/foo --extras A B',
+ '-Q -q . -- --prefix=/home/foo --extras=A B',
status=1,
- stdout="/home/foo\n('A', 'B')\n",
+ stdout="None\n()\n['.', '--prefix=/home/foo', '--extras=A', 'B']\n",
)
-# two-arg option followed by single-arg option
test.run(
- '-Q -q . --extras A B --prefix=/home/foo',
+ '-Q -q . -- --prefix /home/foo --extras A B',
status=1,
- stdout="/home/foo\n('A', 'B')\n",
+ stdout="None\n()\n['.', '--prefix', '/home/foo', '--extras', 'A', 'B']\n",
)
test.pass_test()