Repository: SCons/scons

Issue:
abbreviations of AddOption options do not cause error
From Discord discussion,

As mentioned in the manpage and in the code in `SCons/Script/SConsOptions.py`, options added via `AddOption` are taken literally, abbreviations of the long option names do not work, unlike the optparse `add_option`, where abbreviations are accepted.  This is for sequencing reasons: the list of remaining options is reparsed every time a call to `AddOption` is seen, because SCons wants to allow the user to immediately call `GetOption` in the same sconscript to fish out a possible value for that option.  In this scenario you cannot accept `--foo` as an abbreviation for `--foobar`, because you don't yet know if `--foo` will be a unique abbreviation - perhaps later an `AddOption` would define a `--foobaz` option (in which case foo is no longer unique), or even `--foo` itself.

SCons does indeed not recognize abbreviations as synonyms for an `AddOption` option, but, it also doesn't say anything about them. As per the discussion, this has been seen in the field, where leaving off the final 's' off an option led to behavior surprising because there was no error indicating this option was unrecognized.

Simple illustration - `SConstruct`:

```
AddOption('--foobar',
    dest='myfoo',
    action='store_true',
    default=False,
    help='Supply a silly option for testing'
)
opt = GetOption("myfoo")
print(f"GetOption('myfoo') returns {opt}")
```

Called correctly (the "Added opt" line is debugging from a custom scons version, just for illustration):

```
$ python ../../scripts/scons.py -Q --foobar
Added opt: --foobar, dest=myfoo
GetOption('myfoo') returns True
scons: `.' is up to date.
```

Called with an unknown argument generates an error, as expected:

```
$ python ../../scripts/scons.py -Q --foobaz
Added opt: --foobar, dest=myfoo
GetOption('myfoo') returns False
usage: scons [OPTION] [TARGET] ...

SCons Error: no such option: --foobaz
```

Called with an abbreviation - neither recognized as foobar, nor flagged as unknown.

```
$ python ../../scripts/scons.py -Q --foo
Added opt: --foobar, dest=myfoo
GetOption('myfoo') returns False
scons: `.' is up to date.
```




Discussion:

Resolving patch:
diff --git a/SCons/Script/SConsOptions.py b/SCons/Script/SConsOptions.py
--- a/SCons/Script/SConsOptions.py
+++ b/SCons/Script/SConsOptions.py
@@ -273,8 +273,7 @@ def _process_long_opt(self, rargs, values):
         """
         arg = rargs.pop(0)
 
-        # Value explicitly attached to arg?  Pretend it's the next
-        # argument.
+        # Value explicitly attached to arg?  Pretend it's the next argument.
         if "=" in arg:
             (opt, next_arg) = arg.split("=", 1)
             rargs.insert(0, next_arg)
@@ -284,7 +283,11 @@ def _process_long_opt(self, rargs, values):
             had_explicit_value = False
 
         try:
-            opt = self._match_long_opt(opt)
+            if opt != self._match_long_opt(opt):
+                raise optparse.BadOptionError(
+                    "'%s'. Did you mean '%s'?"
+                    % (opt, self._match_long_opt(opt))
+                )
         except optparse.BadOptionError:
             if self.preserve_unknown_options:
                 # SCons-specific:  if requested, add unknown options to
@@ -398,7 +401,7 @@ def add_local_option(self, *args, **kw):
         """
         Adds a local option to the parser.
 
-        This is initiated by a SetOption() call to add a user-defined
+        This is initiated by an AddOption() call to add a user-defined
         command-line option.  We add the option to a separate option
         group for the local options, creating the group if necessary.
         """


Test patch:
diff --git a/test/AddOption/longopts.py b/test/AddOption/longopts.py
--- a/test/AddOption/longopts.py
+++ b/test/AddOption/longopts.py
@@ -51,6 +51,16 @@
 test.run('-Q -q . --myarg=helloworld',
          stdout="myargument: gully\nmyarg: helloworld\n")
 
+# Issue #3653: add a check for an abbreviation which never gets AddOption'd.
+test.run('-Q -q . --myargumen=helloworld', status=2,
+         stdout="myargument: gully\nmyarg: balla\n",
+         stderr="""\
+usage: scons [OPTION] [TARGET] ...
+
+SCons Error: no such option: '--myargumen'. Did you mean '--myargument'?
+""")
+
+
 test.pass_test()
 
 # Local Variables: