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