You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scons: *** [bin/buildOpts.py] Source `bin.src/buildOpts.py' not found, needed by target `bin/buildOpts.py'.
This was triggered by one of our SCons scripts running in a subdirectory assuming that Glob would only report files that exist. We can workaround the bug by doing an additional file existence check but we are reporting this in case the change to Variables() was an unexpected side effect in v4.11.
I asked Claude to write a demonstration reproducer. I understand that you may close my ticket with prejudice because I have used Claude and failed to post on the mailing list. My apologies if I offend anyone.
Summary
In SCons 4.11.0 a Variables object whose saved-variables file does not exist still causes a File node to be created for that file. The node is created in the directory of the SConscript that was being read when the variables were applied to an environment. Glob() reports the nodes SCons knows about in a directory as well as the files present on disk, so the node for the absent file is returned by a glob of that directory. Passing it to a builder then fails the build with Source ... not found.
This is a regression from 4.10.1, where a missing saved-variables file had no observable effect.
after Variables() -> ['SConscript', 'real.py']
after Environment() -> ['SConscript', 'custom.py', 'real.py']
So which directory acquires the node depends on where the Variables object is applied to an environment, not on where it was created. A project that constructs its environment from a subsidiary SConscript gets the node in that subdirectory.
Analysis
Variables.Update() in SCons/Variables/__init__.py reads each saved-variables file through a node:
env.File() creates the node whether or not a file backs it, and it resolves a relative name against the current SConscript directory. get_text_contents() returns an empty string for a node with no file, so the read is correctly skipped, but the node remains in the directory's entries.
Dir._glob1() in SCons/Node/FS.py collects those entries before it consults the disk:
so the node is matched by the pattern and returned.
Before 4.11 the same loop tested the file with os.path.exists(filename) and opened it directly, which created nothing. The change came in with the fix for #816, so that a saved-variables file could be found in a source directory or a repository rather than only in the build directory:
cb74b0809ec7f857bf21a2eb0901b0e5d26ae9e2 — "Simplify logic to just use default File() behavoir and use that object to get the text contents of the file"
The CHANGES.txt entry is "Fix Variables() not reading a saved-variables file (e.g. custom.py)".
The underlying goal seems right; the side effect of materializing a node for a file that exists nowhere does not.
Impact
This reached us through the LSST build system. sconsUtils always offers an optional buildOpts.py to Variables, and it installs scripts by globbing a bin.src/ directory. In one of the packages the environment is first constructed from bin.src/SConscript, so on 4.11.0 every build of that package fails with:
scons: *** [bin/buildOpts.py] Source `bin.src/buildOpts.py' not found, needed by target `bin/buildOpts.py'.
Reporting it because the failure is remote from its cause: an optional file that is absent, and has always been absent, silently becomes a build target, and only for whichever directory happened to be current.
Suggested resolutions
Any of these would fix it for us; the first seems closest to the intent of #816.
Do not leave a node behind when no file is found. Resolve the candidate paths (local directory, source directory, repositories) and only create the node once a readable file has been located.
Resolve the file relative to the top-level directory rather than the current SConscript directory. The documented use is a top-level custom.py, and depending on which SConscript was being read is surprising in its own right.
Failing either, consider whether Glob() should offer a way to exclude nodes that neither exist nor have a builder. That is a broader change, and it would not address the surprise of the node existing at all.
Describe the bug
With 4.11 one of our scons builds fails with:
This was triggered by one of our SCons scripts running in a subdirectory assuming that Glob would only report files that exist. We can workaround the bug by doing an additional file existence check but we are reporting this in case the change to
Variables()was an unexpected side effect in v4.11.Our workaround is in lsst/sconsUtils#150
I asked Claude to write a demonstration reproducer. I understand that you may close my ticket with prejudice because I have used Claude and failed to post on the mailing list. My apologies if I offend anyone.
Summary
In SCons 4.11.0 a
Variablesobject whose saved-variables file does not exist still causes aFilenode to be created for that file. The node is created in the directory of the SConscript that was being read when the variables were applied to an environment.Glob()reports the nodes SCons knows about in a directory as well as the files present on disk, so the node for the absent file is returned by a glob of that directory. Passing it to a builder then fails the build withSource ... not found.This is a regression from 4.10.1, where a missing saved-variables file had no observable effect.
Versions
SCons: v4.11.0.190853a12d8c2128f29abe849a6a162f0ed9f049SCons: v4.10.1.055b01f429d58b686701a56df863a817c36bb103Reproducer
No saved-variables file is created anywhere in this tree.
Expected, and what 4.10.1 prints
Actual, on 4.11.0
custom.pydoes not exist, insub/or anywhere else.The consequence for a real build
Anything that feeds the glob to a builder now fails.
Replace
sub/SConscriptwith:On 4.10.1 the one real file is copied:
On 4.11.0 the build dies before copying anything:
It is
Update()that creates the node, not the constructorSo which directory acquires the node depends on where the
Variablesobject is applied to an environment, not on where it was created. A project that constructs its environment from a subsidiary SConscript gets the node in that subdirectory.Analysis
Variables.Update()inSCons/Variables/__init__.pyreads each saved-variables file through a node:env.File()creates the node whether or not a file backs it, and it resolves a relative name against the current SConscript directory.get_text_contents()returns an empty string for a node with no file, so the read is correctly skipped, but the node remains in the directory's entries.Dir._glob1()inSCons/Node/FS.pycollects those entries before it consults the disk:so the node is matched by the pattern and returned.
Before 4.11 the same loop tested the file with
os.path.exists(filename)and opened it directly, which created nothing. The change came in with the fix for #816, so that a saved-variables file could be found in a source directory or a repository rather than only in the build directory:8c9149e6151c9be207df2a80872b772a0d68b8b6— "Read Variables saved-variables file from the source directory (Variables('custom.py') should read from source directory #816)"cb74b0809ec7f857bf21a2eb0901b0e5d26ae9e2— "Simplify logic to just use default File() behavoir and use that object to get the text contents of the file"The
CHANGES.txtentry is "Fix Variables() not reading a saved-variables file (e.g. custom.py)".The underlying goal seems right; the side effect of materializing a node for a file that exists nowhere does not.
Impact
This reached us through the LSST build system.
sconsUtilsalways offers an optionalbuildOpts.pytoVariables, and it installs scripts by globbing abin.src/directory. In one of the packages the environment is first constructed frombin.src/SConscript, so on 4.11.0 every build of that package fails with:See lsst/sconsUtils#150.
Reporting it because the failure is remote from its cause: an optional file that is absent, and has always been absent, silently becomes a build target, and only for whichever directory happened to be current.
Suggested resolutions
Any of these would fix it for us; the first seems closest to the intent of #816.
custom.py, and depending on which SConscript was being read is surprising in its own right.Glob()should offer a way to exclude nodes that neither exist nor have a builder. That is a broader change, and it would not address the surprise of the node existing at all.