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)