def _add_it(s, d, actual_s, force=False, dryRun=False): if not exists(d): if dryRun: log.info("add '%s' to '%s' (dry run)", actual_s, d) else: log.info("add '%s' to '%s'", actual_s, d) sh.copy(s, d) elif not _diffPaths(s, d): log.info("skip add of '%s' to '%s': " "no changes", actual_s, d) else: if dryRun: log.info("replace '%s' to '%s' (dry run)", actual_s, d) else: log.info("replace '%s' to '%s'", actual_s, d) sh.copy(s, d)
def patch(patchesDir, sourceDir, config=None, logDir=None, dryRun=0, patchExe=None, logFilename=None): """Patch the given source tree with the given patches. "patchesDir" is a directory tree of patches to apply or a single patch file to apply. Alternatively it may be _list of_ patch directories and/or files. "sourceDir" is the base directory of the source tree to patch. "config" is a configuration object to pass to __patchinfo__.py special control functions. Typically it is a configuration module object but can be any object expected by the given patch directories. "logDir" (optional, default None) is a directory in which applied patches are logged. By default this is not created, however some projects want to create a package including all differences applied to a base source tree. If the package dir already exists it is deleted and re-created. As well, using a logDir is required to be able to unpatch(). "dryRun" (optional, default false) is a boolean indicating that everything except actually applying patches should be done. "patchExe" (optional) can be used to specify a particular patch executable to use. Otherwise one is automatically found from the environment. The given patch tree (or patch trees) is processed as described in the module docstring above. First, the set of patches that should be applied is determined. Then is it checked that all patches _can_ be applied. Then all patches are applied. "Error" is raised if all patches cannot be applied. There is no return value. An error is NOT raised if it looks like all patches have already been applied. """ log.debug( "patch(patchesDir=%r, sourceDir=%r, config=%r, logDir=%r, " "dryRun=%r)", patchesDir, sourceDir, config, logDir, dryRun) # Determine what patch actions should be carried out. # "actions" is a list of the following possible actions: # ("apply", <patches-basedir>, <patchfile-relpath>, <patch-args>) # ("preprocess & apply", <patches-basedir>, <patchfile-relpath>, <patch-args>) # ("add", <patches-basedir>, <src-relpath>, <dst-relpath>, <force>) # ("remove", <patches-basedir>, <dst-relpath>) # Some notes: # - The "add" and "remove" paths can be directories. # - When add'ing a file, if its basename is of the form # "BASE.p.EXT", then it will be preprocessed to "BASE.EXT". actions = [] for patchSpec in patchesDir: if os.path.isfile(patchSpec): actions.append(("apply", os.path.dirname(patchSpec), os.path.basename(patchSpec))) elif os.path.isdir(patchSpec): # Always skip SCC control dirs. if basename(patchSpec) in ("CVS", ".svn", ".hg"): continue os.path.walk(patchSpec, _shouldBeApplied, (patchSpec, actions, config)) else: raise Error("patches directory or file does not exist: '%s'" % patchSpec) log.debug("patch set: %s" % pprint.pformat(actions)) # Create a clean working directory. workDir = _createTempDir() if sys.platform.startswith("win"): # Windows patching leaves around temp files, so we work around this # problem by setting a different temp directory, which is later removed # at the end of this patching. oldTmpDir = os.environ.get("TMP") tmpDir = _createTempDir() os.environ["TMP"] = tmpDir log.debug("created patch working dir: '%s'" % workDir) try: # Create a patch image in the working dir (i.e. copy over patches and # files to add and preprocessed versions of those that need it.) defines = None # preprocessor defines are lazily calculated for action in actions: if action[0] in ("apply", "add"): src = os.path.join(action[1], action[2]) dst = os.path.join(workDir, action[2]) if action[0] == "apply" and os.path.isfile(dst): raise Error( "conflicting patch file '%s': you have a " "patch of the same name in more than one " "patches tree", action[2]) if os.path.isfile(src): preprocess_me, new_filename \ = _shouldPreprocess(basename(src)) if preprocess_me: if defines is None: defines = _getPreprocessorDefines(config) d = join(dirname(dst), new_filename) log.debug("preprocess '%s' to '%s'", src, d) if not exists(dirname(d)): os.makedirs(dirname(d)) preprocess.preprocess(src, d, defines=defines, substitute=1) else: log.debug("cp '%s' to '%s'", src, dst) sh.copy(src, dst) elif os.path.isdir(src): for dirpath, dirnames, filenames in os.walk(src): subpath = (dirpath == src and os.curdir or dirpath[len(src) + 1:]) for exclude_dir in (".svn", "CVS", ".hg"): if exclude_dir in dirnames: dirnames.remove(exclude_dir) for filename in filenames: s = join(dirpath, filename) preprocess_me, new_filename \ = _shouldPreprocess(filename) if preprocess_me: d = normpath(join(dst, subpath, new_filename)) if defines is None: defines = _getPreprocessorDefines(config) log.debug("preprocess '%s' to '%s'", s, d) if not exists(dirname(d)): os.makedirs(dirname(d)) preprocess.preprocess(s, d, defines=defines, substitute=1) else: d = normpath(join(dst, subpath, filename)) log.debug("cp '%s' to '%s'", s, d) sh.copy(s, d) else: raise Error("unknown file type for `%s'" % src) elif action[0] == "preprocess & apply": src = os.path.join(action[1], action[2]) dst = os.path.join(workDir, action[2]) if os.path.isfile(dst): raise Error( "conflicting patch file '%s': you have a " "patch of the same name in more than one " "patches tree", action[2]) if defines is None: defines = _getPreprocessorDefines(config) #log.debug("defines: %s", pprint.pformat(defines)) log.debug("preprocess '%s' to '%s'", src, dst) if not os.path.exists(os.path.dirname(dst)): os.makedirs(os.path.dirname(dst)) preprocess.preprocess(src, dst, defines=defines, substitute=1) elif action[0] == "remove": pass else: raise Error("unknown patch action '%s': %r" % (action[0], action)) # Ensure that each patch action can be carried out. patchExe = _getPatchExe(patchExe) for action in actions: if action[0] in ("apply", "preprocess & apply"): _assertCanApplyPatch(patchExe, os.path.join(workDir, action[2]), sourceDir, patchSrcFile=os.path.join( action[1], action[2]), patchArgs=action[3]) elif action[0] == "add": # ("add", <patches-basedir>, <src-relpath>, <dst-relpath>, <force>) # e.g. ("add", "patches", "hpux_cpp_ext\\thingy.s", "python", False) # # Ensure that we won't clobber a target file that # differs. src = os.path.join(workDir, action[2]) dst = os.path.join(sourceDir, action[3]) if os.path.isdir(src): for dirpath, dirnames, filenames in os.walk(src): subpath = (dirpath == src and os.curdir or dirpath[len(src) + 1:]) for filename in filenames: s = join(dirpath, filename) d = normpath(join(dst, subpath, filename)) # 'actual_s' might actually have a '.p' in there. actual_s = join(action[1], action[2], subpath, filename) if not action[4]: _assertCanAddFile(s, d, actual_s) else: if not action[4]: _assertCanAddFile(src, dst, os.path.join(action[1], action[2])) elif action[0] == "remove": pass else: raise Error("unknown patch action '%s': %r" % (action[0], action)) if logDir: # Log actions. if logFilename is None: logFilename = "__patchlog__.py" patchLogFile = os.path.join(workDir, logFilename) patchLog = open(patchLogFile, "w") try: patchLog.write("""\ # Patch log (%s) # # WARNING: This file is automatically generated by patchtree.py. Any # Changes you make will be lost. sourceDir = %r actions = %s """ % (time.asctime(), sourceDir, pprint.pformat(actions))) finally: patchLog.close() # Write files scheduled for removal to the attic for possible # retrieval during unpatch(). atticDir = os.path.join(workDir, "__attic__") oldAtticDir = os.path.join(logDir, "__attic__") for action in actions: if action[0] == "remove": # ("remove", <patches-basedir>, <dst-relpath>) origLoc = os.path.join(sourceDir, action[2]) oldAtticLoc = os.path.join(oldAtticDir, action[2]) atticLoc = os.path.join(atticDir, action[2]) possibleLocs = [origLoc, oldAtticLoc] for location in possibleLocs: if os.path.exists(location): log.debug("copy '%s' to attic", origLoc) sh.copy(location, atticLoc) # A little helper for doing the 'add' action with appropriate # logging. def _add_it(s, d, actual_s, force=False, dryRun=False): if not exists(d): if dryRun: log.info("add '%s' to '%s' (dry run)", actual_s, d) else: log.info("add '%s' to '%s'", actual_s, d) sh.copy(s, d) elif not _diffPaths(s, d): log.info("skip add of '%s' to '%s': " "no changes", actual_s, d) else: if dryRun: log.info("replace '%s' to '%s' (dry run)", actual_s, d) else: log.info("replace '%s' to '%s'", actual_s, d) sh.copy(s, d) # Carry out each patch action. for action in actions: if action[0] in ("apply", "preprocess & apply"): _applyPatch(patchExe, workDir, action[2], sourceDir, dryRun=dryRun, patchArgs=action[3]) elif action[0] == "add": # ("add", <patches-basedir>, <src-relpath>, <dst-relpath>, <force>) # e.g. ("add", "patches", "hpux_cpp_ext\\thingy.s", "python", False) # #XXX Could improve logging here to only log one message # in certain circumstances: no changes, all files # were added. src = os.path.join(workDir, action[2]) dst = os.path.join(sourceDir, action[3]) if isdir(src): for dirpath, dirnames, filenames in os.walk(src): subpath = (dirpath == src and os.curdir or dirpath[len(src) + 1:]) for filename in filenames: s = join(dirpath, filename) d = normpath(join(dst, subpath, filename)) # 'actual_s' might actually have a '.p' in there. actual_s = join(action[1], action[2], subpath, filename) _add_it(s, d, actual_s, force=action[4], dryRun=dryRun) else: d = isfile(dst) and dst or join(dst, basename(src)) actual_s = join(action[1], action[2]) _add_it(src, d, actual_s, force=action[4], dryRun=dryRun) elif action[0] == "remove": # ("remove", <patches-basedir>, <dst-relpath>) dst = os.path.join(sourceDir, action[2]) if not os.path.exists(dst): log.info("skip removal of '%s': already removed", action[2]) elif dryRun: log.info("remove '%s' (dry run)", action[2]) else: log.info("remove '%s'", action[2]) sh.rm(dst) else: raise Error("unknown patch action '%s': %r" % (action[0], action)) # If a log dir was specified then copy working dir there. if logDir: if dryRun: log.info("creating patch log in '%s' (dry run)", logDir) else: if os.path.exists(logDir): sh.rm(logDir) log.info("creating patch log in '%s'", logDir) sh.copy(workDir, logDir) finally: log.debug("removing temporary working dir '%s'", workDir) try: sh.rm(workDir) except EnvironmentError, ex: log.warn("could not remove temp working dir '%s': %s", workDir, ex) if sys.platform.startswith("win"): if oldTmpDir is not None: os.environ["TMP"] = oldTmpDir try: sh.rm(tmpDir) except EnvironmentError, ex: log.warn("could not remove temp patch dir '%s': %s", tmpDir, ex)
if os.path.exists(sourceFile): fin = open(sourceFile, 'rb') try: sourcemd5 = md5(fin.read()).hexdigest() finally: fin.close() if atticmd5 == sourcemd5: log.info( "skip restoration of '%s' from attic: " "already restored", action[2]) continue if dryRun: log.info("restore '%s' from attic (dry run)", action[2]) else: log.info("restore '%s' from attic", action[2]) sh.copy(atticLoc, sourceLoc) else: raise Error("unknown action, '%s', in patch log: %s" % (action[0], action)) def patch(patchesDir, sourceDir, config=None, logDir=None, dryRun=0, patchExe=None, logFilename=None): """Patch the given source tree with the given patches. "patchesDir" is a directory tree of patches to apply or a single
def patch(patchesDir, sourceDir, config=None, logDir=None, dryRun=0, patchExe=None, logFilename=None): """Patch the given source tree with the given patches. "patchesDir" is a directory tree of patches to apply or a single patch file to apply. Alternatively it may be _list of_ patch directories and/or files. "sourceDir" is the base directory of the source tree to patch. "config" is a configuration object to pass to __patchinfo__.py special control functions. Typically it is a configuration module object but can be any object expected by the given patch directories. "logDir" (optional, default None) is a directory in which applied patches are logged. By default this is not created, however some projects want to create a package including all differences applied to a base source tree. If the package dir already exists it is deleted and re-created. As well, using a logDir is required to be able to unpatch(). "dryRun" (optional, default false) is a boolean indicating that everything except actually applying patches should be done. "patchExe" (optional) can be used to specify a particular patch executable to use. Otherwise one is automatically found from the environment. The given patch tree (or patch trees) is processed as described in the module docstring above. First, the set of patches that should be applied is determined. Then is it checked that all patches _can_ be applied. Then all patches are applied. "Error" is raised if all patches cannot be applied. There is no return value. An error is NOT raised if it looks like all patches have already been applied. """ log.debug("patch(patchesDir=%r, sourceDir=%r, config=%r, logDir=%r, " "dryRun=%r)", patchesDir, sourceDir, config, logDir, dryRun) # Determine what patch actions should be carried out. # "actions" is a list of the following possible actions: # ("apply", <patches-basedir>, <patchfile-relpath>, <patch-args>) # ("preprocess & apply", <patches-basedir>, <patchfile-relpath>, <patch-args>) # ("add", <patches-basedir>, <src-relpath>, <dst-relpath>, <force>) # ("remove", <patches-basedir>, <dst-relpath>) # Some notes: # - The "add" and "remove" paths can be directories. # - When add'ing a file, if its basename is of the form # "BASE.p.EXT", then it will be preprocessed to "BASE.EXT". actions = [] for patchSpec in patchesDir: if os.path.isfile(patchSpec): actions.append( ("apply", os.path.dirname(patchSpec), os.path.basename(patchSpec)) ) elif os.path.isdir(patchSpec): # Always skip SCC control dirs. if basename(patchSpec) in ("CVS", ".svn", ".hg"): continue os.path.walk(patchSpec, _shouldBeApplied, (patchSpec, actions, config)) else: raise Error("patches directory or file does not exist: '%s'" % patchSpec) log.debug("patch set: %s" % pprint.pformat(actions)) # Create a clean working directory. workDir = _createTempDir() if sys.platform.startswith("win"): # Windows patching leaves around temp files, so we work around this # problem by setting a different temp directory, which is later removed # at the end of this patching. oldTmpDir = os.environ.get("TMP") tmpDir = _createTempDir() os.environ["TMP"] = tmpDir log.debug("created patch working dir: '%s'" % workDir) try: # Create a patch image in the working dir (i.e. copy over patches and # files to add and preprocessed versions of those that need it.) defines = None # preprocessor defines are lazily calculated for action in actions: if action[0] in ("apply", "add"): src = os.path.join(action[1], action[2]) dst = os.path.join(workDir, action[2]) if action[0] == "apply" and os.path.isfile(dst): raise Error("conflicting patch file '%s': you have a " "patch of the same name in more than one " "patches tree", action[2]) if os.path.isfile(src): preprocess_me, new_filename \ = _shouldPreprocess(basename(src)) if preprocess_me: if defines is None: defines = _getPreprocessorDefines(config) d = join(dirname(dst), new_filename) log.debug("preprocess '%s' to '%s'", src, d) if not exists(dirname(d)): os.makedirs(dirname(d)) preprocess.preprocess(src, d, defines=defines, substitute=1) else: log.debug("cp '%s' to '%s'", src, dst) sh.copy(src, dst) elif os.path.isdir(src): for dirpath, dirnames, filenames in os.walk(src): subpath = (dirpath == src and os.curdir or dirpath[len(src)+1:]) for exclude_dir in (".svn", "CVS", ".hg"): if exclude_dir in dirnames: dirnames.remove(exclude_dir) for filename in filenames: s = join(dirpath, filename) preprocess_me, new_filename \ = _shouldPreprocess(filename) if preprocess_me: d = normpath(join(dst, subpath, new_filename)) if defines is None: defines = _getPreprocessorDefines(config) log.debug("preprocess '%s' to '%s'", s, d) if not exists(dirname(d)): os.makedirs(dirname(d)) preprocess.preprocess(s, d, defines=defines, substitute=1) else: d = normpath(join(dst, subpath, filename)) log.debug("cp '%s' to '%s'", s, d) sh.copy(s, d) else: raise Error("unknown file type for `%s'" % src) elif action[0] == "preprocess & apply": src = os.path.join(action[1], action[2]) dst = os.path.join(workDir, action[2]) if os.path.isfile(dst): raise Error("conflicting patch file '%s': you have a " "patch of the same name in more than one " "patches tree", action[2]) if defines is None: defines = _getPreprocessorDefines(config) #log.debug("defines: %s", pprint.pformat(defines)) log.debug("preprocess '%s' to '%s'", src, dst) if not os.path.exists(os.path.dirname(dst)): os.makedirs(os.path.dirname(dst)) preprocess.preprocess(src, dst, defines=defines, substitute=1) elif action[0] == "remove": pass else: raise Error("unknown patch action '%s': %r" % (action[0], action)) # Ensure that each patch action can be carried out. patchExe = _getPatchExe(patchExe) for action in actions: if action[0] in ("apply", "preprocess & apply"): _assertCanApplyPatch(patchExe, os.path.join(workDir, action[2]), sourceDir, patchSrcFile=os.path.join(action[1], action[2]), patchArgs=action[3]) elif action[0] == "add": # ("add", <patches-basedir>, <src-relpath>, <dst-relpath>, <force>) # e.g. ("add", "patches", "hpux_cpp_ext\\thingy.s", "python", False) # # Ensure that we won't clobber a target file that # differs. src = os.path.join(workDir, action[2]) dst = os.path.join(sourceDir, action[3]) if os.path.isdir(src): for dirpath, dirnames, filenames in os.walk(src): subpath = (dirpath == src and os.curdir or dirpath[len(src)+1:]) for filename in filenames: s = join(dirpath, filename) d = normpath(join(dst, subpath, filename)) # 'actual_s' might actually have a '.p' in there. actual_s = join(action[1], action[2], subpath, filename) if not action[4]: _assertCanAddFile(s, d, actual_s) else: if not action[4]: _assertCanAddFile(src, dst, os.path.join(action[1], action[2])) elif action[0] == "remove": pass else: raise Error("unknown patch action '%s': %r" % (action[0], action)) if logDir: # Log actions. if logFilename is None: logFilename = "__patchlog__.py" patchLogFile = os.path.join(workDir, logFilename) patchLog = open(patchLogFile, "w") try: patchLog.write("""\ # Patch log (%s) # # WARNING: This file is automatically generated by patchtree.py. Any # Changes you make will be lost. sourceDir = %r actions = %s """ % (time.asctime(), sourceDir, pprint.pformat(actions))) finally: patchLog.close() # Write files scheduled for removal to the attic for possible # retrieval during unpatch(). atticDir = os.path.join(workDir, "__attic__") oldAtticDir = os.path.join(logDir, "__attic__") for action in actions: if action[0] == "remove": # ("remove", <patches-basedir>, <dst-relpath>) origLoc = os.path.join(sourceDir, action[2]) oldAtticLoc = os.path.join(oldAtticDir, action[2]) atticLoc = os.path.join(atticDir, action[2]) possibleLocs = [origLoc, oldAtticLoc] for location in possibleLocs: if os.path.exists(location): log.debug("copy '%s' to attic", origLoc) sh.copy(location, atticLoc) # A little helper for doing the 'add' action with appropriate # logging. def _add_it(s, d, actual_s, force=False, dryRun=False): if not exists(d): if dryRun: log.info("add '%s' to '%s' (dry run)", actual_s, d) else: log.info("add '%s' to '%s'", actual_s, d) sh.copy(s, d) elif not _diffPaths(s, d): log.info("skip add of '%s' to '%s': " "no changes", actual_s, d) else: if dryRun: log.info("replace '%s' to '%s' (dry run)", actual_s, d) else: log.info("replace '%s' to '%s'", actual_s, d) sh.copy(s, d) # Carry out each patch action. for action in actions: if action[0] in ("apply", "preprocess & apply"): _applyPatch(patchExe, workDir, action[2], sourceDir, dryRun=dryRun, patchArgs=action[3]) elif action[0] == "add": # ("add", <patches-basedir>, <src-relpath>, <dst-relpath>, <force>) # e.g. ("add", "patches", "hpux_cpp_ext\\thingy.s", "python", False) # #XXX Could improve logging here to only log one message # in certain circumstances: no changes, all files # were added. src = os.path.join(workDir, action[2]) dst = os.path.join(sourceDir, action[3]) if isdir(src): for dirpath, dirnames, filenames in os.walk(src): subpath = (dirpath == src and os.curdir or dirpath[len(src)+1:]) for filename in filenames: s = join(dirpath, filename) d = normpath(join(dst, subpath, filename)) # 'actual_s' might actually have a '.p' in there. actual_s = join(action[1], action[2], subpath, filename) _add_it(s, d, actual_s, force=action[4], dryRun=dryRun) else: d = isfile(dst) and dst or join(dst, basename(src)) actual_s = join(action[1], action[2]) _add_it(src, d, actual_s, force=action[4], dryRun=dryRun) elif action[0] == "remove": # ("remove", <patches-basedir>, <dst-relpath>) dst = os.path.join(sourceDir, action[2]) if not os.path.exists(dst): log.info("skip removal of '%s': already removed", action[2]) elif dryRun: log.info("remove '%s' (dry run)", action[2]) else: log.info("remove '%s'", action[2]) sh.rm(dst) else: raise Error("unknown patch action '%s': %r" % (action[0], action)) # If a log dir was specified then copy working dir there. if logDir: if dryRun: log.info("creating patch log in '%s' (dry run)", logDir) else: if os.path.exists(logDir): sh.rm(logDir) log.info("creating patch log in '%s'", logDir) sh.copy(workDir, logDir) finally: log.debug("removing temporary working dir '%s'", workDir) try: sh.rm(workDir) except EnvironmentError, ex: log.warn("could not remove temp working dir '%s': %s", workDir, ex) if sys.platform.startswith("win"): if oldTmpDir is not None: os.environ["TMP"] = oldTmpDir try: sh.rm(tmpDir) except EnvironmentError, ex: log.warn("could not remove temp patch dir '%s': %s", tmpDir, ex)
sourceFile = os.path.join(dst, os.path.basename(sourceLoc)) if os.path.exists(sourceFile): fin = open(sourceFile, 'rb') try: sourcemd5 = md5(fin.read()).hexdigest() finally: fin.close() if atticmd5 == sourcemd5: log.info("skip restoration of '%s' from attic: " "already restored", action[2]) continue if dryRun: log.info("restore '%s' from attic (dry run)", action[2]) else: log.info("restore '%s' from attic", action[2]) sh.copy(atticLoc, sourceLoc) else: raise Error("unknown action, '%s', in patch log: %s" % (action[0], action)) def patch(patchesDir, sourceDir, config=None, logDir=None, dryRun=0, patchExe=None, logFilename=None): """Patch the given source tree with the given patches. "patchesDir" is a directory tree of patches to apply or a single patch file to apply. Alternatively it may be _list of_ patch directories and/or files. "sourceDir" is the base directory of the source tree to patch. "config" is a configuration object to pass to __patchinfo__.py special control functions. Typically it is a configuration