def expand_directory_and_symlink(indir, relfile, blacklist, follow_symlinks): """Expands a single input. It can result in multiple outputs. This function is recursive when relfile is a directory. Note: this code doesn't properly handle recursive symlink like one created with: ln -s .. foo """ if os.path.isabs(relfile): raise MappingError('Can\'t map absolute path %s' % relfile) infile = file_path.normpath(os.path.join(indir, relfile)) if not infile.startswith(indir): raise MappingError('Can\'t map file %s outside %s' % (infile, indir)) filepath = os.path.join(indir, relfile) native_filepath = file_path.get_native_path_case(filepath) if filepath != native_filepath: # Special case './'. if filepath != native_filepath + '.' + os.path.sep: # While it'd be nice to enforce path casing on Windows, it's impractical. # Also give up enforcing strict path case on OSX. Really, it's that sad. # The case where it happens is very specific and hard to reproduce: # get_native_path_case( # u'Foo.framework/Versions/A/Resources/Something.nib') will return # u'Foo.framework/Versions/A/resources/Something.nib', e.g. lowercase 'r'. # # Note that this is really something deep in OSX because running # ls Foo.framework/Versions/A # will print out 'Resources', while file_path.get_native_path_case() # returns a lower case 'r'. # # So *something* is happening under the hood resulting in the command 'ls' # and Carbon.File.FSPathMakeRef('path').FSRefMakePath() to disagree. We # have no idea why. if sys.platform not in ('darwin', 'win32'): raise MappingError( 'File path doesn\'t equal native file path\n%s != %s' % (filepath, native_filepath)) symlinks = [] if follow_symlinks: try: relfile, symlinks = expand_symlinks(indir, relfile) except OSError: # The file doesn't exist, it will throw below. pass if relfile.endswith(os.path.sep): if not os.path.isdir(infile): raise MappingError( '%s is not a directory but ends with "%s"' % (infile, os.path.sep)) # Special case './'. if relfile.startswith('.' + os.path.sep): relfile = relfile[2:] outfiles = symlinks try: for filename in fs.listdir(infile): inner_relfile = os.path.join(relfile, filename) if blacklist and blacklist(inner_relfile): continue if os.path.isdir(os.path.join(indir, inner_relfile)): inner_relfile += os.path.sep outfiles.extend( expand_directory_and_symlink(indir, inner_relfile, blacklist, follow_symlinks)) return outfiles except OSError as e: raise MappingError( 'Unable to iterate over directory %s.\n%s' % (infile, e)) else: # Always add individual files even if they were blacklisted. if os.path.isdir(infile): raise MappingError( 'Input directory %s must have a trailing slash' % infile) if not os.path.isfile(infile): raise MappingError('Input file %s doesn\'t exist' % infile) return symlinks + [relfile]
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)