def expand_symlinks(indir, relfile): """Follows symlinks in |relfile|, but treating symlinks that point outside the build tree as if they were ordinary directories/files. Returns the final symlink-free target and a list of paths to symlinks encountered in the process. The rule about symlinks outside the build tree is for the benefit of the Chromium OS ebuild, which symlinks the output directory to an unrelated path in the chroot. Fails when a directory loop is detected, although in theory we could support that case. """ is_directory = relfile.endswith(os.path.sep) done = indir todo = relfile.strip(os.path.sep) symlinks = [] while todo: pre_symlink, symlink, post_symlink = file_path.split_at_symlink(done, todo) if not symlink: todo = file_path.fix_native_path_case(done, todo) done = os.path.join(done, todo) break symlink_path = os.path.join(done, pre_symlink, symlink) post_symlink = post_symlink.lstrip(os.path.sep) # readlink doesn't exist on Windows. # pylint: disable=E1101 target = os.path.normpath(os.path.join(done, pre_symlink)) symlink_target = os.readlink(symlink_path) if os.path.isabs(symlink_target): # Absolute path are considered a normal directories. The use case is # generally someone who puts the output directory on a separate drive. target = symlink_target else: # The symlink itself could be using the wrong path case. target = file_path.fix_native_path_case(target, symlink_target) if not os.path.exists(target): raise MappingError( 'Symlink target doesn\'t exist: %s -> %s' % (symlink_path, target)) target = file_path.get_native_path_case(target) if not file_path.path_starts_with(indir, target): done = symlink_path todo = post_symlink continue if file_path.path_starts_with(target, symlink_path): raise MappingError( 'Can\'t map recursive symlink reference %s -> %s' % (symlink_path, target)) logging.info('Found symlink: %s -> %s', symlink_path, target) symlinks.append(os.path.relpath(symlink_path, indir)) # Treat the common prefix of the old and new paths as done, and start # scanning again. target = target.split(os.path.sep) symlink_path = symlink_path.split(os.path.sep) prefix_length = 0 for target_piece, symlink_path_piece in zip(target, symlink_path): if target_piece == symlink_path_piece: prefix_length += 1 else: break done = os.path.sep.join(target[:prefix_length]) todo = os.path.join( os.path.sep.join(target[prefix_length:]), post_symlink) relfile = os.path.relpath(done, indir) relfile = relfile.rstrip(os.path.sep) + is_directory * os.path.sep return relfile, symlinks
def load_isolate( self, cwd, isolate_file, path_variables, config_variables, extra_variables, blacklist, ignore_broken_items ): """Updates self.isolated and self.saved_state with information loaded from a .isolate file. Processes the loaded data, deduce root_dir, relative_cwd. """ # Make sure to not depend on os.getcwd(). assert os.path.isabs(isolate_file), isolate_file isolate_file = file_path.get_native_path_case(isolate_file) logging.info( "CompleteState.load_isolate(%s, %s, %s, %s, %s, %s)", cwd, isolate_file, path_variables, config_variables, extra_variables, ignore_broken_items, ) # Config variables are not affected by the paths and must be used to # retrieve the paths, so update them first. self.saved_state.update_config(config_variables) with open(isolate_file, "r") as f: # At that point, variables are not replaced yet in command and infiles. # infiles may contain directory entries and is in posix style. command, infiles, read_only, isolate_cmd_dir = isolate_format.load_isolate_for_config( os.path.dirname(isolate_file), f.read(), self.saved_state.config_variables ) # Processes the variables with the new found relative root. Note that 'cwd' # is used when path variables are used. path_variables = normalize_path_variables(cwd, path_variables, isolate_cmd_dir) # Update the rest of the saved state. self.saved_state.update(isolate_file, path_variables, extra_variables) total_variables = self.saved_state.path_variables.copy() total_variables.update(self.saved_state.config_variables) total_variables.update(self.saved_state.extra_variables) command = [isolate_format.eval_variables(i, total_variables) for i in command] total_variables = self.saved_state.path_variables.copy() total_variables.update(self.saved_state.extra_variables) infiles = [isolate_format.eval_variables(f, total_variables) for f in infiles] # root_dir is automatically determined by the deepest root accessed with the # form '../../foo/bar'. Note that path variables must be taken in account # too, add them as if they were input files. self.saved_state.root_dir = isolate_format.determine_root_dir( isolate_cmd_dir, infiles + self.saved_state.path_variables.values() ) # The relative directory is automatically determined by the relative path # between root_dir and the directory containing the .isolate file, # isolate_base_dir. relative_cwd = os.path.relpath(isolate_cmd_dir, self.saved_state.root_dir) # Now that we know where the root is, check that the path_variables point # inside it. for k, v in self.saved_state.path_variables.iteritems(): dest = os.path.join(isolate_cmd_dir, relative_cwd, v) if not file_path.path_starts_with(self.saved_state.root_dir, dest): raise isolated_format.MappingError( "Path variable %s=%r points outside the inferred root directory " "%s; %s" % (k, v, self.saved_state.root_dir, dest) ) # Normalize the files based to self.saved_state.root_dir. It is important to # keep the trailing os.path.sep at that step. infiles = [ file_path.relpath(file_path.normpath(os.path.join(isolate_cmd_dir, f)), self.saved_state.root_dir) for f in infiles ] follow_symlinks = sys.platform != "win32" # Expand the directories by listing each file inside. Up to now, trailing # os.path.sep must be kept. infiles = isolated_format.expand_directories_and_symlinks( self.saved_state.root_dir, infiles, tools.gen_blacklist(blacklist), follow_symlinks, ignore_broken_items ) # Finally, update the new data to be able to generate the foo.isolated file, # the file that is used by run_isolated.py. self.saved_state.update_isolated(command, infiles, read_only, relative_cwd) logging.debug(self)
def load_isolate(self, cwd, isolate_file, path_variables, config_variables, extra_variables, blacklist, ignore_broken_items, collapse_symlinks): """Updates self.isolated and self.saved_state with information loaded from a .isolate file. Processes the loaded data, deduce root_dir, relative_cwd. """ # Make sure to not depend on os.getcwd(). assert os.path.isabs(isolate_file), isolate_file isolate_file = file_path.get_native_path_case(isolate_file) logging.info('CompleteState.load_isolate(%s, %s, %s, %s, %s, %s, %s)', cwd, isolate_file, path_variables, config_variables, extra_variables, ignore_broken_items, collapse_symlinks) # Config variables are not affected by the paths and must be used to # retrieve the paths, so update them first. self.saved_state.update_config(config_variables) with fs.open(isolate_file, 'r') as f: # At that point, variables are not replaced yet in command and infiles. # infiles may contain directory entries and is in posix style. command, infiles, read_only, isolate_cmd_dir = ( isolate_format.load_isolate_for_config( os.path.dirname(isolate_file), f.read(), self.saved_state.config_variables)) # Processes the variables with the new found relative root. Note that 'cwd' # is used when path variables are used. path_variables = normalize_path_variables(cwd, path_variables, isolate_cmd_dir) # Update the rest of the saved state. self.saved_state.update(isolate_file, path_variables, extra_variables) total_variables = self.saved_state.path_variables.copy() total_variables.update(self.saved_state.config_variables) total_variables.update(self.saved_state.extra_variables) command = [ isolate_format.eval_variables(i, total_variables) for i in command ] total_variables = self.saved_state.path_variables.copy() total_variables.update(self.saved_state.extra_variables) infiles = [ isolate_format.eval_variables(f, total_variables) for f in infiles ] # root_dir is automatically determined by the deepest root accessed with the # form '../../foo/bar'. Note that path variables must be taken in account # too, add them as if they were input files. self.saved_state.root_dir = isolate_format.determine_root_dir( isolate_cmd_dir, infiles + self.saved_state.path_variables.values()) # The relative directory is automatically determined by the relative path # between root_dir and the directory containing the .isolate file, # isolate_base_dir. relative_cwd = os.path.relpath(isolate_cmd_dir, self.saved_state.root_dir) # Now that we know where the root is, check that the path_variables point # inside it. for k, v in self.saved_state.path_variables.iteritems(): dest = os.path.join(isolate_cmd_dir, relative_cwd, v) if not file_path.path_starts_with(self.saved_state.root_dir, dest): raise isolated_format.MappingError( 'Path variable %s=%r points outside the inferred root directory ' '%s; %s' % (k, v, self.saved_state.root_dir, dest)) # Normalize the files based to self.saved_state.root_dir. It is important to # keep the trailing os.path.sep at that step. infiles = [ file_path.relpath( file_path.normpath(os.path.join(isolate_cmd_dir, f)), self.saved_state.root_dir) for f in infiles ] follow_symlinks = False if not collapse_symlinks: follow_symlinks = sys.platform != 'win32' # Expand the directories by listing each file inside. Up to now, trailing # os.path.sep must be kept. infiles = isolated_format.expand_directories_and_symlinks( self.saved_state.root_dir, infiles, tools.gen_blacklist(blacklist), follow_symlinks, ignore_broken_items) # Finally, update the new data to be able to generate the foo.isolated file, # the file that is used by run_isolated.py. self.saved_state.update_isolated(command, infiles, read_only, relative_cwd) logging.debug(self)