Use git apply for applying patch files (issue #1825)

This commit is contained in:
Marshall Greenblatt
2017-04-26 21:59:52 -04:00
parent a2b8c250a8
commit 4fe6ac0d4b
33 changed files with 294 additions and 883 deletions

View File

@@ -5,21 +5,20 @@
from subprocess import Popen, PIPE
import sys
def exec_cmd(cmd, path, input_file=None):
def exec_cmd(cmd, path, input_string=None):
""" Execute the specified command and return the result. """
out = ''
err = ''
parts = cmd.split()
try:
if not input_file:
if input_string is None:
process = Popen(parts, cwd=path, stdout=PIPE, stderr=PIPE,
shell=(sys.platform == 'win32'))
out, err = process.communicate()
else:
with open(input_file, 'rb') as f:
process = Popen(parts, cwd=path, stdout=PIPE, stderr=PIPE,
stdin=f,
shell=(sys.platform == 'win32'))
out, err = process.communicate()
process = Popen(parts, cwd=path, stdin=PIPE, stdout=PIPE, stderr=PIPE,
shell=(sys.platform == 'win32'))
out, err = process.communicate(input=input_string)
except IOError, (errno, strerror):
raise
except:

View File

@@ -35,8 +35,7 @@ cmd = [ 'python', 'tools/make_version_header.py',
RunAction(cef_dir, cmd)
print "\nPatching build configuration and source files for CEF..."
cmd = [ 'python', 'tools/patcher.py',
'--patch-config', 'patch/patch.cfg' ]
cmd = [ 'python', 'tools/patcher.py' ]
RunAction(cef_dir, cmd)
print "\nGenerating CEF project files..."

View File

@@ -4,14 +4,21 @@
from exec_util import exec_cmd
import os
import sys
if sys.platform == 'win32':
# Force use of the git version bundled with depot_tools.
git_exe = 'git.bat'
else:
git_exe = 'git'
def is_checkout(path):
""" Returns true if the path represents a git checkout. """
return os.path.exists(os.path.join(path, '.git'))
return os.path.isdir(os.path.join(path, '.git'))
def get_hash(path = '.', branch = 'HEAD'):
""" Returns the git hash for the specified branch/tag/hash. """
cmd = "git rev-parse %s" % branch
cmd = "%s rev-parse %s" % (git_exe, branch)
result = exec_cmd(cmd, path)
if result['out'] != '':
return result['out'].strip()
@@ -19,7 +26,7 @@ def get_hash(path = '.', branch = 'HEAD'):
def get_url(path = '.'):
""" Returns the origin url for the specified path. """
cmd = "git config --get remote.origin.url"
cmd = "%s config --get remote.origin.url" % git_exe
result = exec_cmd(cmd, path)
if result['out'] != '':
return result['out'].strip()
@@ -27,7 +34,7 @@ def get_url(path = '.'):
def get_commit_number(path = '.', branch = 'HEAD'):
""" Returns the number of commits in the specified branch/tag/hash. """
cmd = "git rev-list --count %s" % branch
cmd = "%s rev-list --count %s" % (git_exe, branch)
result = exec_cmd(cmd, path)
if result['out'] != '':
return result['out'].strip()
@@ -37,3 +44,64 @@ def get_changed_files(path = '.'):
""" Retrieves the list of changed files. """
# not implemented
return []
def write_indented_output(output):
""" Apply a fixed amount of intent to lines before printing. """
if output == '':
return
for line in output.split('\n'):
line = line.strip()
if len(line) == 0:
continue
sys.stdout.write('\t%s\n' % line)
def git_apply_patch_file(patch_path, patch_dir):
""" Apply |patch_path| to files in |patch_dir|. """
patch_name = os.path.basename(patch_path)
sys.stdout.write('\nApply %s in %s\n' % (patch_name, patch_dir))
if not os.path.isfile(patch_path):
sys.stdout.write('... patch file does not exist.\n')
return 'fail'
patch_string = open(patch_path, 'rb').read()
if sys.platform == 'win32':
# Convert the patch to Unix line endings. This is necessary to avoid
# whitespace errors with git apply.
patch_string = patch_string.replace('\r\n', '\n')
# Git apply fails silently if not run relative to a respository root.
if not is_checkout(patch_dir):
sys.stdout.write('... patch directory is not a repository root.\n')
return 'fail'
# Output patch contents.
cmd = '%s apply -p0 --numstat' % git_exe
result = exec_cmd(cmd, patch_dir, patch_string)
write_indented_output(result['out'].replace('<stdin>', patch_name))
# Reverse check to see if the patch has already been applied.
cmd = '%s apply -p0 --reverse --check' % git_exe
result = exec_cmd(cmd, patch_dir, patch_string)
if result['err'].find('error:') < 0:
sys.stdout.write('... already applied (skipping).\n')
return 'skip'
# Normal check to see if the patch can be applied cleanly.
cmd = '%s apply -p0 --check' % git_exe
result = exec_cmd(cmd, patch_dir, patch_string)
if result['err'].find('error:') >= 0:
sys.stdout.write('... failed to apply:\n')
write_indented_output(result['err'].replace('<stdin>', patch_name))
return 'fail'
# Apply the patch file. This should always succeed because the previous
# command succeeded.
cmd = '%s apply -p0' % git_exe
result = exec_cmd(cmd, patch_dir, patch_string)
if result['err'] == '':
sys.stdout.write('... successfully applied.\n')
else:
sys.stdout.write('... successfully applied (with warnings):\n')
write_indented_output(result['err'].replace('<stdin>', patch_name))
return 'apply'

View File

@@ -1,2 +1,2 @@
@echo off
python.bat tools\patcher.py --patch-config patch/patch.cfg
python.bat tools\patcher.py

View File

@@ -1,2 +1,2 @@
#!/bin/sh
python tools/patcher.py --patch-config patch/patch.cfg
python tools/patcher.py

View File

@@ -75,7 +75,7 @@ if options.resave and options.revert:
# The CEF root directory is the parent directory of _this_ script.
cef_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
src_dir = os.path.join(cef_dir, os.pardir)
src_dir = os.path.abspath(os.path.join(cef_dir, os.pardir))
# Determine the type of Chromium checkout.
if not git.is_checkout(src_dir):
@@ -104,8 +104,10 @@ for patch in patches:
if os.path.isfile(patch_file):
msg('Reading patch file %s' % patch_file)
patch_root = patch['path']
patch_root_abs = os.path.abspath(os.path.join(cef_dir, patch_root))
if 'path' in patch:
patch_root_abs = os.path.abspath(os.path.join(src_dir, patch['path']))
else:
patch_root_abs = src_dir
# Retrieve the list of paths modified by the patch file.
patch_paths = extract_paths(patch_file)
@@ -136,7 +138,8 @@ for patch in patches:
if not options.revert:
# Apply the patch file.
msg('Applying patch to %s' % patch_root_abs)
result = exec_cmd('patch -p0', patch_root_abs, patch_file)
patch_string = open(patch_file, 'rb').read()
result = exec_cmd('patch -p0', patch_root_abs, patch_string)
if result['err'] != '':
raise Exception('Failed to apply patch file: %s' % result['err'])
sys.stdout.write(result['out'])

View File

@@ -1,615 +0,0 @@
""" Patch utility to apply unified diffs """
""" Brute-force line-by-line parsing
Project home: http://code.google.com/p/python-patch/
This file is subject to the MIT license available here:
http://www.opensource.org/licenses/mit-license.php
CEF Changes
-----------
2016/10/27
- Create folders for new files created by a patch if needed
- Adding support for patches created with git diff on non svn platforms
(git diff on OSX creates source/target as /dev/null
for new/deleted files)
2015/04/22
- Write to stdout instead of using warning() for messages
2013/01/03
- Add support for patches containing new files
2009/07/22
- Add a 'root_directory' argument to PatchInfo::apply
- Fix a Python 2.4 compile error in PatchInfo::parse_stream
"""
__author__ = "techtonik.rainforce.org"
__version__ = "8.12-1"
import copy
import logging
import os
import re
from stat import *
# cStringIO doesn't support unicode in 2.5
from StringIO import StringIO
from logging import debug, info
from os.path import exists, isfile
from os import unlink
debugmode = False
def from_file(filename):
""" read and parse patch file
return PatchInfo() object
"""
info("reading patch from file %s" % filename)
fp = open(filename, "rb")
patch = PatchInfo(fp)
fp.close()
return patch
def from_string(s):
""" parse text string and return PatchInfo() object """
return PatchInfo(
StringIO.StringIO(s)
)
def msg(message):
""" Output a message. """
sys.stdout.write('--> ' + message + "\n")
class HunkInfo(object):
""" parsed hunk data (hunk starts with @@ -R +R @@) """
def __init__(self):
# define HunkInfo data members
self.startsrc=None
self.linessrc=None
self.starttgt=None
self.linestgt=None
self.invalid=False
self.text=[]
def copy(self):
return copy.copy(self)
# def apply(self, estream):
# """ write hunk data into enumerable stream
# return strings one by one until hunk is
# over
#
# enumerable stream are tuples (lineno, line)
# where lineno starts with 0
# """
# pass
class PatchInfo(object):
""" patch information container """
def __init__(self, stream=None):
""" parse incoming stream """
# define PatchInfo data members
# table with a row for every source file
#: list of source filenames
self.source=None
self.target=None
#: list of lists of hunks
self.hunks=None
#: file endings statistics for every hunk
self.hunkends=None
if stream:
self.parse_stream(stream)
def copy(self):
return copy.copy(self)
def parse_stream(self, stream):
""" parse unified diff """
self.source = []
self.target = []
self.hunks = []
self.hunkends = []
# define possible file regions that will direct the parser flow
header = False # comments before the patch body
filenames = False # lines starting with --- and +++
hunkhead = False # @@ -R +R @@ sequence
hunkbody = False #
hunkskip = False # skipping invalid hunk mode
header = True
lineends = dict(lf=0, crlf=0, cr=0)
nextfileno = 0
nexthunkno = 0 #: even if index starts with 0 user messages number hunks from 1
# hunkinfo holds parsed values, hunkactual - calculated
hunkinfo = HunkInfo()
hunkactual = dict(linessrc=None, linestgt=None)
fe = enumerate(stream)
for lineno, line in fe:
# analyze state
if header and line.startswith("--- "):
header = False
# switch to filenames state
filenames = True
#: skip hunkskip and hunkbody code until you read definition of hunkhead
if hunkbody:
# process line first
if re.match(r"^[- \+\\]", line):
# gather stats about line endings
if line.endswith("\r\n"):
self.hunkends[nextfileno-1]["crlf"] += 1
elif line.endswith("\n"):
self.hunkends[nextfileno-1]["lf"] += 1
elif line.endswith("\r"):
self.hunkends[nextfileno-1]["cr"] += 1
if line.startswith("-"):
hunkactual["linessrc"] += 1
elif line.startswith("+"):
hunkactual["linestgt"] += 1
elif not line.startswith("\\"):
hunkactual["linessrc"] += 1
hunkactual["linestgt"] += 1
hunkinfo.text.append(line)
# todo: handle \ No newline cases
else:
msg("invalid hunk no.%d at %d for target file %s" % (nexthunkno, lineno+1, self.target[nextfileno-1]))
# add hunk status node
self.hunks[nextfileno-1].append(hunkinfo.copy())
self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True
# switch to hunkskip state
hunkbody = False
hunkskip = True
# check exit conditions
if hunkactual["linessrc"] > hunkinfo.linessrc or hunkactual["linestgt"] > hunkinfo.linestgt:
msg("extra hunk no.%d lines at %d for target %s" % (nexthunkno, lineno+1, self.target[nextfileno-1]))
# add hunk status node
self.hunks[nextfileno-1].append(hunkinfo.copy())
self.hunks[nextfileno-1][nexthunkno-1]["invalid"] = True
# switch to hunkskip state
hunkbody = False
hunkskip = True
elif hunkinfo.linessrc == hunkactual["linessrc"] and hunkinfo.linestgt == hunkactual["linestgt"]:
self.hunks[nextfileno-1].append(hunkinfo.copy())
# switch to hunkskip state
hunkbody = False
hunkskip = True
# detect mixed window/unix line ends
ends = self.hunkends[nextfileno-1]
if ((ends["cr"]!=0) + (ends["crlf"]!=0) + (ends["lf"]!=0)) > 1:
msg("inconsistent line ends in patch hunks for %s" % self.source[nextfileno-1])
if debugmode:
debuglines = dict(ends)
debuglines.update(file=self.target[nextfileno-1], hunk=nexthunkno)
debug("crlf: %(crlf)d lf: %(lf)d cr: %(cr)d\t - file: %(file)s hunk: %(hunk)d" % debuglines)
if hunkskip:
match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line)
if match:
# switch to hunkhead state
hunkskip = False
hunkhead = True
elif line.startswith("--- "):
# switch to filenames state
hunkskip = False
filenames = True
if debugmode and len(self.source) > 0:
debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1]))
if filenames:
if line.startswith("--- "):
if nextfileno in self.source:
msg("skipping invalid patch for %s" % self.source[nextfileno])
del self.source[nextfileno]
# double source filename line is encountered
# attempt to restart from this second line
re_filename = "^--- ([^\t]+)"
match = re.match(re_filename, line)
if not match:
msg("skipping invalid filename at line %d" % lineno)
# switch back to header state
filenames = False
header = True
else:
self.source.append(match.group(1).strip())
elif not line.startswith("+++ "):
if nextfileno in self.source:
msg("skipping invalid patch with no target for %s" % self.source[nextfileno])
del self.source[nextfileno]
else:
# this should be unreachable
msg("skipping invalid target patch")
filenames = False
header = True
else:
if nextfileno in self.target:
msg("skipping invalid patch - double target at line %d" % lineno)
del self.source[nextfileno]
del self.target[nextfileno]
nextfileno -= 1
# double target filename line is encountered
# switch back to header state
filenames = False
header = True
else:
re_filename = "^\+\+\+ ([^\t]+)"
match = re.match(re_filename, line)
if not match:
msg("skipping invalid patch - no target filename at line %d" % lineno)
# switch back to header state
filenames = False
header = True
else:
self.target.append(match.group(1).strip())
nextfileno += 1
# switch to hunkhead state
filenames = False
hunkhead = True
nexthunkno = 0
self.hunks.append([])
self.hunkends.append(lineends.copy())
continue
if hunkhead:
match = re.match("^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))?", line)
if not match:
if nextfileno-1 not in self.hunks:
msg("skipping invalid patch with no hunks for file %s" % self.target[nextfileno-1])
# switch to header state
hunkhead = False
header = True
continue
else:
# switch to header state
hunkhead = False
header = True
else:
hunkinfo.startsrc = int(match.group(1))
if match.group(3):
hunkinfo.linessrc = int(match.group(3))
else:
hunkinfo.linessrc = 1
hunkinfo.starttgt = int(match.group(4))
if match.group(6):
hunkinfo.linestgt = int(match.group(6))
else:
hunkinfo.linestgt = 1
hunkinfo.invalid = False
hunkinfo.text = []
hunkactual["linessrc"] = hunkactual["linestgt"] = 0
# switch to hunkbody state
hunkhead = False
hunkbody = True
nexthunkno += 1
continue
else:
if not hunkskip:
msg("patch file incomplete - %s" % filename)
# sys.exit(?)
else:
# duplicated message when an eof is reached
if debugmode and len(self.source) > 0:
debug("- %2d hunks for %s" % (len(self.hunks[nextfileno-1]), self.source[nextfileno-1]))
info("total files: %d total hunks: %d" % (len(self.source), sum(len(hset) for hset in self.hunks)))
def apply(self, root_directory = None):
""" apply parsed patch """
total = len(self.source)
for fileno, filename in enumerate(self.source):
# git diff on OSX creates source/target as /dev/null for new/deleted files
if filename != '/dev/null':
f2patch = filename
else:
f2patch = self.target[fileno]
if not root_directory is None:
f2patch = root_directory + f2patch
if not exists(f2patch):
# if the patch contains a single hunk at position 0 consider it a new file
if len(self.hunks[fileno]) == 1 and self.hunks[fileno][0].startsrc == 0:
hunklines = [x[1:].rstrip("\r\n") for x in self.hunks[fileno][0].text if x[0] in " +"]
if len(hunklines) > 0:
f2patchfolder = os.path.dirname(os.path.abspath(f2patch))
if not os.path.exists(f2patchfolder):
msg("creating folder %s" % (f2patchfolder))
os.makedirs(f2patchfolder)
msg("creating file %s" % (f2patch))
f = open(f2patch, "wb")
for line in hunklines:
f.write(line + "\n")
f.close()
continue
f2patch = self.target[fileno]
if not exists(f2patch):
msg("source/target file does not exist\n--- %s\n+++ %s" % (filename, f2patch))
continue
if not isfile(f2patch):
msg("not a file - %s" % f2patch)
continue
filename = f2patch
info("processing %d/%d:\t %s" % (fileno+1, total, filename))
# validate before patching
f2fp = open(filename)
hunkno = 0
hunk = self.hunks[fileno][hunkno]
hunkfind = []
hunkreplace = []
validhunks = 0
canpatch = False
for lineno, line in enumerate(f2fp):
if lineno+1 < hunk.startsrc:
continue
elif lineno+1 == hunk.startsrc:
hunkfind = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " -"]
hunkreplace = [x[1:].rstrip("\r\n") for x in hunk.text if x[0] in " +"]
#pprint(hunkreplace)
hunklineno = 0
# todo \ No newline at end of file
# check hunks in source file
if lineno+1 < hunk.startsrc+len(hunkfind)-1:
if line.rstrip("\r\n") == hunkfind[hunklineno]:
hunklineno+=1
else:
debug("hunk no.%d doesn't match source file %s" % (hunkno+1, filename))
# file may be already patched, but we will check other hunks anyway
hunkno += 1
if hunkno < len(self.hunks[fileno]):
hunk = self.hunks[fileno][hunkno]
continue
else:
break
# check if processed line is the last line
if lineno+1 == hunk.startsrc+len(hunkfind)-1:
debug("file %s hunk no.%d -- is ready to be patched" % (filename, hunkno+1))
hunkno+=1
validhunks+=1
if hunkno < len(self.hunks[fileno]):
hunk = self.hunks[fileno][hunkno]
else:
if validhunks == len(self.hunks[fileno]):
# patch file
canpatch = True
break
else:
if hunkno < len(self.hunks[fileno]) and \
(len(self.hunks[fileno]) != 1 or self.hunks[fileno][0].startsrc != 0):
msg("premature end of source file %s at hunk %d" % (filename, hunkno+1))
f2fp.close()
if validhunks < len(self.hunks[fileno]):
if check_patched(filename, self.hunks[fileno]):
msg("already patched %s" % filename)
else:
msg("source file is different - %s" % filename)
if canpatch:
backupname = filename+".orig"
if exists(backupname):
msg("can't backup original file to %s - aborting" % backupname)
else:
import shutil
shutil.move(filename, backupname)
if patch_hunks(backupname, filename, self.hunks[fileno]):
msg("successfully patched %s" % filename)
unlink(backupname)
else:
msg("error patching file %s" % filename)
shutil.copy(filename, filename+".invalid")
msg("invalid version is saved to %s" % filename+".invalid")
# todo: proper rejects
shutil.move(backupname, filename)
# todo: check for premature eof
def check_patched(filename, hunks):
matched = True
fp = open(filename)
class NoMatch(Exception):
pass
# special case for new files
try:
if len(hunks) == 1 and hunks[0].startsrc == 0:
hunklines = [x[1:].rstrip("\r\n") for x in hunks[0].text if x[0] in " +"]
if len(hunklines) > 0:
for line in hunklines:
srcline = fp.readline()
if not len(srcline) or srcline.rstrip("\r\n") != line:
raise NoMatch
srcline = fp.readline()
if len(srcline):
raise NoMatch
fp.close()
return True
except NoMatch:
fp.close()
fp = open(filename)
lineno = 1
line = fp.readline()
hno = None
try:
if not len(line):
raise NoMatch
for hno, h in enumerate(hunks):
# skip to line just before hunk starts
while lineno < h.starttgt-1:
line = fp.readline()
lineno += 1
if not len(line):
raise NoMatch
for hline in h.text:
# todo: \ No newline at the end of file
if not hline.startswith("-") and not hline.startswith("\\"):
line = fp.readline()
lineno += 1
if not len(line):
raise NoMatch
if line.rstrip("\r\n") != hline[1:].rstrip("\r\n"):
msg("file is not patched - failed hunk: %d" % (hno+1))
raise NoMatch
except NoMatch:
matched = False
# todo: display failed hunk, i.e. expected/found
fp.close()
return matched
def patch_stream(instream, hunks):
""" given a source stream and hunks iterable, yield patched stream
converts lineends in hunk lines to the best suitable format
autodetected from input
"""
# todo: At the moment substituted lineends may not be the same
# at the start and at the end of patching. Also issue a
# warning/throw about mixed lineends (is it really needed?)
hunks = iter(hunks)
srclineno = 1
lineends = {'\n':0, '\r\n':0, '\r':0}
def get_line():
"""
local utility function - return line from source stream
collecting line end statistics on the way
"""
line = instream.readline()
# 'U' mode works only with text files
if line.endswith("\r\n"):
lineends["\r\n"] += 1
elif line.endswith("\n"):
lineends["\n"] += 1
elif line.endswith("\r"):
lineends["\r"] += 1
return line
for hno, h in enumerate(hunks):
debug("hunk %d" % (hno+1))
# skip to line just before hunk starts
while srclineno < h.startsrc:
yield get_line()
srclineno += 1
for hline in h.text:
# todo: check \ No newline at the end of file
if hline.startswith("-") or hline.startswith("\\"):
get_line()
srclineno += 1
continue
else:
if not hline.startswith("+"):
get_line()
srclineno += 1
line2write = hline[1:]
# detect if line ends are consistent in source file
if sum([bool(lineends[x]) for x in lineends]) == 1:
newline = [x for x in lineends if lineends[x] != 0][0]
yield line2write.rstrip("\r\n")+newline
else: # newlines are mixed
yield line2write
for line in instream:
yield line
def patch_hunks(srcname, tgtname, hunks):
# get the current file mode
mode = os.stat(srcname)[ST_MODE]
src = open(srcname, "rb")
tgt = open(tgtname, "wb")
debug("processing target file %s" % tgtname)
tgt.writelines(patch_stream(src, hunks))
tgt.close()
src.close()
# restore the file mode
os.chmod(tgtname, mode)
return True
from optparse import OptionParser
from os.path import exists
import sys
if __name__ == "__main__":
opt = OptionParser(usage="%prog [options] unipatch-file", version="python-patch %s" % __version__)
opt.add_option("-d", action="store_true", dest="debugmode", help="debug mode")
(options, args) = opt.parse_args()
if not args:
opt.print_version()
print("")
opt.print_help()
sys.exit()
debugmode = options.debugmode
patchfile = args[0]
if not exists(patchfile) or not isfile(patchfile):
sys.exit("patch file does not exist - %s" % patchfile)
if debugmode:
logging.basicConfig(level=logging.DEBUG, format="%(levelname)8s %(message)s")
else:
logging.basicConfig(level=logging.INFO, format="%(message)s")
patch = from_file(patchfile)
#pprint(patch)
patch.apply()
# todo: document and test line ends handling logic - patch.py detects proper line-endings
# for inserted hunks and issues a warning if patched file has incosistent line ends

View File

@@ -1,32 +1,18 @@
Chromium Embedded Framework (CEF) Patch Application Tool -- patcher.py
-------------------------------------------------------------------------------
Document Last Updated: July 23, 2009
Document Last Updated: April 26, 2017
OVERVIEW
--------
The CEF patch application tool is used by the patch project to apply patches
to the Chromium and WebKit code bases. Currently only unified diff format is
supported. See the README.txt file in the patch directory for information on
how the patch project uses this tool.
The CEF patch application tool is used to apply patches to the Chromium, Blink
and third-party code bases. Currently only unified diff format is supported.
See the README.txt file in the patch directory for information on how this tool
is used.
The 'patcher.bat' file can be used to run the patch application tool with
The patch.[bat|sh] file can be used to run the patch application tool with
command-line arguments that match the default CEF directory structure and
output options. Run 'patcher.py -h' for a complete list of available command-
line arguments.
CREDITS
-------
Thanks go to techtonik for developing the python-patch script. The
patch_util.py file is a slightly modified version of the original script which
can be found here: http://code.google.com/p/python-patch/
WORK REMAINING
--------------
o Add support for the GIT patch format.

View File

@@ -7,36 +7,44 @@ from optparse import OptionParser
import os
import sys
from file_util import *
from patch_util import *
from git_util import git_apply_patch_file
# Cannot be loaded as a module.
if __name__ != "__main__":
sys.stdout.write('This file cannot be loaded as a module!')
sys.exit()
# The CEF root directory is the parent directory of _this_ script.
cef_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
cef_patch_dir = os.path.join(cef_dir, 'patch')
src_dir = os.path.abspath(os.path.join(cef_dir, os.pardir))
def normalize_dir(dir):
''' Normalize the directory value. '''
dir = dir.replace('\\', '/')
if dir[-1] != '/':
dir += '/'
return dir
def write_note(type, note):
separator = '-' * 79 + '\n'
sys.stdout.write(separator)
sys.stdout.write('!!!! %s: %s\n' % (type, note))
sys.stdout.write(separator)
def patch_file(patch_file, patch_dir):
''' Apply a single patch file in a single directory. '''
if not os.path.isfile(patch_file):
raise Exception('Patch file %s does not exist.' % patch_file)
def apply_patch_file(patch_file, patch_dir):
''' Apply a specific patch file in optional patch directory. '''
patch_path = os.path.join(cef_patch_dir, 'patches', patch_file + '.patch')
sys.stdout.write('Reading patch file %s\n' % patch_file)
patchObj = from_file(patch_file)
patchObj.apply(normalize_dir(patch_dir))
if patch_dir is None or len(patch_dir) == 0:
patch_dir = src_dir
else:
if not os.path.isabs(patch_dir):
# Apply patch relative to the Chromium 'src' directory.
patch_dir = os.path.join(src_dir, patch_dir)
patch_dir = os.path.abspath(patch_dir)
def patch_config(config_file):
result = git_apply_patch_file(patch_path, patch_dir)
if result == 'fail':
write_note('ERROR', 'This patch failed to apply. Your build will not be correct.')
return result
def apply_patch_config():
''' Apply patch files based on a configuration file. '''
# Normalize the patch directory value.
patchdir = normalize_dir(os.path.dirname(os.path.abspath(config_file)))
config_file = os.path.join(cef_patch_dir, 'patch.cfg')
if not os.path.isfile(config_file):
raise Exception('Patch config file %s does not exist.' % config_file)
@@ -45,24 +53,33 @@ def patch_config(config_file):
execfile(config_file, scope)
patches = scope["patches"]
results = {'apply': 0, 'skip': 0, 'fail': 0}
for patch in patches:
file = patchdir+'patches/'+patch['name']+'.patch'
patch_file = patch['name']
dopatch = True
if 'condition' in patch:
# Check that the environment variable is set.
if patch['condition'] not in os.environ:
sys.stdout.write('Skipping patch file %s\n' % file)
sys.stdout.write('\nSkipping patch file %s\n' % patch_file)
dopatch = False
if dopatch:
patch_file(file, patch['path'])
if 'note' in patch:
separator = '-' * 79 + '\n'
sys.stdout.write(separator)
sys.stdout.write('NOTE: %s\n' % patch['note'])
sys.stdout.write(separator)
result = apply_patch_file(patch_file, patch['path'] if 'path' in patch else None)
results[result] += 1
if 'note' in patch:
write_note('NOTE', patch['note'])
else:
results['skip'] += 1
sys.stdout.write('\n%d patches total (%d applied, %d skipped, %d failed)\n' % \
(len(patches), results['apply'], results['skip'], results['fail']))
if results['fail'] > 0:
sys.stdout.write('\n')
write_note('ERROR', '%d patches failed to apply. Your build will not be correct.' % results['fail'])
# Parse command-line options.
disc = """
@@ -70,18 +87,13 @@ This utility applies patch files.
"""
parser = OptionParser(description=disc)
parser.add_option('--patch-config', dest='patchconfig', metavar='DIR',
help='patch configuration file')
parser.add_option('--patch-file', dest='patchfile', metavar='FILE',
help='patch source file')
parser.add_option('--patch-dir', dest='patchdir', metavar='DIR',
help='patch target directory')
(options, args) = parser.parse_args()
if not options.patchconfig is None:
patch_config(options.patchconfig)
elif not options.patchfile is None and not options.patchdir is None:
patch_file(options.patchfile, options.patchdir)
if not options.patchfile is None:
apply_patch_file(options.patchfile, options.patchdir)
else:
parser.print_help(sys.stdout)
sys.exit()
apply_patch_config()