build: added ABI checking to the WAF build
authorAndrew Tridgell <tridge@samba.org>
Sun, 18 Apr 2010 02:43:15 +0000 (12:43 +1000)
committerAndrew Tridgell <tridge@samba.org>
Sun, 18 Apr 2010 05:00:37 +0000 (15:00 +1000)
See http://wiki.samba.org/index.php/Waf#ABI_Checking for details

buildtools/wafsamba/samba_abi.py [new file with mode: 0644]
buildtools/wafsamba/samba_autoconf.py
buildtools/wafsamba/samba_bundled.py
buildtools/wafsamba/samba_utils.py
buildtools/wafsamba/wafsamba.py
buildtools/wafsamba/wscript

diff --git a/buildtools/wafsamba/samba_abi.py b/buildtools/wafsamba/samba_abi.py
new file mode 100644 (file)
index 0000000..6e4d8d8
--- /dev/null
@@ -0,0 +1,112 @@
+# functions for handling ABI checking of libraries
+
+import Options, Utils, os, Logs, samba_utils, sys, Task, fnmatch, re
+from TaskGen import feature, before, after
+
+def normalise_signature(sig):
+    '''normalise a signature from gdb'''
+    sig = sig.strip()
+    sig = re.sub('^\$[0-9]+\s=\s\{*', '', sig)
+    sig = re.sub('\}(\s0x[0-9a-f]+\s<\w+>)?$', '', sig)
+    sig = re.sub('0x[0-9a-f]+', '0xXXXX', sig)
+    return sig
+
+def normalise_varargs(sig):
+    '''cope with older versions of gdb'''
+    sig = re.sub(',\s\.\.\.', '', sig)
+    return sig
+
+def parse_sigs(sigs, abi_match):
+    '''parse ABI signatures file'''
+    abi_match = samba_utils.TO_LIST(abi_match)
+    ret = {}
+    a = sigs.split('\n')
+    for s in a:
+        if s.find(':') == -1:
+            continue
+        sa = s.split(':')
+        if abi_match:
+            matched = False
+            for p in abi_match:
+                if fnmatch.fnmatch(sa[0], p):
+                    matched = True
+                    break
+            if not matched:
+                continue
+        ret[sa[0]] = normalise_signature(sa[1])
+    return ret
+
+def save_sigs(sig_file, parsed_sigs):
+    '''save ABI signatures to a file'''
+    sigs = ''
+    for s in sorted(parsed_sigs.keys()):
+        sigs += '%s: %s\n' % (s, parsed_sigs[s])
+    return samba_utils.save_file(sig_file, sigs, create_dir=True)
+
+
+def abi_check_task(self):
+    '''check if the ABI has changed'''
+    abi_gen = self.ABI_GEN
+
+    libpath = self.inputs[0].abspath(self.env)
+    libname = os.path.basename(libpath)
+
+    sigs = Utils.cmd_output([abi_gen, libpath])
+    parsed_sigs = parse_sigs(sigs, self.ABI_MATCH)
+
+    sig_file = self.ABI_FILE
+
+    old_sigs = samba_utils.load_file(sig_file)
+    if old_sigs is None or Options.options.ABI_UPDATE:
+        if not save_sigs(sig_file, parsed_sigs):
+            raise Utils.WafError('Failed to save ABI file "%s"' % sig_file)
+        Logs.warn('Generated ABI signatures %s' % sig_file)
+        return
+
+    parsed_old_sigs = parse_sigs(old_sigs, self.ABI_MATCH)
+
+    # check all old sigs
+    got_error = False
+    for s in parsed_old_sigs:
+        if not s in parsed_sigs:
+            Logs.error('%s: symbol %s has been removed - please update major version\n\tsignature: %s' % (
+                libname, s, parsed_old_sigs[s]))
+            got_error = True
+        elif normalise_varargs(parsed_old_sigs[s]) != normalise_varargs(parsed_sigs[s]):
+            Logs.error('%s: symbol %s has changed - please update major version\n\told_signature: %s\n\tnew_signature: %s' % (
+                libname, s, parsed_old_sigs[s], parsed_sigs[s]))
+            got_error = True
+
+    for s in parsed_sigs:
+        if not s in parsed_old_sigs:
+            Logs.error('%s: symbol %s has been added - please mark it _PRIVATE_ or update minor version\n\tsignature: %s' % (
+                libname, s, parsed_sigs[s]))
+            got_error = True
+
+    if got_error:
+        raise Utils.WafError('ABI for %s has changed - please fix library version then build with --abi-update\nSee http://wiki.samba.org/index.php/Waf#ABI_Checking for more information' % libname)
+
+
+t = Task.task_type_from_func('abi_check', abi_check_task, color='BLUE', ext_in='.bin')
+t.quiet = True
+
+@after('apply_link')
+@feature('abi_check')
+def abi_check(self):
+    '''check that ABI matches saved signatures'''
+    env = self.bld.env
+    if not env.ABI_CHECK or self.abi_file is None:
+        return
+
+    # if the platform doesn't support -fvisibility=hidden then the ABI
+    # checks become fairly meaningless
+    if not env.HAVE_VISIBILITY_ATTR:
+        return
+
+    topsrc = self.bld.srcnode.abspath()
+    abi_gen = os.path.join(topsrc, 'buildtools/scripts/abi_gen.sh')
+
+    tsk = self.create_task('abi_check', self.link_task.outputs[0])
+    tsk.ABI_FILE = self.abi_file
+    tsk.ABI_MATCH = self.abi_match
+    tsk.ABI_GEN = abi_gen
index dca659596482e2502944fff577e380689c099fee..dd7abe2864c810a4fb8e8bcd157437e8995d64c9 100644 (file)
@@ -604,7 +604,7 @@ def ADD_EXTRA_INCLUDES(conf, includes):
 
 
 
-def CURRENT_CFLAGS(bld, target, cflags):
+def CURRENT_CFLAGS(bld, target, cflags, hide_symbols=False):
     '''work out the current flags. local flags are added first'''
     if not 'EXTRA_CFLAGS' in bld.env:
         list = []
@@ -612,6 +612,8 @@ def CURRENT_CFLAGS(bld, target, cflags):
         list = bld.env['EXTRA_CFLAGS'];
     ret = TO_LIST(cflags)
     ret.extend(list)
+    if hide_symbols and bld.env.HAVE_VISIBILITY_ATTR:
+        ret.append('-fvisibility=hidden')
     return ret
 
 
index 6a393ebf2c204e2ba18be3e241e3a7c79af4cc3e..f668d56cb67f159865c13ad8e27314758cc86215 100644 (file)
@@ -35,6 +35,7 @@ def BUILTIN_LIBRARY(bld, name):
     if bld.env.DISABLE_SHARED:
         return True
     return target_in_list(name, bld.env.BUILTIN_LIBRARIES, False)
+Build.BuildContext.BUILTIN_LIBRARY = BUILTIN_LIBRARY
 
 
 def BUILTIN_DEFAULT(opt, builtins):
index 94975c6519eba5b7ed4c0e37f2b36642765b9332..a3448b702a4863c24b13e14dfd3eb93c51dd9e65 100644 (file)
@@ -464,3 +464,39 @@ def CHECK_MAKEFLAGS(bld):
         Options.options.jobs = 1
             
 Build.BuildContext.CHECK_MAKEFLAGS = CHECK_MAKEFLAGS
+
+option_groups = {}
+
+def option_group(opt, name):
+    '''find or create an option group'''
+    global option_groups
+    if name in option_groups:
+        return option_groups[name]
+    gr = opt.add_option_group(name)
+    option_groups[name] = gr
+    return gr
+Options.Handler.option_group = option_group
+
+
+def save_file(filename, contents, create_dir=False):
+    '''save data to a file'''
+    if create_dir:
+        mkdir_p(os.path.dirname(filename))
+    try:
+        f = open(filename, 'w')
+        f.write(contents)
+        f.close()
+    except:
+        return False
+    return True
+
+
+def load_file(filename):
+    '''return contents of a file'''
+    try:
+        f = open(filename, 'r')
+        r = f.read()
+        f.close()
+    except:
+        return None
+    return r
index 633484bd6820b5d64c8223716418f4dcd0fc31f0..1714718c36bdbf03c25a169be0701264f3e5cc4c 100644 (file)
@@ -20,6 +20,7 @@ from samba_deps import *
 from samba_bundled import *
 import samba_install
 import samba_conftests
+import samba_abi
 import tru64cc
 import irixcc
 import generic_cc
@@ -108,6 +109,9 @@ def SAMBA_LIBRARY(bld, libname, source,
                   target_type='LIBRARY',
                   bundled_extension=True,
                   link_name=None,
+                  abi_file=None,
+                  abi_match=None,
+                  hide_symbols=False,
                   enabled=True):
     '''define a Samba library'''
 
@@ -142,6 +146,7 @@ def SAMBA_LIBRARY(bld, libname, source,
                         autoproto      = autoproto,
                         depends_on     = depends_on,
                         needs_python   = needs_python,
+                        hide_symbols   = hide_symbols,
                         local_include  = local_include)
 
     if libname == obj_target:
@@ -165,6 +170,11 @@ def SAMBA_LIBRARY(bld, libname, source,
         features += ' pyext'
     elif needs_python:
         features += ' pyembed'
+    if abi_file:
+        features += ' abi_check'
+
+    if abi_file:
+        abi_file = os.path.join(bld.curdir, abi_file)
 
     bld.SET_BUILD_GROUP(group)
     t = bld(
@@ -181,7 +191,9 @@ def SAMBA_LIBRARY(bld, libname, source,
         samba_inst_path = install_path,
         name           = libname,
         samba_realname  = realname,
-        samba_install   = install
+        samba_install   = install,
+        abi_file        = abi_file,
+        abi_match       = abi_match
         )
 
     if link_name:
@@ -366,6 +378,7 @@ def SAMBA_SUBSYSTEM(bld, modname, source,
                     use_hostcc=False,
                     use_global_deps=True,
                     vars=None,
+                    hide_symbols=False,
                     needs_python=False):
     '''define a Samba subsystem'''
 
@@ -395,7 +408,7 @@ def SAMBA_SUBSYSTEM(bld, modname, source,
         features       = features,
         source         = source,
         target         = modname,
-        samba_cflags   = CURRENT_CFLAGS(bld, modname, cflags),
+        samba_cflags   = CURRENT_CFLAGS(bld, modname, cflags, hide_symbols=hide_symbols),
         depends_on     = depends_on,
         samba_deps     = TO_LIST(deps),
         samba_includes = includes,
index b4fcb99684864043965915c0507ffa810bfb3570..a611797b473eb51fd7d47669b0ea04afad298dc1 100644 (file)
@@ -12,70 +12,88 @@ def set_options(opt):
 
     opt.tool_options('gnu_dirs')
 
-    opt.add_option('--bundled-libraries',
+    gr = opt.add_option_group('library handling options')
+
+    gr.add_option('--bundled-libraries',
                    help=("comma separated list of bundled libraries. May include !LIBNAME to disable bundling a library. Can be 'NONE' or 'ALL' [auto]"),
                    action="store", dest='BUNDLED_LIBS', default='')
 
     extension_default = Options.options['BUNDLED_EXTENSION_DEFAULT']
-    opt.add_option('--bundled-library-extension',
+    gr.add_option('--bundled-library-extension',
                    help=("name extension for bundled libraries [%s]" % extension_default),
                    action="store", dest='BUNDLED_EXTENSION', default=extension_default)
 
     extension_exception = Options.options['BUNDLED_EXTENSION_EXCEPTION']
-    opt.add_option('--bundled-extension-exception',
+    gr.add_option('--bundled-extension-exception',
                    help=("comman separated list of libraries to not apply extension to [%s]" % extension_exception),
                    action="store", dest='BUNDLED_EXTENSION_EXCEPTION', default=extension_exception)
 
     builtin_defauilt = Options.options['BUILTIN_LIBRARIES_DEFAULT']
-    opt.add_option('--builtin-libraries',
+    gr.add_option('--builtin-libraries',
                    help=("command separated list of libraries to build directly into binaries [%s]" % builtin_defauilt),
                    action="store", dest='BUILTIN_LIBRARIES', default=builtin_defauilt)
 
-    opt.add_option('--minimum-library-version',
+    gr.add_option('--minimum-library-version',
                    help=("list of minimum system library versions (LIBNAME1:version,LIBNAME2:version)"),
                    action="store", dest='MINIMUM_LIBRARY_VERSION', default='')
 
-    opt.add_option('--with-modulesdir',
-                   help=("modules directory [PREFIX/modules]"),
-                   action="store", dest='MODULESDIR', default='${PREFIX}/modules')
-    opt.add_option('--disable-shared',
+    gr.add_option('--disable-shared',
                    help=("Disable all use of shared libraries"),
                    action="store_true", dest='disable_shared', default=False)
-    opt.add_option('--disable-rpath',
+    gr.add_option('--disable-rpath',
                    help=("Disable use of rpath for build binaries"),
                    action="store_true", dest='disable_rpath_build', default=False)
-    opt.add_option('--disable-rpath-install',
+    gr.add_option('--disable-rpath-install',
                    help=("Disable use of rpath for installed binaries"),
                    action="store_true", dest='disable_rpath_install', default=False)
-    opt.add_option('--enable-developer',
+
+    opt.add_option('--with-modulesdir',
+                   help=("modules directory [PREFIX/modules]"),
+                   action="store", dest='MODULESDIR', default='${PREFIX}/modules')
+
+    gr = opt.option_group('developer options')
+
+    gr.add_option('-C',
+                   help='enable configure cacheing',
+                   action='store_true', dest='enable_configure_cache')
+    gr.add_option('--enable-developer',
                    help=("Turn on developer warnings and debugging"),
                    action="store_true", dest='developer', default=False)
-    opt.add_option('--picky-developer',
+    gr.add_option('--picky-developer',
                    help=("Treat all warnings as errors (enable -Werror)"),
                    action="store_true", dest='picky_developer', default=False)
-    opt.add_option('--fatal-errors',
+    gr.add_option('--fatal-errors',
                    help=("Stop compilation on first error (enable -Wfatal-errors)"),
                    action="store_true", dest='fatal_errors', default=False)
-    opt.add_option('--enable-gccdeps',
-                   help=("Enable use gcc -MD dependency module"),
+    gr.add_option('--enable-gccdeps',
+                   help=("Enable use of gcc -MD dependency module"),
                    action="store_true", dest='enable_gccdeps', default=False)
-    opt.add_option('--timestamp-dependencies',
+    gr.add_option('--timestamp-dependencies',
                    help=("use file timestamps instead of content for build dependencies (BROKEN)"),
                    action="store_true", dest='timestamp_dependencies', default=False)
-    opt.add_option('-C',
-                   help='enable configure cacheing',
-                   action='store_true', dest='enable_configure_cache')
-    opt.add_option('--pedantic',
+    gr.add_option('--pedantic',
                   help=("Enable even more compiler warnings"),
                   action='store_true', dest='pedantic', default=False)
 
-    opt.add_option('--cross-compile',
+    gr.add_option('--abi-check',
+                  help=("Check ABI signatures for libraries"),
+                  action='store_true', dest='ABI_CHECK', default=False)
+    gr.add_option('--abi-check-disable',
+                  help=("Disable ABI checking (used with --enable-developer)"),
+                  action='store_true', dest='ABI_CHECK_DISABLE', default=False)
+    gr.add_option('--abi-update',
+                  help=("Update ABI signature files for libraries"),
+                  action='store_true', dest='ABI_UPDATE', default=False)
+
+    gr = opt.add_option_group('cross compilation options')
+
+    gr.add_option('--cross-compile',
                   help=("configure for cross-compilation"),
                   action='store_true', dest='CROSS_COMPILE', default=False)
-    opt.add_option('--cross-execute',
+    gr.add_option('--cross-execute',
                   help=("command prefix to use for cross-execution in configure"),
                   action='store', dest='CROSS_EXECUTE', default='')
-    opt.add_option('--hostcc',
+    gr.add_option('--hostcc',
                   help=("set host compiler when cross compiling"),
                   action='store', dest='HOSTCC', default=False)
 
@@ -147,6 +165,11 @@ def configure(conf):
         Logs.error('ERROR: --program-prefix not supported')
         sys.exit(1)
 
+    # enable ABI checking for developers
+    conf.env.ABI_CHECK = Options.options.ABI_CHECK or Options.options.developer
+    if Options.options.ABI_CHECK_DISABLE:
+        conf.env.ABI_CHECK = False
+
     # see if we can compile and run a simple C program
     conf.CHECK_CODE('printf("hello world\\n")',
                     define='HAVE_SIMPLE_C_PROG',