def test_construct_bash_launcher(): linker, library_path, executable = '../lib/ld-linux.so.2', '../lib/', 'grep' script_content = construct_bash_launcher(linker=linker, library_path=library_path, executable=executable) assert script_content.startswith('#! /bin/bash\n') assert linker in script_content assert executable in script_content
def create_unpackaged_bundle(executables, rename=[], ldd='ldd'): """Creates a temporary directory containing the unpackaged contents of the bundle.""" root_directory = tempfile.mkdtemp(prefix='exodus-bundle-') try: # Make the top-level bundle directories. bin_directory = os.path.join(root_directory, 'bin') os.makedirs(bin_directory) lib_directory = os.path.join(root_directory, 'lib') os.makedirs(lib_directory) bundles_directory = os.path.join(root_directory, 'bundles') # Loop through and package each executable. assert len(executables), 'No executables were specified.' assert len(executables) >= len(rename), \ 'More renamed options were included than executables.' # Pad the rename's so that they have the same length for the `zip()` call. rename = rename + [None for i in range(len(executables) - len(rename))] for name, executable in zip(rename, map(resolve_binary, executables)): # Make the bundle subdirectories for this executable. binary_name = (name or os.path.basename(executable)).replace( os.sep, '') binary_hash = sha256_hash(executable) bundle_directory = os.path.join(bundles_directory, binary_hash) bundle_bin_directory = os.path.join(bundle_directory, 'bin') os.makedirs(bundle_bin_directory) bundle_lib_directory = os.path.join(bundle_directory, 'lib') os.makedirs(bundle_lib_directory) # Copy over the library dependencies and link them. dependencies = find_all_library_dependencies(ldd, executable) for dependency in dependencies: # Create the `lib/{hash}` library file. dependency_name = os.path.basename(dependency) dependency_hash = sha256_hash(dependency) dependency_path = os.path.join(lib_directory, dependency_hash) if not os.path.exists(dependency_path): shutil.copy(dependency, dependency_path) # Create a link to the actual library from inside the bundle lib directory. bundle_dependency_link = os.path.join(bundle_lib_directory, dependency_name) relative_dependency_path = os.path.relpath( dependency_path, bundle_lib_directory) if not os.path.exists(bundle_dependency_link): os.symlink(relative_dependency_path, bundle_dependency_link) else: link_destination = os.readlink(bundle_dependency_link) link_destination = os.path.join(bundle_lib_directory, link_destination) # This is only a problem if the duplicate libraries have different content. if os.path.normpath(link_destination) != os.path.normpath( dependency_path): raise LibraryConflictError( 'A library called "%s" was linked more than once.' % dependency_name) # Copy over the executable. bundle_executable_path = os.path.join(bundle_bin_directory, binary_name) shutil.copy(executable, bundle_executable_path) # Construct the launcher. linker_candidates = list( filter(lambda candidate: candidate.startswith('ld-'), (os.path.basename(dependency) for dependency in dependencies))) assert len(linker_candidates) > 0, 'No linker candidates found.' assert len( linker_candidates) < 2, 'Multiple linker candidates found.' [linker] = linker_candidates # Try a c launcher first and fallback. try: launcher_path = '%s-launcher' % bundle_executable_path launcher_content = construct_binary_launcher( linker=linker, binary=binary_name) with open(launcher_path, 'wb') as f: f.write(launcher_content) except CompilerNotFoundError: logger.warn(( 'Installing either the musl or diet C libraries will result in more efficient ' 'launchers (currently using bash fallbacks instead).')) launcher_path = '%s-launcher.sh' % bundle_executable_path launcher_content = construct_bash_launcher(linker=linker, binary=binary_name) with open(launcher_path, 'w') as f: f.write(launcher_content) shutil.copymode(bundle_executable_path, launcher_path) executable_link = os.path.join(bin_directory, binary_name) relative_launcher_path = os.path.relpath(launcher_path, bin_directory) os.symlink(relative_launcher_path, executable_link) return root_directory except: # noqa: E722 shutil.rmtree(root_directory) raise
def create_launcher(self, working_directory, bundle_root, linker_basename, symlink_basename, shell_launcher=False): """Creates a launcher at `source` for `destination`. Note: If an `entry_point` has been specified, it will also be created. Args: working_directory (str): The root that the `destination` will be joined with. bundle_root (str): The root that `source` will be joined with. linker_basename (str): The basename of the linker to place in the same directory. symlink_basename (str): The basename of the symlink to the actual executable. shell_launcher (bool, optional): Forces the use of shell script launcher instead of attempting to compile first using musl or diet c. Returns: str: The normalized and absolute path to the launcher. """ destination_path = os.path.join(working_directory, self.destination) source_path = os.path.join(bundle_root, self.source) # Create the symlink. source_parent = os.path.dirname(source_path) if not os.path.exists(source_parent): os.makedirs(source_parent) relative_destination_path = os.path.relpath(destination_path, source_parent) symlink_path = os.path.join(source_parent, symlink_basename) os.symlink(relative_destination_path, symlink_path) executable = os.path.join('.', symlink_basename) # Copy over the linker. linker_path = os.path.join(source_parent, linker_basename) if not os.path.exists(linker_path): shutil.copy(self.elf.linker_file.path, linker_path) else: assert filecmp.cmp(self.elf.linker_file.path, linker_path), \ 'The "%s" linker file already exists and has differing contents.' % linker_path linker = os.path.join('.', linker_basename) # Construct the library path original_file_parent = os.path.dirname(self.path) library_paths = os.environ.get('LD_LIBRARY_PATH', '').split(':') library_paths += [ '/lib64', '/usr/lib64', '/lib', '/usr/lib', '/lib32', '/usr/lib32' ] for dependency in self.elf.dependencies: library_paths.append(os.path.dirname(dependency.path)) relative_library_paths = [] for directory in library_paths: if not len(directory): continue # Get the actual absolute path for the library directory. directory = os.path.normpath(os.path.abspath(directory)) if self.chroot: directory = os.path.join(self.chroot, os.path.relpath(directory, '/')) # Convert it into a path relative to the launcher/source. relative_library_path = os.path.relpath(directory, original_file_parent) if relative_library_path not in relative_library_paths: relative_library_paths.append(relative_library_path) library_path = ':'.join(relative_library_paths) # Determine whether this is a "full" linker (*e.g.* GNU linker). with open(self.elf.linker_file.path, 'rb') as f: linker_content = f.read() full_linker = (linker_content.find(b'inhibit-rpath') > -1) # Try a c launcher first and fallback. try: if shell_launcher: raise CompilerNotFoundError() launcher_content = construct_binary_launcher( linker=linker, library_path=library_path, executable=executable, full_linker=full_linker) with open(source_path, 'wb') as f: f.write(launcher_content) except CompilerNotFoundError: if not shell_launcher: logger.warning(( 'Installing either the musl or diet C libraries will result in more efficient ' 'launchers (currently using bash fallbacks instead).')) launcher_content = construct_bash_launcher( linker=linker, library_path=library_path, executable=executable, full_linker=full_linker) with open(source_path, 'w') as f: f.write(launcher_content) shutil.copymode(self.path, source_path) return os.path.normpath(os.path.abspath(source_path))
def test_construct_bash_launcher(): linker, binary = 'ld-linux.so.2', 'grep' script_content = construct_bash_launcher(linker=linker, binary=binary) assert script_content.startswith('#! /bin/bash\n') assert linker in script_content assert binary in script_content