Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 1 | import stat |
| 2 | import mmap |
| 3 | import subprocess |
| 4 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 5 | def runstrip(arg): |
| 6 | # Function to strip a single file, called from split_and_strip_files below |
| 7 | # A working 'file' (one which works on the target architecture) |
| 8 | # |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 9 | # The elftype is a bit pattern (explained in is_elf below) to tell |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 10 | # us what type of file we're processing... |
| 11 | # 4 - executable |
| 12 | # 8 - shared library |
| 13 | # 16 - kernel module |
| 14 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 15 | (file, elftype, strip) = arg |
| 16 | |
| 17 | newmode = None |
| 18 | if not os.access(file, os.W_OK) or os.access(file, os.R_OK): |
| 19 | origmode = os.stat(file)[stat.ST_MODE] |
| 20 | newmode = origmode | stat.S_IWRITE | stat.S_IREAD |
| 21 | os.chmod(file, newmode) |
| 22 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 23 | stripcmd = [strip] |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 24 | skip_strip = False |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 25 | # kernel module |
| 26 | if elftype & 16: |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 27 | if is_kernel_module_signed(file): |
| 28 | bb.debug(1, "Skip strip on signed module %s" % file) |
| 29 | skip_strip = True |
| 30 | else: |
| 31 | stripcmd.extend(["--strip-debug", "--remove-section=.comment", |
| 32 | "--remove-section=.note", "--preserve-dates"]) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 33 | # .so and shared library |
| 34 | elif ".so" in file and elftype & 8: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 35 | stripcmd.extend(["--remove-section=.comment", "--remove-section=.note", "--strip-unneeded"]) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 36 | # shared or executable: |
| 37 | elif elftype & 8 or elftype & 4: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 38 | stripcmd.extend(["--remove-section=.comment", "--remove-section=.note"]) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 39 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 40 | stripcmd.append(file) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 41 | bb.debug(1, "runstrip: %s" % stripcmd) |
| 42 | |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 43 | if not skip_strip: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 44 | output = subprocess.check_output(stripcmd, stderr=subprocess.STDOUT) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 45 | |
| 46 | if newmode: |
| 47 | os.chmod(file, origmode) |
| 48 | |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 49 | # Detect .ko module by searching for "vermagic=" string |
| 50 | def is_kernel_module(path): |
| 51 | with open(path) as f: |
| 52 | return mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ).find(b"vermagic=") >= 0 |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 53 | |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 54 | # Detect if .ko module is signed |
| 55 | def is_kernel_module_signed(path): |
| 56 | with open(path, "rb") as f: |
| 57 | f.seek(-28, 2) |
| 58 | module_tail = f.read() |
| 59 | return "Module signature appended" in "".join(chr(c) for c in bytearray(module_tail)) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 60 | |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 61 | # Return type (bits): |
| 62 | # 0 - not elf |
| 63 | # 1 - ELF |
| 64 | # 2 - stripped |
| 65 | # 4 - executable |
| 66 | # 8 - shared library |
| 67 | # 16 - kernel module |
| 68 | def is_elf(path): |
| 69 | exec_type = 0 |
| 70 | result = subprocess.check_output(["file", "-b", path], stderr=subprocess.STDOUT).decode("utf-8") |
| 71 | |
| 72 | if "ELF" in result: |
| 73 | exec_type |= 1 |
| 74 | if "not stripped" not in result: |
| 75 | exec_type |= 2 |
| 76 | if "executable" in result: |
| 77 | exec_type |= 4 |
| 78 | if "shared" in result: |
| 79 | exec_type |= 8 |
| 80 | if "relocatable" in result: |
| 81 | if path.endswith(".ko") and path.find("/lib/modules/") != -1 and is_kernel_module(path): |
| 82 | exec_type |= 16 |
| 83 | return (path, exec_type) |
| 84 | |
| 85 | def is_static_lib(path): |
| 86 | if path.endswith('.a') and not os.path.islink(path): |
| 87 | with open(path, 'rb') as fh: |
| 88 | # The magic must include the first slash to avoid |
| 89 | # matching golang static libraries |
| 90 | magic = b'!<arch>\x0a/' |
| 91 | start = fh.read(len(magic)) |
| 92 | return start == magic |
| 93 | return False |
| 94 | |
| 95 | def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, d, qa_already_stripped=False): |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 96 | """ |
| 97 | Strip executable code (like executables, shared libraries) _in_place_ |
| 98 | - Based on sysroot_strip in staging.bbclass |
| 99 | :param dstdir: directory in which to strip files |
| 100 | :param strip_cmd: Strip command (usually ${STRIP}) |
| 101 | :param libdir: ${libdir} - strip .so files in this directory |
| 102 | :param base_libdir: ${base_libdir} - strip .so files in this directory |
| 103 | :param qa_already_stripped: Set to True if already-stripped' in ${INSANE_SKIP} |
| 104 | This is for proper logging and messages only. |
| 105 | """ |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 106 | import stat, errno, oe.path, oe.utils |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 107 | |
| 108 | elffiles = {} |
| 109 | inodes = {} |
| 110 | libdir = os.path.abspath(dstdir + os.sep + libdir) |
| 111 | base_libdir = os.path.abspath(dstdir + os.sep + base_libdir) |
| 112 | exec_mask = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
| 113 | # |
| 114 | # First lets figure out all of the files we may have to process |
| 115 | # |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 116 | checkelf = [] |
| 117 | inodecache = {} |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 118 | for root, dirs, files in os.walk(dstdir): |
| 119 | for f in files: |
| 120 | file = os.path.join(root, f) |
| 121 | |
| 122 | try: |
| 123 | ltarget = oe.path.realpath(file, dstdir, False) |
| 124 | s = os.lstat(ltarget) |
| 125 | except OSError as e: |
| 126 | (err, strerror) = e.args |
| 127 | if err != errno.ENOENT: |
| 128 | raise |
| 129 | # Skip broken symlinks |
| 130 | continue |
| 131 | if not s: |
| 132 | continue |
| 133 | # Check its an excutable |
| 134 | if s[stat.ST_MODE] & exec_mask \ |
| 135 | or ((file.startswith(libdir) or file.startswith(base_libdir)) and ".so" in f) \ |
| 136 | or file.endswith('.ko'): |
| 137 | # If it's a symlink, and points to an ELF file, we capture the readlink target |
| 138 | if os.path.islink(file): |
| 139 | continue |
| 140 | |
| 141 | # It's a file (or hardlink), not a link |
| 142 | # ...but is it ELF, and is it already stripped? |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 143 | checkelf.append(file) |
| 144 | inodecache[file] = s.st_ino |
| 145 | results = oe.utils.multiprocess_launch(is_elf, checkelf, d) |
| 146 | for (file, elf_file) in results: |
| 147 | #elf_file = is_elf(file) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 148 | if elf_file & 1: |
| 149 | if elf_file & 2: |
| 150 | if qa_already_stripped: |
| 151 | bb.note("Skipping file %s from %s for already-stripped QA test" % (file[len(dstdir):], pn)) |
| 152 | else: |
| 153 | bb.warn("File '%s' from %s was already stripped, this will prevent future debugging!" % (file[len(dstdir):], pn)) |
| 154 | continue |
| 155 | |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 156 | if inodecache[file] in inodes: |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 157 | os.unlink(file) |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 158 | os.link(inodes[inodecache[file]], file) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 159 | else: |
| 160 | # break hardlinks so that we do not strip the original. |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 161 | inodes[inodecache[file]] = file |
| 162 | bb.utils.break_hardlinks(file) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 163 | elffiles[file] = elf_file |
| 164 | |
| 165 | # |
| 166 | # Now strip them (in parallel) |
| 167 | # |
| 168 | sfiles = [] |
| 169 | for file in elffiles: |
| 170 | elf_file = int(elffiles[file]) |
| 171 | sfiles.append((file, elf_file, strip_cmd)) |
| 172 | |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame] | 173 | oe.utils.multiprocess_launch(runstrip, sfiles, d) |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 174 | |
| 175 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 176 | def file_translate(file): |
| 177 | ft = file.replace("@", "@at@") |
| 178 | ft = ft.replace(" ", "@space@") |
| 179 | ft = ft.replace("\t", "@tab@") |
| 180 | ft = ft.replace("[", "@openbrace@") |
| 181 | ft = ft.replace("]", "@closebrace@") |
| 182 | ft = ft.replace("_", "@underscore@") |
| 183 | return ft |
| 184 | |
| 185 | def filedeprunner(arg): |
| 186 | import re, subprocess, shlex |
| 187 | |
| 188 | (pkg, pkgfiles, rpmdeps, pkgdest) = arg |
| 189 | provides = {} |
| 190 | requires = {} |
| 191 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 192 | file_re = re.compile(r'\s+\d+\s(.*)') |
| 193 | dep_re = re.compile(r'\s+(\S)\s+(.*)') |
| 194 | r = re.compile(r'[<>=]+\s+\S*') |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 195 | |
| 196 | def process_deps(pipe, pkg, pkgdest, provides, requires): |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 197 | file = None |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 198 | for line in pipe.split("\n"): |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 199 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 200 | m = file_re.match(line) |
| 201 | if m: |
| 202 | file = m.group(1) |
| 203 | file = file.replace(pkgdest + "/" + pkg, "") |
| 204 | file = file_translate(file) |
| 205 | continue |
| 206 | |
| 207 | m = dep_re.match(line) |
| 208 | if not m or not file: |
| 209 | continue |
| 210 | |
| 211 | type, dep = m.groups() |
| 212 | |
| 213 | if type == 'R': |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 214 | i = requires |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 215 | elif type == 'P': |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 216 | i = provides |
| 217 | else: |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 218 | continue |
| 219 | |
| 220 | if dep.startswith("python("): |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 221 | continue |
| 222 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 223 | # Ignore all perl(VMS::...) and perl(Mac::...) dependencies. These |
| 224 | # are typically used conditionally from the Perl code, but are |
| 225 | # generated as unconditional dependencies. |
| 226 | if dep.startswith('perl(VMS::') or dep.startswith('perl(Mac::'): |
| 227 | continue |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 228 | |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 229 | # Ignore perl dependencies on .pl files. |
| 230 | if dep.startswith('perl(') and dep.endswith('.pl)'): |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 231 | continue |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 232 | |
| 233 | # Remove perl versions and perl module versions since they typically |
| 234 | # do not make sense when used as package versions. |
| 235 | if dep.startswith('perl') and r.search(dep): |
| 236 | dep = dep.split()[0] |
| 237 | |
| 238 | # Put parentheses around any version specifications. |
| 239 | dep = r.sub(r'(\g<0>)',dep) |
| 240 | |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 241 | if file not in i: |
| 242 | i[file] = [] |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 243 | i[file].append(dep) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 244 | |
| 245 | return provides, requires |
| 246 | |
Brad Bishop | d7bf8c1 | 2018-02-25 22:55:05 -0500 | [diff] [blame] | 247 | output = subprocess.check_output(shlex.split(rpmdeps) + pkgfiles, stderr=subprocess.STDOUT).decode("utf-8") |
| 248 | provides, requires = process_deps(output, pkg, pkgdest, provides, requires) |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 249 | |
| 250 | return (pkg, provides, requires) |
| 251 | |
| 252 | |
| 253 | def read_shlib_providers(d): |
| 254 | import re |
| 255 | |
| 256 | shlib_provider = {} |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 257 | shlibs_dirs = d.getVar('SHLIBSDIRS').split() |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 258 | list_re = re.compile('^(.*)\.list$') |
| 259 | # Go from least to most specific since the last one found wins |
| 260 | for dir in reversed(shlibs_dirs): |
| 261 | bb.debug(2, "Reading shlib providers in %s" % (dir)) |
| 262 | if not os.path.exists(dir): |
| 263 | continue |
| 264 | for file in os.listdir(dir): |
| 265 | m = list_re.match(file) |
| 266 | if m: |
| 267 | dep_pkg = m.group(1) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 268 | try: |
| 269 | fd = open(os.path.join(dir, file)) |
| 270 | except IOError: |
| 271 | # During a build unrelated shlib files may be deleted, so |
| 272 | # handle files disappearing between the listdirs and open. |
| 273 | continue |
Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 274 | lines = fd.readlines() |
| 275 | fd.close() |
| 276 | for l in lines: |
| 277 | s = l.strip().split(":") |
| 278 | if s[0] not in shlib_provider: |
| 279 | shlib_provider[s[0]] = {} |
| 280 | shlib_provider[s[0]][s[1]] = (dep_pkg, s[2]) |
| 281 | return shlib_provider |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 282 | |
| 283 | |
| 284 | def npm_split_package_dirs(pkgdir): |
| 285 | """ |
| 286 | Work out the packages fetched and unpacked by BitBake's npm fetcher |
| 287 | Returns a dict of packagename -> (relpath, package.json) ordered |
| 288 | such that it is suitable for use in PACKAGES and FILES |
| 289 | """ |
| 290 | from collections import OrderedDict |
| 291 | import json |
| 292 | packages = {} |
| 293 | for root, dirs, files in os.walk(pkgdir): |
| 294 | if os.path.basename(root) == 'node_modules': |
| 295 | for dn in dirs: |
| 296 | relpth = os.path.relpath(os.path.join(root, dn), pkgdir) |
| 297 | pkgitems = ['${PN}'] |
| 298 | for pathitem in relpth.split('/'): |
| 299 | if pathitem == 'node_modules': |
| 300 | continue |
| 301 | pkgitems.append(pathitem) |
| 302 | pkgname = '-'.join(pkgitems).replace('_', '-') |
Brad Bishop | 6e60e8b | 2018-02-01 10:27:11 -0500 | [diff] [blame] | 303 | pkgname = pkgname.replace('@', '') |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 304 | pkgfile = os.path.join(root, dn, 'package.json') |
| 305 | data = None |
| 306 | if os.path.exists(pkgfile): |
| 307 | with open(pkgfile, 'r') as f: |
| 308 | data = json.loads(f.read()) |
Patrick Williams | c0f7c04 | 2017-02-23 20:41:17 -0600 | [diff] [blame] | 309 | packages[pkgname] = (relpth, data) |
Patrick Williams | d8c66bc | 2016-06-20 12:57:21 -0500 | [diff] [blame] | 310 | # We want the main package for a module sorted *after* its subpackages |
| 311 | # (so that it doesn't otherwise steal the files for the subpackage), so |
| 312 | # this is a cheap way to do that whilst still having an otherwise |
| 313 | # alphabetical sort |
| 314 | return OrderedDict((key, packages[key]) for key in sorted(packages, key=lambda pkg: pkg + '~')) |