Skip to content

Commit

Permalink
Fix short-option processing
Browse files Browse the repository at this point in the history
Override the _process_short_opts method from optparse so it behaves
better.  This fix is lifted directly from #3799, leaving an additional
part to apply later.

Fixes #3798

Signed-off-by: Mats Wichmann <[email protected]>
  • Loading branch information
mwichmann committed Sep 16, 2024
1 parent 5d453c2 commit 23ccbab
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ NOTE: Python 3.6 support is deprecated and will be dropped in a future release.

RELEASE VERSION/DATE TO BE FILLED IN LATER

From Dillan Mills:
- Fix support for short options (`-x`).

From Mats Wichmann:
- PackageVariable now does what the documentation always said it does
if the variable is used on the command line with one of the enabling
Expand Down
5 changes: 5 additions & 0 deletions RELEASE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ FIXES
it always produced True in this case).
- Temporary files created by TempFileMunge() are now cleaned up on
scons exit, instead of at the time they're used. Fixes #4595.
- AddOption now correctly adds short (single-character) options.
Previously an added short option would always report as unknown,
while long option names for the same option worked. Short options
that take a value require the user to specify the value immediately
following the option, with no spaces (e.g. -j5 and not -j 5).

IMPROVEMENTS
------------
Expand Down
73 changes: 67 additions & 6 deletions SCons/Script/SConsOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,13 @@ def error(self, msg: str) -> None:
def _process_long_opt(self, rargs, values) -> None:
"""SCons-specific processing of long options.
This is copied directly from the normal
``optparse._process_long_opt()`` method, except that, if configured
to do so, we catch the exception thrown when an unknown option
is encountered and just stick it back on the "leftover" arguments
for later (re-)processing. This is because we may see the option
definition later, while processing SConscript files.
This is copied directly from the normal Optparse
:meth:`~optparse.OptionParser._process_long_opt` method, except
that, if configured to do so, we catch the exception thrown
when an unknown option is encountered and just stick it back
on the "leftover" arguments for later (re-)processing. This is
because we may see the option definition later, while processing
SConscript files.
"""
arg = rargs.pop(0)

Expand Down Expand Up @@ -414,6 +415,66 @@ def _process_long_opt(self, rargs, values) -> None:

option.process(opt, value, values, self)


def _process_short_opts(self, rargs, values) -> None:
"""SCons-specific processing of short options.
This is copied directly from the normal Optparse
:meth:`~optparse.OptionParser._process_short_opts` method, except
that, if configured to do so, we catch the exception thrown
when an unknown option is encountered and just stick it back
on the "leftover" arguments for later (re-)processing. This is
because we may see the option definition later, while processing
SConscript files.
"""
arg = rargs.pop(0)
stop = False
i = 1
for ch in arg[1:]:
opt = "-" + ch
option = self._short_opt.get(opt)
i += 1 # we have consumed a character

try:
if not option:
raise optparse.BadOptionError(opt)
except optparse.BadOptionError:
# SCons addition: if requested, add unknown options to
# the "leftover arguments" list for later processing.
if self.preserve_unknown_options:
self.largs.append(arg)
return
raise

if option.takes_value():
# 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

nargs = option.nargs
if len(rargs) < nargs:
if nargs == 1:
self.error(_("%s option requires an argument") % opt)
else:
self.error(_("%s option requires %d arguments")
% (opt, nargs))
elif nargs == 1:
value = rargs.pop(0)
else:
value = tuple(rargs[0:nargs])
del rargs[0:nargs]

else: # option doesn't take a value
value = None

option.process(opt, value, values, self)

if stop:
break


def reparse_local_options(self) -> None:
"""Re-parse the leftover command-line options.
Expand Down
3 changes: 2 additions & 1 deletion test/AddOption/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
DefaultEnvironment(tools=[])
env = Environment(tools=[])
AddOption(
'--force',
'-F', '--force',
action="store_true",
help='force installation (overwrite any existing files)',
)
Expand Down Expand Up @@ -74,6 +74,7 @@

test.run('-Q -q .', stdout="None\nNone\n")
test.run('-Q -q . --force', stdout="True\nNone\n")
test.run('-Q -q . -F', stdout="True\nNone\n")
test.run('-Q -q . --prefix=/home/foo', stdout="None\n/home/foo\n")
test.run('-Q -q . -- --prefix=/home/foo --force', status=1, stdout="None\nNone\n")
# check that SetOption works on prefix...
Expand Down
8 changes: 4 additions & 4 deletions test/SCONSFLAGS.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@
test.must_not_contain_any_line(test.stdout(), ['Help text.'])
test.must_contain_all_lines(test.stdout(), ['-H, --help-options'])

os.environ['SCONSFLAGS'] = '-Z'

expect = r"""usage: scons [OPTIONS] [VARIABLES] [TARGETS]
SCons Error: no such option: -Z
"""

test.run(arguments = "-H", status = 2,
stderr = expect, match=TestSCons.match_exact)
test.run(arguments="-Z", status=2, stderr=expect, match=TestSCons.match_exact)

os.environ['SCONSFLAGS'] = '-Z'
test.run(status=2, stderr=expect, match=TestSCons.match_exact)

test.pass_test()

Expand Down

0 comments on commit 23ccbab

Please sign in to comment.