class NativeLibDownloader(object): """Download native libs on device. 1. Collect info of all native libs in the native_lib_dir on host. 2. Check the available native libs in /data/local/tmp/native_libs on device. 3. Sync native libs on device. """ def __init__(self, ndk_path, device_arch, adb): self.adb = adb self.readelf = ReadElf(ndk_path) self.device_arch = device_arch self.need_archs = self._get_need_archs() self.host_build_id_map = {} # Map from build_id to HostElfEntry. self.device_build_id_map = {} # Map from build_id to relative_path on device. self.name_count_map = {} # Used to give a unique name for each library. self.dir_on_device = NATIVE_LIBS_DIR_ON_DEVICE self.build_id_list_file = 'build_id_list' def _get_need_archs(self): """Return the archs of binaries needed on device.""" if self.device_arch == 'arm64': return ['arm', 'arm64'] if self.device_arch == 'arm': return ['arm'] if self.device_arch == 'x86_64': return ['x86', 'x86_64'] if self.device_arch == 'x86': return ['x86'] return [] def collect_native_libs_on_host(self, native_lib_dir): self.host_build_id_map.clear() for root, _, files in os.walk(native_lib_dir): for name in files: if not name.endswith('.so'): continue self.add_native_lib_on_host(os.path.join(root, name), name) def add_native_lib_on_host(self, path, name): build_id = self.readelf.get_build_id(path) if not build_id: return arch = self.readelf.get_arch(path) if arch not in self.need_archs: return sections = self.readelf.get_sections(path) score = 0 if '.debug_info' in sections: score = 3 elif '.gnu_debugdata' in sections: score = 2 elif '.symtab' in sections: score = 1 entry = self.host_build_id_map.get(build_id) if entry: if entry.score < score: entry.path = path entry.score = score else: repeat_count = self.name_count_map.get(name, 0) self.name_count_map[name] = repeat_count + 1 unique_name = name if repeat_count == 0 else name + '_' + str(repeat_count) self.host_build_id_map[build_id] = HostElfEntry(path, unique_name, score) def collect_native_libs_on_device(self): self.device_build_id_map.clear() self.adb.check_run(['shell', 'mkdir', '-p', self.dir_on_device]) if os.path.exists(self.build_id_list_file): os.remove(self.build_id_list_file) self.adb.run(['pull', self.dir_on_device + self.build_id_list_file]) if os.path.exists(self.build_id_list_file): with open(self.build_id_list_file, 'rb') as fh: for line in fh.readlines(): line = bytes_to_str(line).strip() items = line.split('=') if len(items) == 2: self.device_build_id_map[items[0]] = items[1] remove(self.build_id_list_file) def sync_natives_libs_on_device(self): # Push missing native libs on device. for build_id in self.host_build_id_map: if build_id not in self.device_build_id_map: entry = self.host_build_id_map[build_id] self.adb.check_run(['push', entry.path, self.dir_on_device + entry.name]) # Remove native libs not exist on host. for build_id in self.device_build_id_map: if build_id not in self.host_build_id_map: name = self.device_build_id_map[build_id] self.adb.run(['shell', 'rm', self.dir_on_device + name]) # Push new build_id_list on device. with open(self.build_id_list_file, 'wb') as fh: for build_id in self.host_build_id_map: s = str_to_bytes('%s=%s\n' % (build_id, self.host_build_id_map[build_id].name)) fh.write(s) self.adb.check_run(['push', self.build_id_list_file, self.dir_on_device + self.build_id_list_file]) os.remove(self.build_id_list_file)
class NativeLibDownloader(object): """Download native libs on device. 1. Collect info of all native libs in the native_lib_dir on host. 2. Check the available native libs in /data/local/tmp/native_libs on device. 3. Sync native libs on device. """ def __init__(self, ndk_path, device_arch, adb): self.adb = adb self.readelf = ReadElf(ndk_path) self.device_arch = device_arch self.need_archs = self._get_need_archs() self.host_build_id_map = {} # Map from build_id to HostElfEntry. self.device_build_id_map = { } # Map from build_id to relative_path on device. # Map from filename to HostElfEntry for elf files without build id. self.no_build_id_file_map = {} self.name_count_map = { } # Used to give a unique name for each library. self.dir_on_device = NATIVE_LIBS_DIR_ON_DEVICE self.build_id_list_file = 'build_id_list' def _get_need_archs(self): """Return the archs of binaries needed on device.""" if self.device_arch == 'arm64': return ['arm', 'arm64'] if self.device_arch == 'arm': return ['arm'] if self.device_arch == 'x86_64': return ['x86', 'x86_64'] if self.device_arch == 'x86': return ['x86'] return [] def collect_native_libs_on_host(self, native_lib_dir): self.host_build_id_map.clear() for root, _, files in os.walk(native_lib_dir): for name in files: if not name.endswith('.so'): continue self.add_native_lib_on_host(os.path.join(root, name), name) def add_native_lib_on_host(self, path, name): arch = self.readelf.get_arch(path) if arch not in self.need_archs: return sections = self.readelf.get_sections(path) score = 0 if '.debug_info' in sections: score = 3 elif '.gnu_debugdata' in sections: score = 2 elif '.symtab' in sections: score = 1 build_id = self.readelf.get_build_id(path) if build_id: entry = self.host_build_id_map.get(build_id) if entry: if entry.score < score: entry.path = path entry.score = score else: repeat_count = self.name_count_map.get(name, 0) self.name_count_map[name] = repeat_count + 1 unique_name = name if repeat_count == 0 else name + '_' + str( repeat_count) self.host_build_id_map[build_id] = HostElfEntry( path, unique_name, score) else: entry = self.no_build_id_file_map.get(name) if entry: if entry.score < score: entry.path = path entry.score = score else: self.no_build_id_file_map[name] = HostElfEntry( path, name, score) def collect_native_libs_on_device(self): self.device_build_id_map.clear() self.adb.check_run(['shell', 'mkdir', '-p', self.dir_on_device]) if os.path.exists(self.build_id_list_file): os.remove(self.build_id_list_file) result, output = self.adb.run_and_return_output( ['shell', 'ls', self.dir_on_device]) if not result: return file_set = set(output.strip().split()) if self.build_id_list_file not in file_set: return self.adb.run(['pull', self.dir_on_device + self.build_id_list_file]) if os.path.exists(self.build_id_list_file): with open(self.build_id_list_file, 'rb') as fh: for line in fh.readlines(): line = bytes_to_str(line).strip() items = line.split('=') if len(items) == 2: build_id, filename = items if filename in file_set: self.device_build_id_map[build_id] = filename remove(self.build_id_list_file) def sync_native_libs_on_device(self): # Push missing native libs on device. for build_id in self.host_build_id_map: if build_id not in self.device_build_id_map: entry = self.host_build_id_map[build_id] self.adb.check_run( ['push', entry.path, self.dir_on_device + entry.name]) # Remove native libs not exist on host. for build_id in self.device_build_id_map: if build_id not in self.host_build_id_map: name = self.device_build_id_map[build_id] self.adb.run(['shell', 'rm', self.dir_on_device + name]) # Push new build_id_list on device. with open(self.build_id_list_file, 'wb') as fh: for build_id in self.host_build_id_map: s = str_to_bytes( '%s=%s\n' % (build_id, self.host_build_id_map[build_id].name)) fh.write(s) self.adb.check_run([ 'push', self.build_id_list_file, self.dir_on_device + self.build_id_list_file ]) os.remove(self.build_id_list_file) # Push elf files without build id on device. for entry in self.no_build_id_file_map.values(): target = self.dir_on_device + entry.name # Skip download if we have a file with the same name and size on device. result, output = self.adb.run_and_return_output( ['shell', 'ls', '-l', target], log_output=False, log_stderr=False) if result: items = output.split() if len(items) > 5: try: file_size = int(items[4]) except ValueError: file_size = 0 if file_size == os.path.getsize(entry.path): continue self.adb.check_run(['push', entry.path, target])
class NativeLibDownloader(object): """Download native libs on device. 1. Collect info of all native libs in the native_lib_dir on host. 2. Check the available native libs in /data/local/tmp/native_libs on device. 3. Sync native libs on device. """ def __init__(self, ndk_path, device_arch, adb): self.adb = adb self.readelf = ReadElf(ndk_path) self.device_arch = device_arch self.need_archs = self._get_need_archs() self.host_build_id_map = {} # Map from build_id to HostElfEntry. self.device_build_id_map = {} # Map from build_id to relative_path on device. self.name_count_map = {} # Used to give a unique name for each library. self.dir_on_device = NATIVE_LIBS_DIR_ON_DEVICE self.build_id_list_file = 'build_id_list' def _get_need_archs(self): """Return the archs of binaries needed on device.""" if self.device_arch == 'arm64': return ['arm', 'arm64'] if self.device_arch == 'arm': return ['arm'] if self.device_arch == 'x86_64': return ['x86', 'x86_64'] if self.device_arch == 'x86': return ['x86'] return [] def collect_native_libs_on_host(self, native_lib_dir): self.host_build_id_map.clear() for root, _, files in os.walk(native_lib_dir): for name in files: if not name.endswith('.so'): continue self.add_native_lib_on_host(os.path.join(root, name), name) def add_native_lib_on_host(self, path, name): build_id = self.readelf.get_build_id(path) if not build_id: return arch = self.readelf.get_arch(path) if arch not in self.need_archs: return sections = self.readelf.get_sections(path) score = 0 if '.debug_info' in sections: score = 3 elif '.gnu_debugdata' in sections: score = 2 elif '.symtab' in sections: score = 1 entry = self.host_build_id_map.get(build_id) if entry: if entry.score < score: entry.path = path entry.score = score else: repeat_count = self.name_count_map.get(name, 0) self.name_count_map[name] = repeat_count + 1 unique_name = name if repeat_count == 0 else name + '_' + str(repeat_count) self.host_build_id_map[build_id] = HostElfEntry(path, unique_name, score) def collect_native_libs_on_device(self): self.device_build_id_map.clear() self.adb.check_run(['shell', 'mkdir', '-p', self.dir_on_device]) if os.path.exists(self.build_id_list_file): os.remove(self.build_id_list_file) self.adb.run(['pull', self.dir_on_device + self.build_id_list_file]) if os.path.exists(self.build_id_list_file): with open(self.build_id_list_file, 'rb') as fh: for line in fh.readlines(): line = line.strip() items = line.split('=') if len(items) == 2: self.device_build_id_map[items[0]] = items[1] remove(self.build_id_list_file) def sync_natives_libs_on_device(self): # Push missing native libs on device. for build_id in self.host_build_id_map: if build_id not in self.device_build_id_map: entry = self.host_build_id_map[build_id] self.adb.check_run(['push', entry.path, self.dir_on_device + entry.name]) # Remove native libs not exist on host. for build_id in self.device_build_id_map: if build_id not in self.host_build_id_map: name = self.device_build_id_map[build_id] self.adb.run(['shell', 'rm', self.dir_on_device + name]) # Push new build_id_list on device. with open(self.build_id_list_file, 'wb') as fh: for build_id in self.host_build_id_map: fh.write('%s=%s\n' % (build_id, self.host_build_id_map[build_id].name)) self.adb.check_run(['push', self.build_id_list_file, self.dir_on_device + self.build_id_list_file]) os.remove(self.build_id_list_file)