Repository: SCons/scons

Issue:
SConf marks files up-to-date while using them in checks
This issue was originally created at: 2011-04-23 13:50:13.
This issue was reported by: `garyo`.
garyo said at 2011-04-23 13:50:13
>If you have a `Configure` (aka `SConf`) test that uses a header file, say `foo.h`, and `foo.h` is modified, what happens is the Configure test runs (it's like a mini-SCons run) and updates the `ninfo.csig` of `foo.h`'s node.  Then the rest of the SConstruct gets read, and `foo.h` appears unmodified, so none of its dependents get built.  Don't have a repro case handy here, will try to provide one soon.




Discussion:
#2485 is an example of this, it looks like.
See also this email thread: http://four.pairlist.net/pipermail/scons-users/2021-April/008527.html
Should SConf use a separate sconsign file and just stick in sconf_temp dir?

Not sure if that will really fix it, because the state may be in memory from just running the SConf checks.
That, on the surface, sounds like it could alleviate several of the interaction problems that people seem to have run into - there are a LOT of Configure bugs (of course the devil is in the details as you note).  Seems like a lot of the old bugs were handwaved away with "when we rewrite blah blah...." which didn't happen.

Funny thing is, I was just thinking about another reason why one might want to be able to specify an alternate sconsign file.

Resolving patch:
diff --git a/SCons/SConf.py b/SCons/SConf.py
--- a/SCons/SConf.py
+++ b/SCons/SConf.py
@@ -227,6 +227,8 @@ class SConfBuildTask(SCons.Taskmaster.AlwaysTask):
     This is almost the same as SCons.Script.BuildTask. Handles SConfErrors
     correctly and knows about the current cache_mode.
     """
+    non_sconf_nodes = set()
+
     def display(self, message):
         if sconf_global.logstream:
             sconf_global.logstream.write("scons: Configure: " + message + "\n")
@@ -376,6 +378,25 @@ def execute(self):
                     sconsign.set_entry(t.name, sconsign_entry)
                     sconsign.merge()
 
+    def make_ready_current(self):
+        # We're overriding make_ready_current() call to add to the list
+        # of nodes used by this task, filtering out any nodes created
+        # by the checker for it's own purpose.
+        self.non_sconf_nodes.update([t for t in self.targets if not t.is_conftest()])
+        super().make_ready_current()
+    make_ready = make_ready_current
+
+    def postprocess(self):
+        # We're done executing this task, so now we'll go through all the
+        # nodes used by this task which aren't nodes created for
+        # Configure checkers, but rather are existing or built files
+        # and reset their node info.
+        # If we do not reset their node info, any changes in these
+        # nodes will not trigger builds in the normal build process
+        for node in self.non_sconf_nodes:
+            node.ninfo = node.new_ninfo()
+        super().postprocess()
+
 class SConfBase:
     """This is simply a class to represent a configure context. After
     creating a SConf object, you can call any tests. After finished with your
diff --git a/SCons/SConfTests.py b/SCons/SConfTests.py
--- a/SCons/SConfTests.py
+++ b/SCons/SConfTests.py
@@ -214,6 +214,8 @@ def built(self):
                         pass
                     def get_stored_info(self):
                         pass
+                    def is_conftest(self):
+                        return True
                     def get_executor(self):
                         class Executor:
                             def __init__(self, targets):


Test patch:
diff --git a/test/Configure/conftest_source_file.py b/test/Configure/conftest_source_file.py
new file mode 100644
--- /dev/null
+++ b/test/Configure/conftest_source_file.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+#
+# MIT License
+#
+# Copyright The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+Template for end-to-end test file.
+Replace this with a description of the test.
+"""
+
+import textwrap
+import os 
+
+import TestSCons
+
+test = TestSCons.TestSCons()
+
+test.dir_fixture("conftest_source_file")
+
+test.run(arguments='.')
+
+conf_text = textwrap.dedent("""\
+    Checking for C header file header1.h... {arg1}yes
+    Checking for C header file header3.h... {arg2}yes
+""")
+
+test.up_to_date(read_str=conf_text.format(arg1='(cached) ', arg2='(cached) '))
+
+test.write('header2.h', """
+#pragma once
+int test_header = 2;
+""")
+
+test.not_up_to_date(read_str=conf_text.format(arg1='(cached) ', arg2='(cached) '))
+
+test.up_to_date(read_str=conf_text.format(arg1='', arg2='(cached) '))
+os.environ['SCONSFLAGS'] = '--config=force'
+test.up_to_date(read_str=conf_text.format(arg1='', arg2=''))
+os.environ['SCONSFLAGS'] = ''
+
+test.up_to_date(read_str=conf_text.format(arg1='(cached) ', arg2='(cached) '))
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
diff --git a/test/Configure/conftest_source_file/SConstruct b/test/Configure/conftest_source_file/SConstruct
new file mode 100644
--- /dev/null
+++ b/test/Configure/conftest_source_file/SConstruct
@@ -0,0 +1,8 @@
+DefaultEnvironment(tools=[])
+env = Environment()
+env.Append(CPPPATH=['.'])
+conf1 = Configure(env)
+conf1.CheckHeader("header1.h")
+conf1.CheckHeader("header3.h")
+conf1.Finish()
+env.Program('out', 'main.c')
\ No newline at end of file
diff --git a/test/Configure/conftest_source_file/header1.h b/test/Configure/conftest_source_file/header1.h
new file mode 100644
--- /dev/null
+++ b/test/Configure/conftest_source_file/header1.h
@@ -0,0 +1,2 @@
+#pragma once
+#include "header2.h"
\ No newline at end of file
diff --git a/test/Configure/conftest_source_file/header2.h b/test/Configure/conftest_source_file/header2.h
new file mode 100644
--- /dev/null
+++ b/test/Configure/conftest_source_file/header2.h
@@ -0,0 +1,2 @@
+#pragma once
+int test_header = 1;
\ No newline at end of file
diff --git a/test/Configure/conftest_source_file/header3.h b/test/Configure/conftest_source_file/header3.h
new file mode 100644
--- /dev/null
+++ b/test/Configure/conftest_source_file/header3.h
@@ -0,0 +1,2 @@
+#pragma once
+int test_header = 3;
\ No newline at end of file
diff --git a/test/Configure/conftest_source_file/main.c b/test/Configure/conftest_source_file/main.c
new file mode 100644
--- /dev/null
+++ b/test/Configure/conftest_source_file/main.c
@@ -0,0 +1,2 @@
+#include "header1.h"
+int main(){return 0;}
\ No newline at end of file
diff --git a/test/sconsign/script/Configure.py b/test/sconsign/script/Configure.py
--- a/test/sconsign/script/Configure.py
+++ b/test/sconsign/script/Configure.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python
 #
-# __COPYRIGHT__
+# MIT License
+#
+# Copyright The SCons Foundation
 #
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
@@ -20,9 +22,6 @@
 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 """
 Verify that we can print .sconsign files with Configure context
@@ -58,6 +57,9 @@
 
 test.write('SConstruct', """
 import os
+
+DefaultEnvironment(tools=[])
+
 env = Environment(ENV={'PATH' : os.environ.get('PATH','')})
 conf = Configure(env)
 r1 = conf.CheckCHeader( 'math.h' )
@@ -84,10 +86,10 @@
         %(sig_re)s \[.*\]
 conftest_%(sig_re)s_0_%(sig_re)s%(_obj)s:
         %(_sconf_temp_conftest_0_c)s: %(sig_re)s \d+ \d+
-        %(CC)s: %(sig_re)s \d+ \d+
+        %(CC)s: %(sig_re)s None None
         %(sig_re)s \[.*\]
 === %(CC_dir)s:
-%(CC_file)s: %(sig_re)s \d+ \d+
+%(CC_file)s: None None None
 """ % locals()
 
 # grab .sconsign or .sconsign_<hashname>
diff --git a/testing/framework/TestSCons.py b/testing/framework/TestSCons.py
--- a/testing/framework/TestSCons.py
+++ b/testing/framework/TestSCons.py
@@ -498,7 +498,7 @@ def up_to_date(self, arguments='.', read_str="", **kw):
         kw['match'] = self.match_re_dotall
         self.run(**kw)
 
-    def not_up_to_date(self, arguments='.', **kw):
+    def not_up_to_date(self, arguments='.', read_str="", **kw):
         """Asserts that none of the targets listed in arguments is
         up to date, but does not make any assumptions on other targets.
         This function is most useful in conjunction with the -n option.
@@ -508,7 +508,7 @@ def not_up_to_date(self, arguments='.', **kw):
             s = f"{s}(?!scons: `{re.escape(arg)}' is up to date.)"
         s = f"({s}[^\n]*\n)*"
         kw['arguments'] = arguments
-        stdout = re.escape(self.wrap_stdout(build_str='ARGUMENTSGOHERE'))
+        stdout = re.escape(self.wrap_stdout(read_str=read_str, build_str='ARGUMENTSGOHERE'))
         kw['stdout'] = stdout.replace('ARGUMENTSGOHERE', s)
         kw['match'] = self.match_re_dotall
         self.run(**kw)