s4-build: fixed uses of os.path.relpath()
[samba.git] / buildtools / wafsamba / symbols.py
1 # a waf tool to extract symbols from object files or libraries
2 # using nm, producing a set of exposed defined/undefined symbols
3
4 import Utils, Build, subprocess, Logs
5 from samba_wildcard import fake_build_environment
6 from samba_utils import *
7
8 def symbols_extract(objfiles, dynamic=False):
9     '''extract symbols from objfile, returning a dictionary containing
10        the set of undefined and public symbols for each file'''
11
12     ret = {}
13
14     cmd = ["nm"]
15     if dynamic:
16         # needed for some .so files
17         cmd.append("-D")
18     cmd.extend(objfiles)
19
20     nmpipe = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
21     if len(objfiles) == 1:
22         filename = objfiles[0]
23         ret[filename] = { "PUBLIC": set(), "UNDEFINED" : set()}
24
25     for line in nmpipe:
26         line = line.strip()
27         if line.endswith(':'):
28             filename = line[:-1]
29             ret[filename] = { "PUBLIC": set(), "UNDEFINED" : set() }
30             continue
31         cols = line.split(" ")
32         if cols == ['']:
33             continue
34         # see if the line starts with an address
35         if len(cols) == 3:
36             symbol_type = cols[1]
37             symbol = cols[2]
38         else:
39             symbol_type = cols[0]
40             symbol = cols[1]
41         if symbol_type in "BDGTRVWSi":
42             # its a public symbol
43             ret[filename]["PUBLIC"].add(symbol)
44         elif symbol_type in "U":
45             ret[filename]["UNDEFINED"].add(symbol)
46
47     return ret
48
49
50 def real_name(name):
51     if name.find(".objlist") != -1:
52         name = name[:-8]
53     return name
54
55
56 def find_syslib_path(bld, libname, deps):
57     '''find the path to the syslib we will link against'''
58     # the strategy is to use the targets that depend on the library, and run ldd
59     # on it to find the real location of the library that is used
60
61     linkpath = deps[0].link_task.outputs[0].abspath(bld.env)
62
63     if libname == "python":
64         libname += bld.env.PYTHON_VERSION
65
66     ret = None
67
68     lddpipe = subprocess.Popen(['ldd', linkpath], stdout=subprocess.PIPE).stdout
69     for line in lddpipe:
70         line = line.strip()
71         cols = line.split(" ")
72         if len(cols) < 3 or cols[1] != "=>":
73             continue
74         if cols[0].startswith("lib%s." % libname.lower()):
75             ret = cols[2]
76         if cols[0].startswith("libc."):
77             # save this one too
78             bld.env.libc_path = cols[2]
79     return ret
80
81
82 def build_symbol_sets(bld, tgt_list):
83     '''build the public_symbols and undefined_symbols attributes for each target'''
84
85     objlist = []  # list of object file
86     objmap = {}   # map from object filename to target
87
88
89     for t in tgt_list:
90         t.public_symbols = set()
91         t.undefined_symbols = set()
92         for tsk in getattr(t, 'compiled_tasks', []):
93             for output in tsk.outputs:
94                 objpath = output.abspath(bld.env)
95                 objlist.append(objpath)
96                 objmap[objpath] = t
97
98     symbols = symbols_extract(objlist)
99     for obj in objlist:
100         t = objmap[obj]
101         t.public_symbols = t.public_symbols.union(symbols[obj]["PUBLIC"])
102         t.undefined_symbols = t.undefined_symbols.union(symbols[obj]["UNDEFINED"])
103
104     t.undefined_symbols = t.undefined_symbols.difference(t.public_symbols)
105
106     # and the reverse map of public symbols to subsystem name
107     bld.env.symbol_map = {}
108
109     for t in tgt_list:
110         for s in t.public_symbols:
111             bld.env.symbol_map[s] = real_name(t.sname)
112
113     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
114
115     bld.env.public_symbols = {}
116     for t in tgt_list:
117         name = real_name(t.sname)
118         if name in bld.env.public_symbols:
119             bld.env.public_symbols[name] = bld.env.public_symbols[name].union(t.public_symbols)
120         else:
121             bld.env.public_symbols[name] = t.public_symbols
122         if t.samba_type == 'LIBRARY':
123             for dep in t.add_objects:
124                 t2 = bld.name_to_obj(dep, bld.env)
125                 bld.ASSERT(t2 is not None, "Library '%s' has unknown dependency '%s'" % (name, dep))
126                 bld.env.public_symbols[name] = bld.env.public_symbols[name].union(t2.public_symbols)
127
128
129 def build_syslib_sets(bld, tgt_list):
130     '''build the public_symbols for all syslibs'''
131
132     # work out what syslibs we depend on, and what targets those are used in
133     syslibs = {}
134     objmap = {}
135     for t in tgt_list:
136         if getattr(t, 'uselib', []) and t.samba_type in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
137             for lib in t.uselib:
138                 if lib in ['PYEMBED', 'PYEXT']:
139                     lib = "python"
140                 if not lib in syslibs:
141                     syslibs[lib] = []
142                 syslibs[lib].append(t)
143
144     # work out the paths to each syslib
145     syslib_paths = []
146     for lib in syslibs:
147         path = find_syslib_path(bld, lib, syslibs[lib])
148         if path is None:
149             Logs.warn("Unable to find syslib path for %s" % lib)
150         if path is not None:
151             syslib_paths.append(path)
152             objmap[path] = lib.lower()
153
154     # add in libc
155     syslib_paths.append(bld.env.libc_path)
156     objmap[bld.env.libc_path] = 'c'
157
158     symbols = symbols_extract(syslib_paths, dynamic=True)
159
160     # keep a map of syslib names to public symbols
161     bld.env.syslib_symbols = {}
162     for lib in symbols:
163         bld.env.syslib_symbols[lib] = symbols[lib]["PUBLIC"]
164
165     # add to the map of symbols to dependencies
166     for lib in symbols:
167         for sym in symbols[lib]["PUBLIC"]:
168             bld.env.symbol_map[sym] = objmap[lib]
169
170     # keep the libc symbols as well, as these are useful for some of the
171     # sanity checks
172     bld.env.libc_symbols = symbols[bld.env.libc_path]["PUBLIC"]
173
174     # add to the combined map of dependency name to public_symbols
175     for lib in bld.env.syslib_symbols:
176         bld.env.public_symbols[objmap[lib]] = bld.env.syslib_symbols[lib]
177
178 def build_autodeps(bld, t):
179     '''build the set of dependencies for a target'''
180     deps = set()
181     name = real_name(t.sname)
182
183     targets    = LOCAL_CACHE(bld, 'TARGET_TYPE')
184
185     for sym in t.undefined_symbols:
186         if sym in t.public_symbols:
187             continue
188         if sym in bld.env.symbol_map:
189             depname = bld.env.symbol_map[sym]
190             if depname == name:
191                 # self dependencies aren't interesting
192                 continue
193             if t.in_library == [depname]:
194                 # no need to depend on the library we are part of
195                 continue
196             if depname in ['c', 'python']:
197                 # these don't go into autodeps
198                 continue
199             if targets[depname] in [ 'SYSLIB' ]:
200                 deps.add(depname)
201                 continue
202             t2 = bld.name_to_obj(depname, bld.env)
203             if len(t2.in_library) != 1:
204                 deps.add(depname)
205                 continue
206             if t2.in_library == t.in_library:
207                 # if we're part of the same library, we don't need to autodep
208                 continue
209             deps.add(t2.in_library[0])
210     t.autodeps = deps
211
212
213 def build_library_names(bld, tgt_list):
214     '''add a in_library attribute to all targets that are part of a library'''
215     for t in tgt_list:
216         t.in_library = []
217
218     for t in tgt_list:
219         if t.samba_type in [ 'LIBRARY' ]:
220             for obj in t.samba_deps_extended:
221                 t2 = bld.name_to_obj(obj, bld.env)
222                 if t2 and t2.samba_type in [ 'SUBSYSTEM', 'ASN1' ]:
223                     if not t.sname in t2.in_library:
224                         t2.in_library.append(t.sname)
225
226
227 def check_library_deps(bld, t):
228     '''check that all the autodeps that have mutual dependency of this
229     target are in the same library as the target'''
230
231     name = real_name(t.sname)
232
233     if len(t.in_library) > 1:
234         Logs.warn("WARNING: Target '%s' in multiple libraries: %s" % (t.sname, t.in_library))
235
236     for dep in t.autodeps:
237         t2 = bld.name_to_obj(dep, bld.env)
238         if t2 is None:
239             continue
240         for dep2 in t2.autodeps:
241             if dep2 == name and t.in_library != t2.in_library:
242                 Logs.warn("WARNING: mutual dependency %s <=> %s" % (name, real_name(t2.sname)))
243                 Logs.warn("Libraries should match. %s != %s" % (t.in_library, t2.in_library))
244                 # raise Utils.WafError("illegal mutual dependency")
245
246
247 def check_syslib_collisions(bld, tgt_list):
248     '''check if a target has any symbol collisions with a syslib
249
250     We do not want any code in Samba to use a symbol name from a
251     system library. The chance of that causing problems is just too
252     high. Note that libreplace uses a rep_XX approach of renaming
253     symbols via macros
254     '''
255
256     has_error = False
257     for t in tgt_list:
258         for lib in bld.env.syslib_symbols:
259             common = t.public_symbols.intersection(bld.env.syslib_symbols[lib])
260             if common:
261                 Logs.error("ERROR: Target '%s' has symbols '%s' which is also in syslib '%s'" % (t.sname, common, lib))
262                 has_error = True
263     if has_error:
264         raise Utils.WafError("symbols in common with system libraries")
265
266
267 def check_dependencies(bld, t):
268     '''check for depenencies that should be changed'''
269
270     if bld.name_to_obj(t.sname + ".objlist", bld.env):
271         return
272
273     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
274
275     remaining = t.undefined_symbols.copy()
276     remaining = remaining.difference(t.public_symbols)
277
278     sname = real_name(t.sname)
279
280     deps = set(t.samba_deps)
281     for d in t.samba_deps:
282         if targets[d] in [ 'EMPTY', 'DISABLED', 'SYSLIB' ]:
283             continue
284         bld.ASSERT(d in bld.env.public_symbols, "Failed to find symbol list for dependency '%s'" % d)
285         diff = remaining.intersection(bld.env.public_symbols[d])
286         if not diff and targets[sname] != 'LIBRARY':
287             Logs.info("Target '%s' has no dependency on %s" % (sname, d))
288         else:
289             remaining = remaining.difference(diff)
290
291     t.unsatisfied_symbols = set()
292     needed = {}
293     for sym in remaining:
294         if sym in bld.env.symbol_map:
295             dep = bld.env.symbol_map[sym]
296             if not dep in needed:
297                 needed[dep] = set()
298             needed[dep].add(sym)
299         else:
300             t.unsatisfied_symbols.add(sym)
301
302     for dep in needed:
303         Logs.info("Target '%s' should add dep '%s' for symbols %s" % (sname, dep, " ".join(needed[dep])))
304
305
306
307 def check_syslib_dependencies(bld, t):
308     '''check for syslib depenencies'''
309
310     if bld.name_to_obj(t.sname + ".objlist", bld.env):
311         return
312
313     sname = real_name(t.sname)
314
315     remaining = set()
316
317     features = TO_LIST(t.features)
318     if 'pyembed' in features or 'pyext' in features:
319         t.unsatisfied_symbols = t.unsatisfied_symbols.difference(bld.env.public_symbols['python'])
320
321     needed = {}
322     for sym in t.unsatisfied_symbols:
323         if sym in bld.env.symbol_map:
324             dep = bld.env.symbol_map[sym]
325             if dep == 'c':
326                 continue
327             if not dep in needed:
328                 needed[dep] = set()
329             needed[dep].add(sym)
330         else:
331             remaining.add(sym)
332
333     for dep in needed:
334         Logs.info("Target '%s' should add syslib dep '%s' for symbols %s" % (sname, dep, " ".join(needed[dep])))
335
336     if remaining:
337         debug("deps: Target '%s' has unsatisfied symbols: %s" % (sname, " ".join(remaining)))
338
339
340
341 def symbols_symbolcheck(task):
342     '''check the internal dependency lists'''
343     bld = task.env.bld
344     tgt_list = get_tgt_list(bld)
345
346     build_symbol_sets(bld, tgt_list)
347     build_library_names(bld, tgt_list)
348
349     for t in tgt_list:
350         t.autodeps = set()
351         if getattr(t, 'source', ''):
352             build_autodeps(bld, t)
353
354     for t in tgt_list:
355         check_dependencies(bld, t)
356
357     for t in tgt_list:
358         check_library_deps(bld, t)
359
360 def symbols_syslibcheck(task):
361     '''check the syslib dependencies'''
362     bld = task.env.bld
363     tgt_list = get_tgt_list(bld)
364
365     build_syslib_sets(bld, tgt_list)
366     check_syslib_collisions(bld, tgt_list)
367
368     for t in tgt_list:
369         check_syslib_dependencies(bld, t)
370
371
372 def SYMBOL_CHECK(bld):
373     '''check our dependency lists'''
374     if Options.options.SYMBOLCHECK:
375         bld.SET_BUILD_GROUP('symbolcheck')
376         task = bld(rule=symbols_symbolcheck, always=True, name='symbol checking')
377         task.env.bld = bld
378
379         bld.SET_BUILD_GROUP('syslibcheck')
380         task = bld(rule=symbols_syslibcheck, always=True, name='syslib checking')
381         task.env.bld = bld
382 Build.BuildContext.SYMBOL_CHECK = SYMBOL_CHECK