Author: brane
Date: Tue Jun 10 11:32:35 2025
New Revision: 1926330

URL: http://svn.apache.org/viewvc?rev=1926330&view=rev
Log:
Teach the SCons build to limit exported symbol visibility in Mach-O and
ELF shared libraries, just like the CMake build does.

* build/exports.py:
  (ExportGenerator): New, exported symbols generator. Based on the previous
   gen_def.py, but generates more than just DLL .def files.
* build/gen_def.py: Use the new ExportGenerator from exports.py.
* SConstruct
  (GenExports): Add a builder for Mach-O .exp and ELF .map files and use it
   on supported platforms. We still use gen_def.py for Windows.

* build/SerfPlatform.cmake: Add NetBSD to the recognized ELF platform list.

Added:
    serf/trunk/build/exports.py   (with props)
Modified:
    serf/trunk/SConstruct
    serf/trunk/build/SerfPlatform.cmake
    serf/trunk/build/gen_def.py

Modified: serf/trunk/SConstruct
URL: 
http://svn.apache.org/viewvc/serf/trunk/SConstruct?rev=1926330&r1=1926329&r2=1926330&view=diff
==============================================================================
--- serf/trunk/SConstruct (original)
+++ serf/trunk/SConstruct Tue Jun 10 11:32:35 2025
@@ -40,6 +40,7 @@ except ImportError:
 src_dir = File('SConstruct').rfile().get_dir().abspath
 sys.path.insert(0, src_dir)
 import build.scons_extras
+import build.exports
 
 custom_tests = {'CheckGnuCC': build.scons_extras.CheckGnuCC}
 
@@ -213,14 +214,31 @@ env = Environment(variables=opts,
                   CPPPATH=['.', ],
                   )
 
+# "Legacy" .def file builder
 gen_def_script = env.File('build/gen_def.py').rstr()
-
 env.Append(BUILDERS = {
     'GenDef' :
       Builder(action = '"%s" "%s" $SOURCES > $TARGET' % (sys.executable, 
gen_def_script,),
               suffix='.def', src_suffix='.h')
   })
 
+# Export symbol generator (for Windows DLL, Mach-O and ELF)
+export_generator = build.exports.ExportGenerator()
+if export_generator.target is None:
+  # Nothing to do on this platform
+  export_generator = None
+else:
+  def generate_exports(target, source, env):
+    for target_path in (str(t) for t in target):
+      stream = open(target_path, 'wt')
+      export_generator.generate(stream, *(str(s) for s in source))
+      stream.close()
+  env.Append(BUILDERS = {
+    'GenExports': Builder(action=generate_exports,
+                          suffix=export_generator.target.ext,
+                          src_suffix='.h')
+  })
+
 match = re.search('SERF_MAJOR_VERSION ([0-9]+).*'
                   'SERF_MINOR_VERSION ([0-9]+).*'
                   'SERF_PATCH_VERSION ([0-9]+)',
@@ -294,6 +312,11 @@ elif env['SHLIBPREFIX'] == '$LIBPREFIX':
 else:
   SHLIBNAME = '%sserf-%d' % (env['SHLIBPREFIX'], MAJOR)
 
+if export_generator is None:
+  export_filter = None
+else:
+  export_filter = '%s%s' % (SHLIBNAME, export_generator.target.ext)
+
 env.Append(RPATH=[libdir],
            PDB='${TARGET.filebase}.pdb')
 
@@ -359,16 +382,21 @@ else:
 # PLAN THE BUILD
 SHARED_SOURCES = []
 if sys.platform == 'win32':
-  env.GenDef(['serf.h','serf_bucket_types.h', 'serf_bucket_util.h'])
-  SHARED_SOURCES.append(['serf.def'])
+  env.GenDef(target=[export_filter], source=HEADER_FILES)
+  SHARED_SOURCES.append([export_filter])
   dll_res = env.RES(['serf.rc'])
   SHARED_SOURCES.append(dll_res)
+  # TODO: Use GenExports instead.
+  export_filter = None
 
 SOURCES = Glob('src/*.c') + Glob('buckets/*.c') + Glob('auth/*.c') + \
           Glob('protocols/*.c')
 
 lib_static = env.StaticLibrary(LIBNAME, SOURCES)
 lib_shared = env.SharedLibrary(SHLIBNAME, SOURCES + SHARED_SOURCES)
+if export_filter is not None:
+  env.GenExports(target=export_filter, source=HEADER_FILES)
+  env.Depends(lib_shared, export_filter)
 
 # Define OPENSSL_NO_STDIO to prevent using _fp() API.
 env.Append(CPPDEFINES=['OPENSSL_NO_STDIO'])
@@ -600,6 +628,20 @@ if brotli and CALLOUT_OKAY:
     Exit(1)
   env = conf.Finish()
 
+if CALLOUT_OKAY:
+  conf = Configure(env, custom_tests=custom_tests)
+
+  ### some configuration stuffs
+  if conf.CheckCHeader('stdbool.h'):
+    env.Append(CPPDEFINES=['HAVE_STDBOOL_H'])
+
+  env = conf.Finish()
+
+# Tweak the link flags for selecting exported symbols. Must come after the
+# config checks, because the symbols file doesn't exist yet.
+if export_filter is not None:
+  env.Append(LINKFLAGS=[export_generator.target.link_flag % export_filter])
+
 # Set preprocessor define to disable the logging framework
 if disablelogging:
     env.Append(CPPDEFINES=['SERF_DISABLE_LOGGING'])
@@ -626,16 +668,6 @@ pkgconfig = env.Textfile('serf-%d.pc' %
 
 env.Default(lib_static, lib_shared, pkgconfig)
 
-if CALLOUT_OKAY:
-  conf = Configure(env, custom_tests=custom_tests)
-
-  ### some configuration stuffs
-  if conf.CheckCHeader('stdbool.h'):
-    env.Append(CPPDEFINES=['HAVE_STDBOOL_H'])
-
-  env = conf.Finish()
-
-
 # INSTALLATION STUFF
 
 install_static = env.Install(libdir, lib_static)

Modified: serf/trunk/build/SerfPlatform.cmake
URL: 
http://svn.apache.org/viewvc/serf/trunk/build/SerfPlatform.cmake?rev=1926330&r1=1926329&r2=1926330&view=diff
==============================================================================
--- serf/trunk/build/SerfPlatform.cmake (original)
+++ serf/trunk/build/SerfPlatform.cmake Tue Jun 10 11:32:35 2025
@@ -43,6 +43,11 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Ope
   set(SERF_OPENBSD TRUE)
   set(SERF_ELF_TARGET TRUE)
   set(SERF_PLATFORM "OpenBSD")
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "NetBSD")
+  set(SERF_UNIX TRUE)
+  set(SERF_NETBSD TRUE)
+  set(SERF_ELF_TARGET TRUE)
+  set(SERF_PLATFORM "NetBSD")
 elseif(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
   set(SERF_WINDOWS TRUE)
   if(CMAKE_GENERATOR_PLATFORM MATCHES "(x64|ARM64|IA64)")

Added: serf/trunk/build/exports.py
URL: 
http://svn.apache.org/viewvc/serf/trunk/build/exports.py?rev=1926330&view=auto
==============================================================================
--- serf/trunk/build/exports.py (added)
+++ serf/trunk/build/exports.py Tue Jun 10 11:32:35 2025
@@ -0,0 +1,146 @@
+#
+# ====================================================================
+#    Licensed to the Apache Software Foundation (ASF) under one
+#    or more contributor license agreements.  See the NOTICE file
+#    distributed with this work for additional information
+#    regarding copyright ownership.  The ASF licenses this file
+#    to you under the Apache License, Version 2.0 (the
+#    "License"); you may not use this file except in compliance
+#    with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing,
+#    software distributed under the License is distributed on an
+#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#    KIND, either express or implied.  See the License for the
+#    specific language governing permissions and limitations
+#    under the License.
+# ====================================================================
+#
+# Generator for lists of public symbols to export from shared libraries.
+# Supports:
+#     - Windows DLL definitions files (.def)
+#     - Mach-O dylib exported symbols list (.exp -- for macOS and suchlike)
+#     - ELF version scripts (.map -- Linux, FreeBSD, OpenBSD, etc.)
+#
+
+import re
+import sys
+import collections
+
+# This regex parses function declarations that look like:
+#
+#    return_type serf_func1(...
+#    return_type *serf_func2(...
+#
+# Where return_type is a combination of words and "*" each separated by a
+# SINGLE space. If the function returns a pointer type (like serf_func2),
+# then a space may exist between the "*" and the function name. Thus,
+# a more complicated example might be:
+#    const type * const * serf_func3(...
+#
+_funcs = re.compile(r'^(?:(?:\w+|\*) )+\*?(serf_[a-z][a-zA-Z_0-9]*)\(',
+                    re.MULTILINE)
+
+# This regex parses the bucket type definitions which look like:
+#
+#    extern const serf_bucket_type_t serf_bucket_type_FOO;
+#
+_types = re.compile(r'^extern const serf_bucket_type_t (serf_[a-z_]*);',
+                    re.MULTILINE)
+
+
+# Blacklist the serf v2 API for now.
+BLACKLIST = frozenset(['serf_connection_switch_protocol',
+                       'serf_http_protocol_create',
+                       'serf_https_protocol_create',
+                       'serf_http_request_queue',
+                       ])
+
+
+# These are the types of export symbols formats that we know about.
+ExportTarget = collections.namedtuple('ExportTarget',
+                                      ('ext', 'link_flag', 'description'))
+TARGET_WINDLL = ExportTarget('.def',
+                             '/DEF:%s',
+                             'Windows dynamic link library definitions')
+TARGET_MACHO = ExportTarget('.exp',
+                            '-Wl,-exported_symbols_list,%s',
+                            'Mach-O dynamic library exported symbols')
+TARGET_ELF = ExportTarget('.map',
+                          '-Wl,--version-script,%s',
+                          'ELF shared library symbol map')
+
+
+class ExportGenerator(object):
+  '''Generic exports generator, can guess the file format.'''
+
+  def __init__(self, target=None):
+    if target is None:
+      target = self._guess_target()
+
+    self._target = target
+    self._dispatch = {
+      TARGET_WINDLL: self._gen_win_def,
+      TARGET_MACHO: self._gen_macho_exp,
+      TARGET_ELF: self._gen_elf_map,
+    }
+
+  @property
+  def target(self):
+    return self._target
+
+  def generate(self, stream, *headers):
+    exports = set()
+    for fname in headers:
+      content = open(fname).read()
+      for name in _funcs.findall(content):
+        exports.add(name)
+      for name in _types.findall(content):
+        exports.add(name)
+
+    write = self._dispatch.get(self._target, self._gen_error)
+    return write(stream, sorted(exports - BLACKLIST))
+
+  def _guess_target(self):
+    if sys.platform == 'win32':
+      return TARGET_WINDLL
+    if sys.platform == 'darwin':
+      return TARGET_MACHO
+    if sys.platform.startswith('linux'):
+      return TARGET_ELF
+    if (sys.platform.startswith('freebsd') and int(sys.platform[7:]) >= 4):
+      return TARGET_ELF
+    if (sys.platform.startswith('openbsd') and int(sys.platform[7:]) >= 6):
+      return TARGET_ELF
+    if (sys.platform.startswith('netbsd') and int(sys.platform[6:]) >= 2):
+      return TARGET_ELF
+    return None
+
+  def _gen_win_def(self, stream, symbols):
+    stream.write('EXPORTS\n')
+    for symbol in symbols:
+      stream.write('%s\n' % symbol)
+    return True
+
+  def _gen_macho_exp(self, stream, symbols):
+    stream.write('# Exported symbols\n')
+    for symbol in symbols:
+      stream.write('_%s\n' % symbol)
+    return True
+
+  def _gen_elf_map(self, stream, symbols):
+    stream.write('{\n'
+                 '  global:\n')
+    for symbol in symbols:
+      stream.write('    %s;\n' % symbol)
+    stream.write('  local:\n'
+                 '    *;\n'
+                 '};\n')
+    return True
+
+  def _gen_error(self, *_):
+    sys.stderr.write('error: unknown exports format: %s\n'
+                     % repr(self._target))
+    return False

Propchange: serf/trunk/build/exports.py
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: serf/trunk/build/gen_def.py
URL: 
http://svn.apache.org/viewvc/serf/trunk/build/gen_def.py?rev=1926330&r1=1926329&r2=1926330&view=diff
==============================================================================
--- serf/trunk/build/gen_def.py (original)
+++ serf/trunk/build/gen_def.py Tue Jun 10 11:32:35 2025
@@ -10,9 +10,9 @@
 #   to you under the Apache License, Version 2.0 (the
 #   "License"); you may not use this file except in compliance
 #   with the License.  You may obtain a copy of the License at
-# 
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 #   Unless required by applicable law or agreed to in writing,
 #   software distributed under the License is distributed on an
 #   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -26,55 +26,10 @@
 #    C:\PATH> python build/gen_def.py serf.h serf_bucket_types.h 
serf_bucket_util.h > build/serf.def
 #
 
-import re
 import sys
-
-# This regex parses function declarations that look like:
-#
-#    return_type serf_func1(...
-#    return_type *serf_func2(...
-#
-# Where return_type is a combination of words and "*" each separated by a
-# SINGLE space. If the function returns a pointer type (like serf_func2),
-# then a space may exist between the "*" and the function name. Thus,
-# a more complicated example might be:
-#    const type * const * serf_func3(...
-#
-_funcs = re.compile(r'^(?:(?:\w+|\*) )+\*?(serf_[a-z][a-zA-Z_0-9]*)\(',
-                    re.MULTILINE)
-
-# This regex parses the bucket type definitions which look like:
-#
-#    extern const serf_bucket_type_t serf_bucket_type_FOO;
-#
-_types = re.compile(r'^extern const serf_bucket_type_t (serf_[a-z_]*);',
-                    re.MULTILINE)
-
-
-def extract_exports(fname):
-  content = open(fname).read()
-  exports = set()
-  for name in _funcs.findall(content):
-    exports.add(name)
-  for name in _types.findall(content):
-    exports.add(name)
-  return exports
-
-
-# Blacklist the serf v2 API for now
-BLACKLIST = set(['serf_connection_switch_protocol',
-                 'serf_http_protocol_create',
-                 'serf_https_protocol_create',
-                 'serf_http_request_queue',
-                 ])
-
+import exports
 
 if __name__ == '__main__':
-  # run the extraction over each file mentioned
-  import sys
-  print("EXPORTS")
-
-  for fname in sys.argv[1:]:
-    funclist = extract_exports(fname) - BLACKLIST
-    for func in funclist:
-      print(func)
+  gen = exports.ExportGenerator(exports.TARGET_WINDLL)
+  if not gen.generate(sys.stdout, *sys.argv[1:]):
+    sys.exit(1)


Reply via email to