blob: 2d3bb85b8d08402e047b6f89b8cc5a76cc82dbc2 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001inherit package
2
3def splitdebuginfo(file, dvar, debugdir, debuglibdir, debugappend, debugsrcdir, sourcefile, d):
4 # Function to split a single file into two components, one is the stripped
5 # target system binary, the other contains any debugging information. The
6 # two files are linked to reference each other.
7 #
8 # sourcefile is also generated containing a list of debugsources
9
10 import stat
11 import subprocess
12
13 src = file[len(dvar):]
14 dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(src) + debugappend
15 debugfile = dvar + dest
16 # Split the file...
17 bb.utils.mkdirhier(os.path.dirname(debugfile))
18 #bb.note("Split %s -> %s" % (file, debugfile))
19 # Only store off the hard link reference if we successfully split!
20
21 dvar = d.getVar('PKGD')
22 objcopy = d.getVar("OBJCOPY")
23
24 # We ignore kernel modules, we don't generate debug info files.
25 if file.find("/lib/modules/") != -1 and file.endswith(".ko"):
26 return 1
27
28 newmode = None
29 if os.path.exists(file):
30 if not os.access(file, os.W_OK) or os.access(file, os.R_OK):
31 origmode = os.stat(file)[stat.ST_MODE]
32 newmode = origmode | stat.S_IWRITE | stat.S_IREAD
33 os.chmod(file, newmode)
34
35 # We need to extract the debug src information here...
36 if debugsrcdir:
37 append_source_info(file, sourcefile, d)
38
39 bb.utils.mkdirhier(os.path.dirname(debugfile))
40
41 if os.path.basename(src) != "udevd" and os.path.basename(src) != "init":
42 subprocess.check_output([objcopy, file, debugfile], stderr=subprocess.STDOUT)
43
44 # Set the debuglink to have the view of the file path on the target
45 if not cpath.islink(file):
46 subprocess.check_output([objcopy, '--add-gnu-debuglink', debugfile, file], stderr=subprocess.STDOUT)
47
48 if newmode:
49 os.chmod(file, origmode)
50
51 return 0
52
53python split_and_strip_files () {
54 import stat, errno
55 import subprocess
56
57 dvar = d.getVar('PKGD')
58 pn = d.getVar('PN')
59 targetos = d.getVar('TARGET_OS')
60
61 oldcwd = os.getcwd()
62 os.chdir(dvar)
63
64 # We default to '.debug' style
65 if d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-file-directory':
66 # Single debug-file-directory style debug info
67 debugappend = ".debug"
68 debugdir = ""
69 debuglibdir = "/usr/lib/debug"
70 debugsrcdir = "/usr/src/debug"
71 elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-without-src':
72 # Original OE-core, a.k.a. ".debug", style debug info, but without sources in /usr/src/debug
73 debugappend = ""
74 debugdir = "/.debug"
75 debuglibdir = ""
76 debugsrcdir = ""
77 elif d.getVar('PACKAGE_DEBUG_SPLIT_STYLE') == 'debug-with-srcpkg':
78 debugappend = ""
79 debugdir = "/.debug"
80 debuglibdir = ""
81 debugsrcdir = "/usr/src/debug"
82 else:
83 # Original OE-core, a.k.a. ".debug", style debug info
84 debugappend = ""
85 debugdir = "/.debug"
86 debuglibdir = ""
87 debugsrcdir = "/usr/src/debug"
88
89 sourcefile = d.expand("${WORKDIR}/debugsources.list")
90 bb.utils.remove(sourcefile)
91
92 #
93 # First lets figure out all of the files we may have to process ... do this only once!
94 #
95 elffiles = {}
96 symlinks = {}
97 kernmods = []
98 staticlibs = []
99 inodes = {}
100 libdir = os.path.abspath(dvar + os.sep + d.getVar("libdir"))
101 baselibdir = os.path.abspath(dvar + os.sep + d.getVar("base_libdir"))
102 skipfiles = (d.getVar("INHIBIT_PACKAGE_STRIP_FILES") or "").split()
103 if (d.getVar('INHIBIT_PACKAGE_STRIP') != '1' or \
104 d.getVar('INHIBIT_PACKAGE_DEBUG_SPLIT') != '1'):
105 checkelf = {}
106 checkelflinks = {}
107 for root, dirs, files in cpath.walk(dvar):
108 for f in files:
109 file = os.path.join(root, f)
110 if file.endswith(".ko") and file.find("/lib/modules/") != -1:
111 kernmods.append(file)
112 continue
113 if oe.package.is_static_lib(file):
114 staticlibs.append(file)
115 continue
116
117 # Skip debug files
118 if debugappend and file.endswith(debugappend):
119 continue
120 if debugdir and debugdir in os.path.dirname(file[len(dvar):]):
121 continue
122
123 if file in skipfiles:
124 continue
125
126 try:
127 ltarget = cpath.realpath(file, dvar, False)
128 s = cpath.lstat(ltarget)
129 except OSError as e:
130 (err, strerror) = e.args
131 if err != errno.ENOENT:
132 raise
133 # Skip broken symlinks
134 continue
135 if not s:
136 continue
137 # Check its an executable
138 if (s[stat.ST_MODE] & stat.S_IXUSR) or (s[stat.ST_MODE] & stat.S_IXGRP) or (s[stat.ST_MODE] & stat.S_IXOTH) \
139 or ((file.startswith(libdir) or file.startswith(baselibdir)) and (".so" in f or ".node" in f)):
140
141 if cpath.islink(file):
142 checkelflinks[file] = ltarget
143 continue
144 # Use a reference of device ID and inode number to identify files
145 file_reference = "%d_%d" % (s.st_dev, s.st_ino)
146 checkelf[file] = (file, file_reference)
147
148 results = oe.utils.multiprocess_launch(oe.package.is_elf, checkelflinks.values(), d)
149 results_map = {}
150 for (ltarget, elf_file) in results:
151 results_map[ltarget] = elf_file
152 for file in checkelflinks:
153 ltarget = checkelflinks[file]
154 # If it's a symlink, and points to an ELF file, we capture the readlink target
155 if results_map[ltarget]:
156 target = os.readlink(file)
157 #bb.note("Sym: %s (%d)" % (ltarget, results_map[ltarget]))
158 symlinks[file] = target
159
160 results = oe.utils.multiprocess_launch(oe.package.is_elf, checkelf.keys(), d)
161 for (file, elf_file) in results:
162 # It's a file (or hardlink), not a link
163 # ...but is it ELF, and is it already stripped?
164 if elf_file & 1:
165 if elf_file & 2:
166 if 'already-stripped' in (d.getVar('INSANE_SKIP_' + pn) or "").split():
167 bb.note("Skipping file %s from %s for already-stripped QA test" % (file[len(dvar):], pn))
168 else:
169 msg = "File '%s' from %s was already stripped, this will prevent future debugging!" % (file[len(dvar):], pn)
170 package_qa_handle_error("already-stripped", msg, d)
171 continue
172
173 # At this point we have an unstripped elf file. We need to:
174 # a) Make sure any file we strip is not hardlinked to anything else outside this tree
175 # b) Only strip any hardlinked file once (no races)
176 # c) Track any hardlinks between files so that we can reconstruct matching debug file hardlinks
177
178 # Use a reference of device ID and inode number to identify files
179 file_reference = checkelf[file][1]
180 if file_reference in inodes:
181 os.unlink(file)
182 os.link(inodes[file_reference][0], file)
183 inodes[file_reference].append(file)
184 else:
185 inodes[file_reference] = [file]
186 # break hardlink
187 bb.utils.break_hardlinks(file)
188 elffiles[file] = elf_file
189 # Modified the file so clear the cache
190 cpath.updatecache(file)
191
192 #
193 # First lets process debug splitting
194 #
195 if (d.getVar('INHIBIT_PACKAGE_DEBUG_SPLIT') != '1'):
196 oe.utils.multiprocess_launch(splitdebuginfo, list(elffiles), d, extraargs=(dvar, debugdir, debuglibdir, debugappend, debugsrcdir, sourcefile, d))
197
198 # Add this line to keep link so(like libc.so.6) in dbg-rpm dir.
199 oe.utils.multiprocess_launch(splitdebuginfo, list(symlinks), d, extraargs=(dvar, debugdir, debuglibdir, debugappend, debugsrcdir, sourcefile, d))
200
201 if debugsrcdir and not targetos.startswith("mingw"):
202 for file in staticlibs:
203 append_source_info(file, sourcefile, d, fatal=False)
204
205 # Hardlink our debug symbols to the other hardlink copies
206 for ref in inodes:
207 if len(inodes[ref]) == 1:
208 continue
209
210 target = inodes[ref][0][len(dvar):]
211 for file in inodes[ref][1:]:
212 src = file[len(dvar):]
213 dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(target) + debugappend
214 fpath = dvar + dest
215 ftarget = dvar + debuglibdir + os.path.dirname(target) + debugdir + "/" + os.path.basename(target) + debugappend
216 bb.utils.mkdirhier(os.path.dirname(fpath))
217 # Only one hardlink of separated debug info file in each directory
218 if not os.access(fpath, os.R_OK):
219 #bb.note("Link %s -> %s" % (fpath, ftarget))
220 os.link(ftarget, fpath)
221
222 # Create symlinks for all cases we were able to split symbols
223 for file in symlinks:
224 src = file[len(dvar):]
225 dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(src) + debugappend
226 fpath = dvar + dest
227 # Skip it if the target doesn't exist
228 try:
229 s = os.stat(fpath)
230 except OSError as e:
231 (err, strerror) = e.args
232 if err != errno.ENOENT:
233 raise
234 continue
235
236 ltarget = symlinks[file]
237 lpath = os.path.dirname(ltarget)
238 lbase = os.path.basename(ltarget)
239 ftarget = ""
240 if lpath and lpath != ".":
241 ftarget += lpath + debugdir + "/"
242 ftarget += lbase + debugappend
243 if lpath.startswith(".."):
244 ftarget = os.path.join("..", ftarget)
245 bb.utils.mkdirhier(os.path.dirname(fpath))
246 #bb.note("Symlink %s -> %s" % (fpath, ftarget))
247 if not os.path.exists(fpath):
248 os.symlink(ftarget, fpath)
249
250 # Process the debugsrcdir if requested...
251 # This copies and places the referenced sources for later debugging...
252 copydebugsources(debugsrcdir, d)
253 #
254 # End of debug splitting
255 #
256
257 #
258 # Now lets go back over things and strip them
259 #
260 if (d.getVar('INHIBIT_PACKAGE_STRIP') != '1'):
261 strip = d.getVar("STRIP")
262 sfiles = []
263 for file in elffiles:
264 elf_file = int(elffiles[file])
265 #bb.note("Strip %s" % file)
266 sfiles.append((file, elf_file, strip))
267 for f in kernmods:
268 sfiles.append((f, 16, strip))
269
270 oe.utils.multiprocess_launch(oe.package.runstrip, sfiles, d)
271
272 #
273 # End of strip
274 #
275 os.chdir(oldcwd)
276}
277
278python package_do_shlibs() {
279 import re, pipes
280 import subprocess
281
282 exclude_shlibs = d.getVar('EXCLUDE_FROM_SHLIBS', False)
283 if exclude_shlibs:
284 bb.note("not generating shlibs")
285 return
286
287 lib_re = re.compile("^.*\.so")
288 libdir_re = re.compile(".*/%s$" % d.getVar('baselib'))
289
290 packages = d.getVar('PACKAGES')
291
292 shlib_pkgs = []
293 exclusion_list = d.getVar("EXCLUDE_PACKAGES_FROM_SHLIBS")
294 if exclusion_list:
295 for pkg in packages.split():
296 if pkg not in exclusion_list.split():
297 shlib_pkgs.append(pkg)
298 else:
299 bb.note("not generating shlibs for %s" % pkg)
300 else:
301 shlib_pkgs = packages.split()
302
303 targetos = d.getVar('TARGET_OS')
304
305 workdir = d.getVar('WORKDIR')
306
307 ver = d.getVar('PKGV')
308 if not ver:
309 msg = "PKGV not defined"
310 package_qa_handle_error("pkgv-undefined", msg, d)
311 return
312
313 pkgdest = d.getVar('PKGDEST')
314
315 shlibswork_dir = d.getVar('SHLIBSWORKDIR')
316
317 def linux_so(file, pkg, pkgver, d):
318 needs_ldconfig = False
319 needed = set()
320 sonames = set()
321 renames = []
322 if "-dbg" in pkg:
323 return (needs_ldconfig, needed, sonames, renames)
324
325 ldir = os.path.dirname(file).replace(pkgdest + "/" + pkg, '')
326 cmd = d.getVar('OBJDUMP') + " -p " + pipes.quote(file) + " 2>/dev/null"
327 fd = os.popen(cmd)
328 lines = fd.readlines()
329 fd.close()
330 rpath = tuple()
331 for l in lines:
332 m = re.match("\s+RPATH\s+([^\s]*)", l)
333 if m:
334 rpaths = m.group(1).replace("$ORIGIN", ldir).split(":")
335 rpath = tuple(map(os.path.normpath, rpaths))
336 for l in lines:
337 m = re.match("\s+NEEDED\s+([^\s]*)", l)
338 if m:
339 dep = m.group(1)
340 if dep not in needed:
341 needed.add((dep, file, rpath))
342 m = re.match("\s+SONAME\s+([^\s]*)", l)
343 if m:
344 this_soname = m.group(1)
345 prov = (this_soname, ldir, pkgver)
346 if not prov in sonames:
347 # if library is private (only used by package) then do not build shlib for it
348 if not private_libs or this_soname not in private_libs:
349 sonames.add(prov)
350 if libdir_re.match(os.path.dirname(file)):
351 needs_ldconfig = True
352 if snap_symlinks and (os.path.basename(file) != this_soname):
353 renames.append((file, os.path.join(os.path.dirname(file), this_soname)))
354 return (needs_ldconfig, needed, sonames, renames)
355
356 def darwin_so(file, needed, sonames, renames, pkgver):
357 if not os.path.exists(file):
358 return
359 ldir = os.path.dirname(file).replace(pkgdest + "/" + pkg, '')
360
361 def get_combinations(base):
362 #
363 # Given a base library name, find all combinations of this split by "." and "-"
364 #
365 combos = []
366 options = base.split(".")
367 for i in range(1, len(options) + 1):
368 combos.append(".".join(options[0:i]))
369 options = base.split("-")
370 for i in range(1, len(options) + 1):
371 combos.append("-".join(options[0:i]))
372 return combos
373
374 if (file.endswith('.dylib') or file.endswith('.so')) and not pkg.endswith('-dev') and not pkg.endswith('-dbg') and not pkg.endswith('-src'):
375 # Drop suffix
376 name = os.path.basename(file).rsplit(".",1)[0]
377 # Find all combinations
378 combos = get_combinations(name)
379 for combo in combos:
380 if not combo in sonames:
381 prov = (combo, ldir, pkgver)
382 sonames.add(prov)
383 if file.endswith('.dylib') or file.endswith('.so'):
384 rpath = []
385 p = subprocess.Popen([d.expand("${HOST_PREFIX}otool"), '-l', file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
386 out, err = p.communicate()
387 # If returned successfully, process stdout for results
388 if p.returncode == 0:
389 for l in out.split("\n"):
390 l = l.strip()
391 if l.startswith('path '):
392 rpath.append(l.split()[1])
393
394 p = subprocess.Popen([d.expand("${HOST_PREFIX}otool"), '-L', file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
395 out, err = p.communicate()
396 # If returned successfully, process stdout for results
397 if p.returncode == 0:
398 for l in out.split("\n"):
399 l = l.strip()
400 if not l or l.endswith(":"):
401 continue
402 if "is not an object file" in l:
403 continue
404 name = os.path.basename(l.split()[0]).rsplit(".", 1)[0]
405 if name and name not in needed[pkg]:
406 needed[pkg].add((name, file, tuple()))
407
408 def mingw_dll(file, needed, sonames, renames, pkgver):
409 if not os.path.exists(file):
410 return
411
412 if file.endswith(".dll"):
413 # assume all dlls are shared objects provided by the package
414 sonames.add((os.path.basename(file), os.path.dirname(file).replace(pkgdest + "/" + pkg, ''), pkgver))
415
416 if (file.endswith(".dll") or file.endswith(".exe")):
417 # use objdump to search for "DLL Name: .*\.dll"
418 p = subprocess.Popen([d.expand("${HOST_PREFIX}objdump"), "-p", file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
419 out, err = p.communicate()
420 # process the output, grabbing all .dll names
421 if p.returncode == 0:
422 for m in re.finditer("DLL Name: (.*?\.dll)$", out.decode(), re.MULTILINE | re.IGNORECASE):
423 dllname = m.group(1)
424 if dllname:
425 needed[pkg].add((dllname, file, tuple()))
426
427 if d.getVar('PACKAGE_SNAP_LIB_SYMLINKS') == "1":
428 snap_symlinks = True
429 else:
430 snap_symlinks = False
431
432 use_ldconfig = bb.utils.contains('DISTRO_FEATURES', 'ldconfig', True, False, d)
433
434 needed = {}
435
436 # Take shared lock since we're only reading, not writing
437 lf = bb.utils.lockfile(d.expand("${PACKAGELOCK}"), True)
438 shlib_provider = oe.package.read_shlib_providers(d)
439 bb.utils.unlockfile(lf)
440
441 for pkg in shlib_pkgs:
442 private_libs = d.getVar('PRIVATE_LIBS_' + pkg) or d.getVar('PRIVATE_LIBS') or ""
443 private_libs = private_libs.split()
444 needs_ldconfig = False
445 bb.debug(2, "calculating shlib provides for %s" % pkg)
446
447 pkgver = d.getVar('PKGV_' + pkg)
448 if not pkgver:
449 pkgver = d.getVar('PV_' + pkg)
450 if not pkgver:
451 pkgver = ver
452
453 needed[pkg] = set()
454 sonames = set()
455 renames = []
456 linuxlist = []
457 for file in pkgfiles[pkg]:
458 soname = None
459 if cpath.islink(file):
460 continue
461 if targetos == "darwin" or targetos == "darwin8":
462 darwin_so(file, needed, sonames, renames, pkgver)
463 elif targetos.startswith("mingw"):
464 mingw_dll(file, needed, sonames, renames, pkgver)
465 elif os.access(file, os.X_OK) or lib_re.match(file):
466 linuxlist.append(file)
467
468 if linuxlist:
469 results = oe.utils.multiprocess_launch(linux_so, linuxlist, d, extraargs=(pkg, pkgver, d))
470 for r in results:
471 ldconfig = r[0]
472 needed[pkg] |= r[1]
473 sonames |= r[2]
474 renames.extend(r[3])
475 needs_ldconfig = needs_ldconfig or ldconfig
476
477 for (old, new) in renames:
478 bb.note("Renaming %s to %s" % (old, new))
479 os.rename(old, new)
480 pkgfiles[pkg].remove(old)
481
482 shlibs_file = os.path.join(shlibswork_dir, pkg + ".list")
483 if len(sonames):
484 fd = open(shlibs_file, 'w')
485 for s in sonames:
486 if s[0] in shlib_provider and s[1] in shlib_provider[s[0]]:
487 (old_pkg, old_pkgver) = shlib_provider[s[0]][s[1]]
488 if old_pkg != pkg:
489 bb.warn('%s-%s was registered as shlib provider for %s, changing it to %s-%s because it was built later' % (old_pkg, old_pkgver, s[0], pkg, pkgver))
490 bb.debug(1, 'registering %s-%s as shlib provider for %s' % (pkg, pkgver, s[0]))
491 fd.write(s[0] + ':' + s[1] + ':' + s[2] + '\n')
492 if s[0] not in shlib_provider:
493 shlib_provider[s[0]] = {}
494 shlib_provider[s[0]][s[1]] = (pkg, pkgver)
495 fd.close()
496 if needs_ldconfig and use_ldconfig:
497 bb.debug(1, 'adding ldconfig call to postinst for %s' % pkg)
498 postinst = d.getVar('pkg_postinst_%s' % pkg)
499 if not postinst:
500 postinst = '#!/bin/sh\n'
501 postinst += d.getVar('ldconfig_postinst_fragment')
502 d.setVar('pkg_postinst_%s' % pkg, postinst)
503 bb.debug(1, 'LIBNAMES: pkg %s sonames %s' % (pkg, sonames))
504
505 assumed_libs = d.getVar('ASSUME_SHLIBS')
506 if assumed_libs:
507 libdir = d.getVar("libdir")
508 for e in assumed_libs.split():
509 l, dep_pkg = e.split(":")
510 lib_ver = None
511 dep_pkg = dep_pkg.rsplit("_", 1)
512 if len(dep_pkg) == 2:
513 lib_ver = dep_pkg[1]
514 dep_pkg = dep_pkg[0]
515 if l not in shlib_provider:
516 shlib_provider[l] = {}
517 shlib_provider[l][libdir] = (dep_pkg, lib_ver)
518
519 libsearchpath = [d.getVar('libdir'), d.getVar('base_libdir')]
520
521 for pkg in shlib_pkgs:
522 bb.debug(2, "calculating shlib requirements for %s" % pkg)
523
524 private_libs = d.getVar('PRIVATE_LIBS_' + pkg) or d.getVar('PRIVATE_LIBS') or ""
525 private_libs = private_libs.split()
526
527 deps = list()
528 for n in needed[pkg]:
529 # if n is in private libraries, don't try to search provider for it
530 # this could cause problem in case some abc.bb provides private
531 # /opt/abc/lib/libfoo.so.1 and contains /usr/bin/abc depending on system library libfoo.so.1
532 # but skipping it is still better alternative than providing own
533 # version and then adding runtime dependency for the same system library
534 if private_libs and n[0] in private_libs:
535 bb.debug(2, '%s: Dependency %s covered by PRIVATE_LIBS' % (pkg, n[0]))
536 continue
537 if n[0] in shlib_provider.keys():
538 shlib_provider_path = []
539 for k in shlib_provider[n[0]].keys():
540 shlib_provider_path.append(k)
541 match = None
542 for p in list(n[2]) + shlib_provider_path + libsearchpath:
543 if p in shlib_provider[n[0]]:
544 match = p
545 break
546 if match:
547 (dep_pkg, ver_needed) = shlib_provider[n[0]][match]
548
549 bb.debug(2, '%s: Dependency %s requires package %s (used by files: %s)' % (pkg, n[0], dep_pkg, n[1]))
550
551 if dep_pkg == pkg:
552 continue
553
554 if ver_needed:
555 dep = "%s (>= %s)" % (dep_pkg, ver_needed)
556 else:
557 dep = dep_pkg
558 if not dep in deps:
559 deps.append(dep)
560 continue
561 bb.note("Couldn't find shared library provider for %s, used by files: %s" % (n[0], n[1]))
562
563 deps_file = os.path.join(pkgdest, pkg + ".shlibdeps")
564 if os.path.exists(deps_file):
565 os.remove(deps_file)
566 if len(deps):
567 fd = open(deps_file, 'w')
568 for dep in deps:
569 fd.write(dep + '\n')
570 fd.close()
571}
572PACKAGEBUILDPKGD := " \
573 perform_packagecopy \
574 ${PACKAGE_PREPROCESS_FUNCS} \
575 split_and_strip_files \
576 fixup_perms \
577 "