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)