Repository: SCons/scons

Issue:
Add check for structure member
This issue was originally created at: 2005-02-11 10:05:06.
This issue was reported by: `hsn`.
hsn said at 2005-02-11 10:05:06
>Add framework function for checking for specified member of some structure. I need to check struct dirent members.

issues@scons said at 2005-02-11 10:05:06
>Converted from SourceForge tracker item 1120953

gregnoel said at 2009-02-06 05:48:37
>Bug party triage.  Gnu configure calls it AC_CHECK_MEMBER; it ought to be a fairly simple wrapper around TryLink().

Edited: link updated:  https://www.gnu.org/software/autoconf/manual/autoconf-2.71/html_node/Generic-Structures.html


Discussion:
I've implemented for my project like this:
```py
def CheckMember(context, aggregate_member: str, header = None, language = None):
    """
    Configure check for a C or C++ member "aggregate_member".
    Optional "header" can be defined to include a header file.
    "language" should be "C" or "C++" and is used to select the compiler.
    Default is "C".
    Note that this uses the current value of compiler and linker flags, make
    sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
    Returns:
        status : int
            0 if the check failed, 1 if succeeded."""
    context.Display("Checking for member %s... " % (aggregate_member))
    suffix = _lang2suffix(language)
    if suffix is None:
        context.Result(0)
        return 0
    fields = aggregate_member.split('.')
    if len(fields) != 2:
        context.Result(0)
        return 0
    aggregate, member = fields[0], fields[1]
    text = '''
%(header)s
int main(void) {
  if (sizeof ((%(aggregate)s *) 0)->%(member)s)
    return 0;
}''' % { 'header': header,
        'aggregate': aggregate,
        'member':member }

    ret = context.TryLink(text, suffix)
    #print(text, suffix, ret)
    context.Result(ret)
    return ret


def _lang2suffix(lang):
    """
    Convert a language name to a suffix.
    When "lang" is empty or None C is assumed.
    """
    if not lang or lang in ["C", "c"]:
        return '.c'
    if lang in ["c++", "C++", "cpp", "CXX", "cxx"]:
        return '.cpp'
    return None


...
    conf = Configure(env, custom_tests={'CheckMember': CheckMember})
    for member_header in [
        ('struct tm.tm_gmtoff', '#include <time.h>'),
    ]:
        if conf.CheckMember(member_header[0], member_header[1]):
            tmpConfH.write('#define %s\n' % ToMacro(member_header[0]))

```
@yuzhichang - great! Any chance you could create a PR for this so we can get it into the next release?

Resolving patch:
diff --git a/SCons/Conftest.py b/SCons/Conftest.py
--- a/SCons/Conftest.py
+++ b/SCons/Conftest.py
@@ -568,6 +568,63 @@ def CheckDeclaration(context, symbol, includes = None, language = None):
                  "Set to 1 if %s is defined." % symbol)
     return st
 
+
+def CheckMember(context, aggregate_member, header = None, language = None):
+    """
+    Configure check for a C or C++ member "aggregate_member".
+    Optional "header" can be defined to include a header file.
+    "language" should be "C" or "C++" and is used to select the compiler.
+    Default is "C".
+    Note that this uses the current value of compiler and linker flags, make
+    sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly.
+
+    Arguments:
+        aggregate_member : str
+            the member to check. For example, 'struct tm.tm_gmtoff'.
+        includes : str
+            Optional "header" can be defined to include a header file.
+        language : str
+            only C and C++ supported.
+
+    Returns the status (0 or False = Passed, True/non-zero = Failed).
+    """
+
+    lang, suffix, msg = _lang2suffix(language)
+    if msg:
+        context.Display("Cannot check for member %s: %s\n" % (aggregate_member, msg))
+        return True
+    context.Display("Checking for %s member %s... " % (lang, aggregate_member))
+    fields = aggregate_member.split('.')
+    if len(fields) != 2:
+        msg = "shall contain just one dot, for example 'struct tm.tm_gmtoff'"
+        context.Display("Cannot check for member %s: %s\n" % (aggregate_member, msg))
+        return True
+    aggregate, member = fields[0], fields[1]
+
+    # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H.
+    if context.headerfilename:
+        includetext = '#include "%s"' % context.headerfilename
+    else:
+        includetext = ''
+    if not header:
+        header = ''
+    text = '''
+%(include)s
+%(header)s
+
+int main(void) {
+  if (sizeof ((%(aggregate)s *) 0)->%(member)s)
+    return 0;
+}''' % {'include': includetext,
+        'header': header,
+        'aggregate': aggregate,
+        'member': member}
+
+    ret = context.BuildProg(text, suffix)
+    _YesNoResult(context, ret, "HAVE_" + aggregate_member, text,
+                 "Define to 1 if the system has the member `%s`." % aggregate_member)
+    return ret
+
 def CheckLib(context, libs, func_name = None, header = None,
              extra_libs = None, call = None, language = None, autoadd = 1,
              append = True):
diff --git a/SCons/SConf.py b/SCons/SConf.py
--- a/SCons/SConf.py
+++ b/SCons/SConf.py
@@ -450,6 +450,7 @@ def force_build(dependency, target, prev_ni,
                  'CheckFunc'          : CheckFunc,
                  'CheckType'          : CheckType,
                  'CheckTypeSize'      : CheckTypeSize,
+                 'CheckMember'        : CheckMember,
                  'CheckDeclaration'   : CheckDeclaration,
                  'CheckHeader'        : CheckHeader,
                  'CheckCHeader'       : CheckCHeader,
@@ -992,6 +993,13 @@ def CheckDeclaration(context, declaration, includes = "", language = None):
     context.did_show_result = 1
     return not res
 
+def CheckMember(context, aggregate_member, header = None, language = None):
+    '''Returns the status (False : failed, True : ok).'''
+    res = SCons.Conftest.CheckMember(context, aggregate_member, header=header, language=language)
+    context.did_show_result = 1
+    return not res
+
+
 def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'):
     # used by CheckHeader and CheckLibWithHeader to produce C - #include
     # statements from the specified header (list)
diff --git a/SCons/SConfTests.py b/SCons/SConfTests.py
--- a/SCons/SConfTests.py
+++ b/SCons/SConfTests.py
@@ -725,6 +725,27 @@ def test_CheckDeclaration(self):
         finally:
             sconf.Finish()
 
+    def test_CheckMember(self):
+        """Test SConf.CheckMember()
+        """
+        self._resetSConfState()
+        sconf = self.SConf.SConf(self.scons_env,
+                                 conf_dir=self.test.workpath('config.tests'),
+                                 log_file=self.test.workpath('config.log'))
+
+        try:
+            # CheckMember()
+            r = sconf.CheckMember('struct timespec.tv_sec', '#include <time.h>')
+            assert r, "did not find timespec.tv_sec"
+            r = sconf.CheckMember('struct timespec.tv_nano', '#include <time.h>')
+            assert not r, "unexpectedly found struct timespec.tv_nano"
+            r = sconf.CheckMember('hopefullynomember')
+            assert not r, "unexpectedly found hopefullynomember :%s" % r
+
+        finally:
+            sconf.Finish()
+
+
     def test_(self):
         """Test SConf.CheckType()
         """


Test patch:
diff --git a/test/Configure/config-h.py b/test/Configure/config-h.py
--- a/test/Configure/config-h.py
+++ b/test/Configure/config-h.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
+# MIT License
 #
-# __COPYRIGHT__
+# 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
@@ -25,7 +26,6 @@
 Verify creation of a config.h file from a Configure context.
 """
 
-__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
 import re
 
@@ -46,6 +46,10 @@
 r3 = conf.CheckFunc('memmove')
 r4 = conf.CheckType('int')
 r5 = conf.CheckType('noType')
+
+m1 = conf.CheckMember('struct timespec.tv_sec', '#include <time.h>')
+m2 = conf.CheckMember('struct timespec.tv_nanosec', '#include <time.h>')
+
 r6 = conf.CheckCHeader('stdio.h', '<>')
 r7 = conf.CheckCHeader('hopefullynoc-header.h')
 r8 = conf.CheckCXXHeader('vector', '<>')
@@ -66,6 +70,8 @@
 Checking for C function memmove()... yes
 Checking for C type int... yes
 Checking for C type noType... no
+Checking for C member struct timespec.tv_sec... yes
+Checking for C member struct timespec.tv_nanosec... no
 Checking for C header file stdio.h... yes
 Checking for C header file hopefullynoc-header.h... no
 Checking for C++ header file vector... yes
@@ -104,6 +110,12 @@
 /* Define to 1 if the system has the type `noType'. */
 /* #undef HAVE_NOTYPE */
 
+/* Define to 1 if the system has the member `struct timespec.tv_sec`. */
+#define HAVE_STRUCT_TIMESPEC_TV_SEC 1
+
+/* Define to 1 if the system has the member `struct timespec.tv_nanosec`. */
+/* #undef HAVE_STRUCT_TIMESPEC_TV_NANOSEC */
+
 /* Define to 1 if you have the <stdio.h> header file. */
 #define HAVE_STDIO_H 1