2010-04-18 06:43:15 +04:00
# functions for handling ABI checking of libraries
2018-01-31 12:48:43 +03:00
import os
import sys
import re
import fnmatch
from waflib import Options , Utils , Logs , Task , Build , Errors
from waflib . TaskGen import feature , before , after
2018-08-06 17:02:18 +03:00
from wafsamba import samba_utils
2010-04-18 06:43:15 +04:00
2010-04-18 09:39:14 +04:00
# these type maps cope with platform specific names for common types
# please add new type mappings into the list below
abi_type_maps = {
' _Bool ' : ' bool ' ,
2010-04-18 12:21:04 +04:00
' struct __va_list_tag * ' : ' va_list '
2010-04-18 09:39:14 +04:00
}
2018-08-01 14:26:03 +03:00
version_key = lambda x : list ( map ( int , x . split ( " . " ) ) )
2011-03-12 03:58:17 +03:00
2010-04-18 06:43:15 +04:00
def normalise_signature ( sig ) :
''' normalise a signature from gdb '''
sig = sig . strip ( )
2023-01-19 10:30:19 +03:00
sig = re . sub ( r ' ^ \ $[0-9]+ \ s= \ s \ { (.+) \ }$ ' , r ' \ 1 ' , sig )
sig = re . sub ( r ' ^ \ $[0-9]+ \ s= \ s \ { (.+) \ }( \ s0x[0-9a-f]+ \ s< \ w+>)+$ ' , r ' \ 1 ' , sig )
sig = re . sub ( r ' ^ \ $[0-9]+ \ s= \ s(0x[0-9a-f]+) \ s?(< \ w+>)?$ ' , r ' \ 1 ' , sig )
sig = re . sub ( r ' 0x[0-9a-f]+ ' , ' 0xXXXX ' , sig )
2012-03-20 05:31:02 +04:00
sig = re . sub ( ' " , <incomplete sequence ( \\ \\ [a-z0-9]+)> ' , r ' \ 1 " ' , sig )
2010-04-18 09:39:14 +04:00
for t in abi_type_maps :
2010-04-18 12:21:04 +04:00
# we need to cope with non-word characters in mapped types
m = t
2023-01-19 10:30:19 +03:00
m = m . replace ( ' * ' , r ' \ * ' )
2010-04-18 12:21:04 +04:00
if m [ - 1 ] . isalnum ( ) or m [ - 1 ] == ' _ ' :
m + = ' \\ b '
if m [ 0 ] . isalnum ( ) or m [ 0 ] == ' _ ' :
m = ' \\ b ' + m
sig = re . sub ( m , abi_type_maps [ t ] , sig )
2010-04-18 06:43:15 +04:00
return sig
2012-03-20 05:31:02 +04:00
2010-04-18 06:43:15 +04:00
def normalise_varargs ( sig ) :
''' cope with older versions of gdb '''
2023-01-19 10:30:19 +03:00
sig = re . sub ( r ' , \ s \ . \ . \ . ' , ' ' , sig )
wafsamba: Normalize strings in gdb output when comparing ABI
This fixes an issue with gdb >= 13:
libndr.so: symbol ndr_transfer_syntax_ndr64 has changed
old_signature: uuid = {
time_low = 1903232307,
time_mid = 48826,
time_hi_and_version = 18743,
clock_seq = "\203\031",
node = "\265\333\357\234\314\066"
}, if_version = 1
new_signature: uuid = {
time_low = 1903232307,
time_mid = 48826,
time_hi_and_version = 18743,
clock_seq = "\203\031",
node = "\265\333\357\234\3146"
}, if_version = 1
\314\066 and \3146 are the same as \066 translates into the char '6'. In order
to address this we should do byte comparison in python.
Pair-Programmed-With: Andreas Schneider <asn@samba.org>
Signed-off-by: Andreas Schneider <asn@samba.org>
Signed-off-by: Alexander Bokovoy <ab@redhat.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2023-04-24 15:29:49 +03:00
# Make sure we compare bytes and not strings
return bytes ( sig , encoding = ' utf-8 ' ) . decode ( ' unicode_escape ' )
2010-04-18 06:43:15 +04:00
2012-03-20 05:31:02 +04:00
2010-04-18 06:43:15 +04:00
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
2013-04-03 16:52:06 +04:00
negative = False
2010-04-18 06:43:15 +04:00
for p in abi_match :
2010-04-18 12:21:04 +04:00
if p [ 0 ] == ' ! ' and fnmatch . fnmatch ( sa [ 0 ] , p [ 1 : ] ) :
2013-04-03 16:52:06 +04:00
negative = True
2010-04-18 12:21:04 +04:00
break
elif fnmatch . fnmatch ( sa [ 0 ] , p ) :
2010-04-18 06:43:15 +04:00
matched = True
break
2013-04-03 16:52:06 +04:00
if ( not matched ) and negative :
2010-04-18 06:43:15 +04:00
continue
2012-05-10 09:07:32 +04:00
Logs . debug ( " %s -> %s " % ( sa [ 1 ] , normalise_signature ( sa [ 1 ] ) ) )
2010-04-18 06:43:15 +04:00
ret [ sa [ 0 ] ] = normalise_signature ( sa [ 1 ] )
return ret
def save_sigs ( sig_file , parsed_sigs ) :
''' save ABI signatures to a file '''
2019-08-25 23:53:59 +03:00
sigs = " " . join ( ' %s : %s \n ' % ( s , parsed_sigs [ s ] ) for s in sorted ( parsed_sigs . keys ( ) ) )
2010-04-18 06:43:15 +04:00
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 )
2019-02-06 18:27:41 +03:00
sigs = samba_utils . get_string ( Utils . cmd_output ( [ abi_gen , libpath ] ) )
2010-04-18 06:43:15 +04:00
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 ) :
2018-01-31 12:48:43 +03:00
raise Errors . WafError ( ' Failed to save ABI file " %s " ' % sig_file )
2010-04-18 06:43:15 +04:00
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 \t signature: %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 \t old_signature: %s \n \t new_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 \t signature: %s ' % (
libname , s , parsed_sigs [ s ] ) )
got_error = True
if got_error :
2018-01-31 12:48:43 +03:00
raise Errors . WafError ( ' ABI for %s has changed - please fix library version then build with --abi-update \n See http://wiki.samba.org/index.php/Waf#ABI_Checking for more information \n If you have not changed any ABI, and your platform always gives this error, please configure with --abi-check-disable to skip this check ' % libname )
2010-04-18 06:43:15 +04:00
2018-01-31 12:48:43 +03:00
t = Task . task_factory ( ' abi_check ' , abi_check_task , color = ' BLUE ' , ext_in = ' .bin ' )
2010-04-18 06:43:15 +04:00
t . quiet = True
2010-04-20 06:49:50 +04:00
# allow "waf --abi-check" to force re-checking the ABI
if ' --abi-check ' in sys . argv :
2018-06-15 16:32:33 +03:00
t . always_run = True
2010-04-18 06:43:15 +04:00
@after ( ' apply_link ' )
@feature ( ' abi_check ' )
def abi_check ( self ) :
''' check that ABI matches saved signatures '''
env = self . bld . env
2010-12-09 03:10:45 +03:00
if not env . ABI_CHECK or self . abi_directory is None :
2010-04-18 06:43:15 +04:00
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 ' )
2015-08-14 13:17:48 +03:00
abi_file = " %s / %s - %s .sigs " % ( self . abi_directory , self . version_libname ,
2021-07-01 16:29:46 +03:00
self . abi_vnum )
2010-12-09 03:10:45 +03:00
2010-04-18 06:43:15 +04:00
tsk = self . create_task ( ' abi_check ' , self . link_task . outputs [ 0 ] )
2010-12-09 03:10:45 +03:00
tsk . ABI_FILE = abi_file
2010-04-18 06:43:15 +04:00
tsk . ABI_MATCH = self . abi_match
tsk . ABI_GEN = abi_gen
2010-12-09 03:10:45 +03:00
def abi_process_file ( fname , version , symmap ) :
''' process one ABI file, adding new symbols to the symmap '''
2015-06-26 21:48:43 +03:00
for line in Utils . readf ( fname ) . splitlines ( ) :
2010-12-09 03:10:45 +03:00
symname = line . split ( " : " ) [ 0 ]
if not symname in symmap :
symmap [ symname ] = version
2021-08-18 18:34:09 +03:00
def version_script_map_process_file ( fname , version , abi_match ) :
''' process one standard version_script file, adding the symbols to the
abi_match '''
in_section = False
in_global = False
in_local = False
for _line in Utils . readf ( fname ) . splitlines ( ) :
line = _line . strip ( )
if line == " " :
continue
if line . startswith ( " # " ) :
continue
if line . endswith ( " { " ) :
in_section = True
continue
if line == " }; " :
assert in_section
in_section = False
in_global = False
in_local = False
continue
if not in_section :
continue
if line == " global: " :
in_global = True
in_local = False
continue
if line == " local: " :
in_global = False
in_local = True
continue
symname = line . split ( " ; " ) [ 0 ]
assert symname != " "
if in_local :
if symname == " * " :
continue
symname = " ! %s " % symname
if not symname in abi_match :
abi_match . append ( symname )
2012-09-27 20:30:47 +04:00
2012-11-05 22:36:28 +04:00
def abi_write_vscript ( f , libname , current_version , versions , symmap , abi_match ) :
""" Write a vscript file for a library in --version-script format.
: param f : File - like object to write to
2011-02-28 19:13:07 +03:00
: param libname : Name of the library , uppercased
: param current_version : Current version
: param versions : Versions to consider
: param symmap : Dictionary mapping symbols - > version
2012-11-05 22:36:28 +04:00
: param abi_match : List of symbols considered to be public in the current
version
"""
2010-12-09 03:10:45 +03:00
invmap = { }
for s in symmap :
invmap . setdefault ( symmap [ s ] , [ ] ) . append ( s )
last_key = " "
2011-03-12 03:58:17 +03:00
versions = sorted ( versions , key = version_key )
2011-03-12 03:09:31 +03:00
for k in versions :
2010-12-09 03:10:45 +03:00
symver = " %s _ %s " % ( libname , k )
2011-02-28 19:13:07 +03:00
if symver == current_version :
2010-12-09 03:10:45 +03:00
break
2011-02-28 19:13:07 +03:00
f . write ( " %s { \n " % symver )
2012-11-05 22:36:29 +04:00
if k in sorted ( invmap . keys ( ) ) :
2012-11-06 00:48:52 +04:00
f . write ( " \t global: \n " )
2011-02-28 19:13:07 +03:00
for s in invmap . get ( k , [ ] ) :
2023-08-29 11:47:58 +03:00
f . write ( " \t \t %s ; \n " % s )
2010-12-09 03:10:45 +03:00
f . write ( " } %s ; \n \n " % last_key )
last_key = " %s " % symver
2011-02-28 19:13:07 +03:00
f . write ( " %s { \n " % current_version )
2018-08-02 16:42:55 +03:00
local_abi = list ( filter ( lambda x : x [ 0 ] == ' ! ' , abi_match ) )
global_abi = list ( filter ( lambda x : x [ 0 ] != ' ! ' , abi_match ) )
2010-12-18 00:23:52 +03:00
f . write ( " \t global: \n " )
2012-08-30 19:46:23 +04:00
if len ( global_abi ) > 0 :
for x in global_abi :
f . write ( " \t \t %s ; \n " % x )
else :
f . write ( " \t \t *; \n " )
2018-07-12 10:19:41 +03:00
# Always hide symbols that must be local if exist
local_abi . extend ( [ " !_end " , " !__bss_start " , " !_edata " ] )
f . write ( " \t local: \n " )
for x in local_abi :
f . write ( " \t \t %s ; \n " % x [ 1 : ] )
if global_abi != [ " * " ] :
2012-11-05 22:36:30 +04:00
if len ( global_abi ) > 0 :
f . write ( " \t \t *; \n " )
2010-12-18 00:23:52 +03:00
f . write ( " }; \n " )
2010-12-09 03:10:45 +03:00
def abi_build_vscript ( task ) :
''' generate a vscript file for our public libraries '''
tgt = task . outputs [ 0 ] . bldpath ( task . env )
symmap = { }
2011-02-28 19:13:07 +03:00
versions = [ ]
2021-07-01 16:29:46 +03:00
abi_match = list ( task . env . ABI_MATCH )
2010-12-09 03:10:45 +03:00
for f in task . inputs :
fname = f . abspath ( task . env )
basename = os . path . basename ( fname )
2021-08-18 18:20:12 +03:00
if basename . endswith ( " .sigs " ) :
version = basename [ len ( task . env . LIBNAME ) + 1 : - len ( " .sigs " ) ]
versions . append ( version )
abi_process_file ( fname , version , symmap )
continue
2021-08-18 18:34:09 +03:00
if basename == " version-script.map " :
version_script_map_process_file ( fname , task . env . VERSION , abi_match )
continue
2021-08-18 18:20:12 +03:00
raise Errors . WafError ( ' Unsupported input " %s " ' % fname )
2021-07-01 16:29:46 +03:00
if task . env . PRIVATE_LIBRARY :
# For private libraries we need to inject
# each public symbol explicitly into the
# abi match array and remove all explicit
# versioning so that each exported symbol
# is tagged with the private library tag.
for s in symmap :
abi_match . append ( s )
symmap = { }
versions = [ ]
2012-11-05 22:36:28 +04:00
f = open ( tgt , mode = ' w ' )
try :
abi_write_vscript ( f , task . env . LIBNAME , task . env . VERSION , versions ,
2021-07-01 16:29:46 +03:00
symmap , abi_match )
2012-11-05 22:36:28 +04:00
finally :
f . close ( )
2010-12-09 03:10:45 +03:00
2021-08-18 18:34:09 +03:00
def VSCRIPT_MAP_PRIVATE ( bld , libname , orig_vscript , version , private_vscript ) :
2024-08-05 15:51:01 +03:00
version = re . sub ( r ' [^. \ w] ' , ' _ ' , version ) . upper ( )
2021-08-18 18:34:09 +03:00
t = bld . SAMBA_GENERATOR ( private_vscript ,
rule = abi_build_vscript ,
source = orig_vscript ,
group = ' vscripts ' ,
target = private_vscript )
t . env . ABI_MATCH = [ ]
t . env . VERSION = version
t . env . LIBNAME = libname
t . env . PRIVATE_LIBRARY = True
t . vars = [ ' LIBNAME ' , ' VERSION ' , ' ABI_MATCH ' , ' PRIVATE_LIBRARY ' ]
Build . BuildContext . VSCRIPT_MAP_PRIVATE = VSCRIPT_MAP_PRIVATE
2010-12-09 03:10:45 +03:00
2021-07-01 16:29:46 +03:00
def ABI_VSCRIPT ( bld , libname , abi_directory , version , vscript , abi_match = None , private_library = False ) :
2010-12-09 03:10:45 +03:00
''' generate a vscript file for our public libraries '''
if abi_directory :
2015-11-19 03:36:47 +03:00
source = bld . path . ant_glob ( ' %s / %s -[0-9]*.sigs ' % ( abi_directory , libname ) , flat = True )
2011-03-12 03:58:17 +03:00
def abi_file_key ( path ) :
return version_key ( path [ : - len ( " .sigs " ) ] . rsplit ( " - " ) [ - 1 ] )
source = sorted ( source . split ( ) , key = abi_file_key )
2010-12-09 03:10:45 +03:00
else :
source = ' '
2021-07-01 16:29:46 +03:00
if private_library is None :
private_library = False
2011-02-17 06:42:19 +03:00
libname = os . path . basename ( libname )
version = os . path . basename ( version )
2024-08-05 15:51:01 +03:00
libname = re . sub ( r ' [^. \ w] ' , ' _ ' , libname ) . upper ( )
version = re . sub ( r ' [^. \ w] ' , ' _ ' , version ) . upper ( )
2010-12-09 03:10:45 +03:00
t = bld . SAMBA_GENERATOR ( vscript ,
rule = abi_build_vscript ,
source = source ,
group = ' vscripts ' ,
target = vscript )
2010-12-18 00:23:52 +03:00
if abi_match is None :
abi_match = [ " * " ]
else :
abi_match = samba_utils . TO_LIST ( abi_match )
t . env . ABI_MATCH = abi_match
2010-12-09 04:24:48 +03:00
t . env . VERSION = version
2010-12-09 03:10:45 +03:00
t . env . LIBNAME = libname
2021-07-01 16:29:46 +03:00
t . env . PRIVATE_LIBRARY = private_library
t . vars = [ ' LIBNAME ' , ' VERSION ' , ' ABI_MATCH ' , ' PRIVATE_LIBRARY ' ]
2010-12-09 03:10:45 +03:00
Build . BuildContext . ABI_VSCRIPT = ABI_VSCRIPT