Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split out CPPDEFINES handling in Append methods #4263

Merged
merged 16 commits into from
Feb 18, 2023

Conversation

mwichmann
Copy link
Collaborator

Rather than having lots of special-case code for CPPDEFINES in four separate routines, add a new _add_cppdefines function to handle it, paying attention to append/prepend, unique/duplicating, and keep-original/replace-original. The existing special case handing was then removed from Append and AppendUnique (it was never present in Prepend and PrependUnique anyway - see #3876, but these now get it due to a call to the new function).

Tuple handling is now consistent with list handling: a single tuple is treated as macro names to add, not as a name=value pair. A tuple or list has to be a member of a containing tuple or list to get the macro=value treatment. This may affect some existing usage. macro=value tuples without a value can now be entered either in (macro,) form or (macro, None) form.

Internally, whenever append/prepend is done, existing contents are forced to a deque, which allows efficient adding at either end without resorting to the tricks the Prepend functions currently do (they still do these tricks, but only in non-CPPDEFINES cases). As a result, values from a dict are not stored as a dict, which has some effect on ordering: values will be consistently ordered, but the ones from the dict are no longer necessarily sorted.

In SCons/Defaults.py, processDefines no longer sorts a dict it is passed, since Python now preserves dict order. This does not affect the E2E test for CPPDEFINES - since those all call an Append routine, CPPDEFINES will always be a deque, and so processDefines never sees a dict in that case. It could well affect real-life usage - if the setup of CPPDEFINES was such that it used to contain a dict with multiple entries, the order might change (sorting would have presented the keys from that dict in alphabetical order). This would lead to a one-time rebuild for actions that change (after that it will remain consistent).

In the E2E test CPPDEFINES/append.py some bits were reformatted, and the handler routine now accounts for the type being a deque - since the test does a text comparison of internally produced output, it failed if the word "deque" appeared. Some new test cases were also added to exercise strings with spaces embedded in them.

Changes were made to the expected output of the E2E test. These reflect changes in the way data is now stored in CPPDEFINES, and in some cases in order. Most of these do not change the meaning (i.e. "result" changes, but "final" output is the same). These are the exceptions:

  • "appending a dict to a list-of-2lists", AppendUnique case: order now preserved as entered (previously took the order of the appended dict)

  • "appending a string to a dict", Append case: not stored as a dict, so ordering is as originally entered.

  • "appending a dict to a dict", Append case: no longer merge into a dict, so this is now an actual append rather than a merge of dicts which caused the uniquing effect even without calling AppendUnique (arguably the old way was incorrect).

A new test/CPPDEFINES/prepend.py is added to test Prepend* cases.

append.py and prepend.py are structured to fetch the SConstruct from a fixture file.

append.py got an added test in the main text matrix, a string of the macro=value form. The same 5x5 maxtrix is used in the new prepend.py test as well ("expected" values for these had to be added as well).

Cosmetically, append and prepend now print their test summary so strings have quotation marks - the "orig" lines in the expected output was adjusted. This change looks like:

-   orig = FOO, append = FOO
+   orig = 'FOO', append = 'FOO'

The other tests in test/CPPDEFINES got copyright updating and reformatting, but otherwise do not change.

Documentation updated to clarify behavior.

Fixes #4254
Fixes #3876

Signed-off-by: Mats Wichmann mats@linux.com

Contributor Checklist:

  • I have created a new test or updated the unit tests to cover the new/changed functionality.
  • I have updated CHANGES.txt (and read the README.rst)
  • I have updated the appropriate documentation

@mwichmann mwichmann added documentation maintenance Tasks to maintain internal SCons code/tools Append Issues in Append/Prepend + Unique variants labels Nov 14, 2022
@mwichmann mwichmann mentioned this pull request Dec 15, 2022
3 tasks
RELEASE.txt Outdated
@@ -64,6 +69,17 @@ FIXES
- Ninja: Fix execution environment sanitation for launching ninja. Previously if you set an
execution environment variable set to a python list it would crash. Now it
will create a string joining the list with os.pathsep
- Inconsistent behavior of adding CPPDEFINES values via tuple arguments
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this can change the CPPDEFINES, we should add a section at the top with
NOTE: The following usages of CPPDEFINES have changed
env.Append(CPPDEFINES,('a','b') ) now yields -Da -Db instead of -Da=b
etc..
Should be a fairly small set of these to clearly spell out what's changed?

CHANGES.txt Outdated
@@ -86,7 +86,17 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
still run into problems on some configurations), and automated
tests are now run on changes in this area so future problems can
be spotted.

- Special-case handling for CPPDEFINES in the Append/Prepend routines
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be multiple bullets.

Copy link
Collaborator Author

@mwichmann mwichmann Jan 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do that, but it's really convoluted - see the long report in the one issue of the various inconsistencies I had discovered. So there would be a bunch of "now get (b) instead of (a), but only if (c); if (d) the behavior was already (b); if (e), this previously did not work at all".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be valuable. This whole PR is really about fixing some corner cases (and reorganizing the code so it's more sane), so explaining which corner cases are now fixed (changed) would be useful for anyone who updated and saw their build change, and/or has a build not doing what they expect and sees that the newer version solves that.

Bigger organizations often need a forcing function to move to a newer version.

If any element is a tuple (or list)
then the first item of the tuple is the macro name
and the second is the macro definition.
If the definition is not omitted or <literal>None</literal>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value instead of definition?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I was writing it in terms of how the C standard expresses those... although now I look, the standard uses "replacement" rather than "definition".

</example_commands>

<para>
:<emphasis>Changed in version 4.5</emphasis>:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4.5.0

CXXFLAGS=-std=c11 -O, CPPDEFINES=['RELEASE', 'EXTRA']
CPPDEFINES will expand to -DRELEASE -DEXTRA
CXXFLAGS = -std=c11, CPPDEFINES = RELEASE
CXXFLAGS = -std=c11 -O, CPPDEFINES = deque(['RELEASE', 'EXTRA'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding deque here will most likely just confuse users. Many of whom aren't strong python programmers. I'd leave it just the list.

(<computeroutput>-DBAR=1</computeroutput>).
&SCons; allows you to specify a macro with a definition
using a <literal>name=value</literal> string,
or a tuple <literal>(name, value)</literal>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this no longer be done with a list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, it's just awkward to keep repeating multiples all over the place. It would be nice to use "sequence" as that covers all of tuple, list, UserList, etc. but it also includes str, which is not a data type that can be used for a name, value pair.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed a rewording of this part.

CPPDEFINES=['FOO', 'BAR=1', ('OTHER', 2)]
CPPDEFINES=['FOO', 'BAR=1', ('OTHER', 2), {'EXTRA': 'arg'}]
CPPDEFINES = FOO
CPPDEFINES = deque(['FOO', 'BAR=1'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deque?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code takes the initial value given to CPPDEFINES as-is, but once you further Append/Prepend it takes the liberty of converting to a type useful to it. It always did that, but the chosen compound type depends on the sequence of events - could be a list, a dict, etc. Or in the case of AppendUnique, it converted it to a type incorrectly in some cases. Now we consistently get a deque, allowing the processing to be sane. Yes, as noted elsewhere, it could be a list instead but that shouldn't really be important to the user. Which type is really only exposed if you dig, as it happens here trying to show an example.

</screen>

<para>
<emphasis>Changed in version 4.5</emphasis>:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4.5.0

<screen>
$ scons -Q
CPPDEFINES = ('MACRO1', 'MACRO2')
CPPDEFINES = deque(['MACRO1', 'MACRO2', ('MACRO3', 'MACRO4')])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deque?

@bdbaddog
Copy link
Contributor

Curious: Why use deque instead of a list to hold the defines?

SCons/Environment.py Outdated Show resolved Hide resolved
@mwichmann
Copy link
Collaborator Author

Curious: Why use deque instead of a list to hold the defines?

This is a natural fit for a deque since it's designed to be added to from either end with Prepend and Append. Prepending a deque is as fast as appending, and this way don't have to play silly games (reverse the list, append to it, reverse back... that kind of thing).

@bdbaddog
Copy link
Contributor

Would you expect all the examples in this sample code https://github.com/bdbaddog/scons-bugswork/tree/main/4254
(Derived from description in Issue #4254 )
To work with your current code here? I'm seeing some issues.

This is the example run on current master:

% python ~/devel/scons/git/as_scons/scripts/scons.py -n
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o main_case0.o -c -Dz=case2 main.c
gcc -o main_case1.o -c "-D('z', 'case1')" main.c
gcc -o main_case2.o -c -Dz=case2 -Da -Db main.c
gcc -o main_case4.o -c -Dcase4 -Dz=case4 main.c
gcc -o main_case5.o -c -Dz -Dcase5 main.c
scons: done building targets.

This is the example run against this branch (Note FIXED=1 enables case 3 which crashes in master)

% python ~/devel/scons/git/mwichmann/scripts/scons.py -n FIXED=1
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o main_case0.o -c -Dz=case2 main.c
gcc -o main_case1.o -c "-D('z', 'case1')" main.c
gcc -o main_case2.o -c -Dz -Dcase2 -Da -Db main.c
gcc -o main_case3.o -c -Dz -Dcase3 -Dsimple_string main.c
gcc -o main_case4.o -c -Dcase4 main.c
gcc -o main_case5.o -c -Dz -Dcase5 main.c
scons: done building targets.

Rather than having lots of special-case code for CPPDEFINES in
four separate routines, add a new _add_cppdefines function to
handle it, paying attention to append/prepend, unique/duplicating,
and keep-original/replace-original. The existing special case handing
was then removed from Append and AppendUnique (it was never present in
Prepend and PrependUnique anyway - see SCons#3876, but these now get it due
to a call to the new function).

Tuple handling is now consistent with list handling: a single tuple is
treated as macro names to add, not as a name=value pair. A tuple or list
has to be a member of a containing tuple or list to get the macro=value
treatment. This *may* affect some existing usage.  macro=value tuples
without a value can now be entered either in (macro,) form or (macro,
None) form.

Internally, whenever append/prepend is done, existing contents are
forced to a deque, which allows efficient adding at either end without
resorting to the tricks the Prepend functions currently do (they still
do these tricks, but only in non-CPPDEFINES cases).  As a result, values
from a dict are not stored as a dict, which has some effect on ordering:
values will be *consistently* ordered, but the ones from the dict are
no longer necessarily sorted.

In SCons/Defaults.py, processDefines no longer sorts a dict it is
passed, since Python now preserves dict order.  This does not affect
the E2E test for CPPDEFINES - since those all call an Append routine,
CPPDEFINES will always be a deque, and so processDefines never sees a
dict in that case. It could well affect real-life usage - if setup of
CPPDEFINES was such that it used to contain a dict with multiple entries,
the order might change (sorting would have presented the keys from that
dict in alphabetical order).  This would lead to a one-time rebuild for
actions that change (after that it will remain consistent).

In the E2E test CPPDEFINES/append.py some bits were reformatted, and the
handler routine now accounts for the type being a deque - since the test
does a text comparison of internally produced output, it failed if the
word "deque" appeared.  Some new test cases were added to also exercise
strings with spaces embedded in them.

Changes were made to the expected output of the E2E test. These reflect
changes in the way data is now stored in CPPDEFINES, and in some cases
in order. Most of these do not change the meaning (i.e. "result" changes,
but "final" output is the same). These are the exceptions:

- "appending a dict to a list-of-2lists", AppendUnique case: order now
  preserved as entered (previously took the order of the appended dict)

- "appending a string to a dict", Append case: not stored as a dict,
  so ordering is as originally entered.

- "appending a dict to a dict", Append case: no longer merge into a
  dict, so this is now an actual append rather than a merge of dicts which
  caused the uniquing effect even without calling AppendUnique (arguably
  the old way was incorrect).

A new test/CPPDEFINES/prepend.py is added to test Prepend* cases.

append.py and prepend.py are structured to fetch the SConstruct from a
fixture file.

append.py got an added test in the main text matrix, a string of the
macro=value form. The same 5x5 maxtrix is used in the new prepend.py
test as well ("expected" values for these had to be added as well).

Cosmetically, append and prepend now print their test summary so
strings have quotation marks - the "orig" lines in the expected output
was adjusted.  This change looks like:

-   orig = FOO, append = FOO
+   orig = 'FOO', append = 'FOO'

The other tests in test/CPPDEFINES got copyright updating and
reformatting, but otherwise do not change.

Documentation updated to clarify behavior.

Fixes SCons#4254
Fixes SCons#3876

Signed-off-by: Mats Wichmann <mats@linux.com>
Signed-off-by: Mats Wichmann <mats@linux.com>
The test output for append and prepend generated tabs,
and so the "expected" strings had to contain tab indents.
This made Sider on the CI extremely noisy.  There's no
specific benefit to tabs here, so just change them to spaces.

Signed-off-by: Mats Wichmann <mats@linux.com>
Fix a typo
Try to further clarify how to add valued macros.

Signed-off-by: Mats Wichmann <mats@linux.com>
@mwichmann
Copy link
Collaborator Author

I've pushed a rebase of this (had to force), dealing with Util having been refactored meanwhile. A couple of small changes, plus test expansion to make sure those cases are covered, coming next. Then back to the RELEASE/CHANGES material.

A few places were deciding what to do based on result of is_List, when the
intent was really list-or-tuple-or-derivatives - changed to is_Sequence.

Added some test cases, including a unit test for processDefines
(which would have caught one of these cases).

Signed-off-by: Mats Wichmann <mats@linux.com>
@mwichmann
Copy link
Collaborator Author

FWIW, this is the output from the current version:

scons: Building targets ...
gcc -o main_case0.o -c -Dz=case2 main.c
gcc -o main_case1.o -c -Dz -Dcase1 main.c
gcc -o main_case2.o -c -Dz -Dcase2 -Da -Db main.c
gcc -o main_case3.o -c -Dz -Dcase3 -Dsimple_string main.c
gcc -o main_case4.o -c -Dcase4 -Dz -Dcase4 main.c
gcc -o main_case5.o -c -Dz -Dcase5 main.c
scons: done building targets.

@bdbaddog
Copy link
Contributor

FWIW, this is the output from the current version:

scons: Building targets ...
gcc -o main_case0.o -c -Dz=case2 main.c
gcc -o main_case1.o -c -Dz -Dcase1 main.c
gcc -o main_case2.o -c -Dz -Dcase2 -Da -Db main.c
gcc -o main_case3.o -c -Dz -Dcase3 -Dsimple_string main.c
gcc -o main_case4.o -c -Dcase4 -Dz -Dcase4 main.c
gcc -o main_case5.o -c -Dz -Dcase5 main.c
scons: done building targets.

So only case 0 is correct?

@mwichmann
Copy link
Collaborator Author

Define correct here. If you specifically wanted the output result -Dz=case2 the three ways to enter it are:

[('z','case2')]
{'z': 'case2'}
"z=case2"

Only your case0 uses one of those three forms.

Otherwise, I'm not sure what you're after... the appending is all being handled as I expect now.

@bdbaddog
Copy link
Contributor

bdbaddog commented Feb 3, 2023

Updated logic to match current behavior when you append a tuple to yield -Dkey=value

@bdbaddog
Copy link
Contributor

bdbaddog commented Feb 3, 2023

The only question mark is, do we change specifying CPPDEFINES initially as a tuple to yield -Dkey=value, or leave as is.
It would seem more consistent if it was changed.

Thoughts?

RELEASE.txt Outdated
@@ -17,6 +17,9 @@ NOTE: If you build with Python 3.10.0 and then rebuild with 3.10.1 (or higher),
see unexpected rebuilds. This is due to Python internals changing which changed
the signature of a Python Action Function.

NOTE: If you use dictionaries to specify your CPPDEFINES, most likely the order of the CPPDEFINES
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That note, basically, was already in place. Okay if you think it's helpful to have it at the top as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always put the "it may rebuild" warnings at the top. to try to minimize the TL;DR issue...

@mwichmann
Copy link
Collaborator Author

If you're okay with tuple specifically having a different meaning than list - personally, I think that is inconsistent. If so, then some further rework is needed (see below). What do you propose happens if someone tried to Append, say, ('FOO', ('BAR', '22'))? Currently that would end up expanding to "FOO=('BAR', '22')", whereas a list version of the same becomes ['FOO', 'BAR=22'].

Rework:

  • docs need to describe that adding a tuple by itself becomes a valued-macro
  • the test cases will need to be gone through to reset the expectations for the changed behavior.
  • "initial value" needs to be handled. With the latest change, Appending a tuple when CPPDEFINES doesn't exist yet gives a different result than when CPPDEFINES has any value. Obviously, that's a no-go - user code isn't supposed to have to know if it already had a value. This happens in processDefines.
  • currently, processDefines takes any sequence embedded in a sequence as a valued-macro. What should it do if it sees a non-tuple here? This is the pre-PR versionn (mine only combines the two conditions into a single is_Sequence check):
           elif SCons.Util.is_List(d) or isinstance(d, tuple):
               if len(d) >= 2:
                   l.append(str(d[0]) + '=' + str(d[1]))
               else:
                   l.append(str(d[0]))

@mwichmann
Copy link
Collaborator Author

mwichmann commented Feb 3, 2023

Attaching the result of running the CPPDEFINES tests as they exist in the PR against a version that changes both the add-single-tuple and the processDefines-single-tuple behavior (the last CI run had only one of these two changes, so the results would not have been the same there)

Changed_tuple.txt

@mwichmann
Copy link
Collaborator Author

mwichmann commented Feb 3, 2023

Even with the two changes there's still a hole: appending a tuple works as you want, and initializing to a single tuple works (because then processDefines just sees that tuple`), but initializing with a tuple and then appending (anything) to it does not (because at the time of the first append the original tuple is converted into the internal datatype first, losing its tuple status). You want me to keep going down this road?

@mwichmann
Copy link
Collaborator Author

mwichmann commented Feb 3, 2023

One more question is what to do with strings - when to split on space and when not. We know a string entry inside a list is promised not to be split, so you can enter entries with spaces (that's not CPPDEFINES specific, of course). But:

initial 'FOO BAR=22' without adds -> ['FOO BAR=22']  # initial is not split if never added to
iniitial 'FOO BAR=22' Append ('B', 'DEBUG') -> ['FOO', 'BAR=22', 'B=DEBUG']  # initial has been split
initial None Append 'BAZ B=DEBUG' -> ['BAZ', 'B=DEBUG']  # addition has been split
initial 'FOO BAR=22' Append 'BAZ B=DEBUG' -> ['FOO', 'BAR=22', 'BAZ B=DEBUG']  # initial split, addition has not been split

Isn't this fun?

@bdbaddog
Copy link
Contributor

bdbaddog commented Feb 3, 2023

Even with the two changes there's still a hole: appending a tuple works as you want, and initializing to a single tuple works (because then processDefines just sees that tuple`), but initializing with a tuple and then appending (anything) to it does not (because at the time of the first append the original tuple is converted into the internal datatype first, losing its tuple status). You want me to keep going down this road?

Seems like a tuple should always defined -Dkey=value and not change when somethings appended to it right?

@bdbaddog bdbaddog closed this Feb 3, 2023
@bdbaddog bdbaddog reopened this Feb 3, 2023
@bdbaddog
Copy link
Contributor

bdbaddog commented Feb 3, 2023

oopsie.. accidently closed.

@mwichmann
Copy link
Collaborator Author

Seems like a tuple should always defined -Dkey=value and not change when somethings appended to it right?

If the decision is that's what a tuple is, then it should be consistently so - see my quote from earlier:

Obviously, that's a no-go

mwichmann and others added 4 commits February 9, 2023 12:41
After the previous proposals, addition of a bare tuple is restored
to the valued-macro behavior (this previously did not work
consistently).  Strings are now consistently split if they contain
spaces, except if entered as a list member, in which case (as
documented) they are left alone.  tuples with nore than two members
are now flagged as a UserError; previously the first two members were
taken as name=value and any further members were silently ignored.

More tests added.

Signed-off-by: Mats Wichmann <mats@linux.com>
In a hope to make the tests runnable against a checkout that does
not include PR SCons#4263, add some try blocks so the things which used
to blow up can generate errors rather than aborting the test run.
The prepend test still fails against master.

Signed-off-by: Mats Wichmann <mats@linux.com>
…negative test for non-two-tuple. Minor wording change
Added another try block so complete prepend e2e test can be run
on a branch where PR  SCons#4263 is not applied (for comparison).
Added license header.

Changed invalid-tuple unittest to use assertRaises (behavior
is the same)

Also added a versionadded and a versionchanged to docstrings.

Signed-off-by: Mats Wichmann <mats@linux.com>
@mwichmann
Copy link
Collaborator Author

For completeness, tweaked tests slightly so tests can run against master without the changes applied. Such a test run is attached here for comparison (these tests run "clean" on the current PR version):

LOG.txt

@mwichmann
Copy link
Collaborator Author

oops, spotted that I'd made an error in preparing the Prepend test (copy-paste error, hadn't changed one instance of Append to Prepend), and fixed it... and then didn't remember to go fix the "expected" text. Fix for that forthcoming.

@bdbaddog bdbaddog merged commit 4b97682 into SCons:master Feb 18, 2023
@mwichmann mwichmann added this to the 4.5 milestone Feb 18, 2023
@mwichmann mwichmann deleted the maint/CPPDEFINES-Append branch February 18, 2023 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Append Issues in Append/Prepend + Unique variants documentation maintenance Tasks to maintain internal SCons code/tools
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CPPDEFINES handling of tuple arguments inconsistent Append/Prepend special CPPDEFINES handling problems
2 participants