Skip to content

Commit

Permalink
Merge pull request #1011 from mingwandroid/master
Browse files Browse the repository at this point in the history
patch: Automatically determine patch strip level
  • Loading branch information
msarahan committed Jun 8, 2016
2 parents e74fb44 + d474ca6 commit cc4eeb6
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 12 deletions.
74 changes: 62 additions & 12 deletions conda_build/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
from os.path import join, isdir, isfile, abspath, expanduser, basename
from shutil import copytree, copy2
from subprocess import check_call, Popen, PIPE, CalledProcessError, check_output
from subprocess import check_call, Popen, PIPE, check_output
import locale
import time

Expand Down Expand Up @@ -314,6 +314,62 @@ def _ensure_unix_line_endings(path):
return out_path


def _commonpath(paths):
"""Python 2 doesn't have os.path.commonpath(), so roll our own"""
folders = [path.split(b'/') for path in paths]
minfolders = min(folders)
maxfolders = max(folders)
common = []
for minf, maxf in zip(minfolders, maxfolders[:len(minfolders)]):
if minf != maxf:
break
common.append(minf)
if len(common):
return b'/'.join(common) + b'/'
return b''


def _guess_patch_strip_level(filesstr, src_dir):
""" Determine the patch strip level automatically. """
maxlevel = None
files = {filestr.encode(errors='ignore') for filestr in filesstr}
src_dir = src_dir.encode(errors='ignore')
for file in files:
numslash = file.count(b'/')
maxlevel = numslash if not maxlevel else min(maxlevel, numslash)
if maxlevel == 0:
patchlevel = 0
else:
histo = dict()
histo = {i: 0 for i in range(maxlevel + 1)}
if len(files) == 1:
(common,) = files
else:
common = _commonpath(files)
maxlevel = common.count(b'/')
for file in files:
parts = file.split(b'/')
for level in range(maxlevel + 1):
if os.path.exists(join(src_dir, *parts[-len(parts) + level:])):
histo[level] += 1
order = sorted(histo, key=histo.get, reverse=True)
if histo[order[0]] == histo[order[1]]:
print("Patch level ambiguous, selecting least deep")
patchlevel = min([key for key, value
in histo.items() if value == histo[order[0]]])
return patchlevel


def _source_files_from_patch_file(path):
re_source_files = re.compile('^--- ([^\n\t]+)')
files = set()
with open(path) as f:
files = {m.group(1) for l in f.readlines()
for m in [re_source_files.search(l)]
if m and m.group(1) != '/dev/null'}
return files


def apply_patch(src_dir, path):
print('Applying patch: %r' % path)
if not isfile(path):
Expand All @@ -325,20 +381,14 @@ def apply_patch(src_dir, path):
Error:
Did not find 'patch' in: %s
You can install 'patch' using apt-get, yum (Linux), Xcode (MacOSX),
or conda, cygwin (Windows),
or conda, m2-patch (Windows),
""" % (os.pathsep.join(external.dir_paths)))
patch_args = ['-p0', '-i', path]
files = _source_files_from_patch_file(path)
patch_strip_level = _guess_patch_strip_level(files, src_dir)
patch_args = ['-p%d' % patch_strip_level, '-i', path]
if sys.platform == 'win32':
patch_args[-1] = _ensure_unix_line_endings(path)
try:
check_call([patch] + patch_args, cwd=src_dir)
except CalledProcessError:
# fallback to -p1, the git default
patch_args[0] = '-p1'
try:
check_call([patch] + patch_args, cwd=src_dir)
except CalledProcessError:
sys.exit(1)
check_call([patch] + patch_args, cwd=src_dir)
if sys.platform == 'win32' and os.path.exists(patch_args[-1]):
os.remove(patch_args[-1]) # clean up .patch_unix file

Expand Down
61 changes: 61 additions & 0 deletions tests/test_build_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from conda.compat import PY3, TemporaryDirectory
from conda.config import subdir
from conda.fetch import download
from conda_build.source import _guess_patch_strip_level, apply_patch

thisdir = os.path.dirname(os.path.realpath(__file__))
metadata_dir = os.path.join(thisdir, "test-recipes/metadata")
Expand Down Expand Up @@ -277,3 +278,63 @@ def test_token_upload():
cmd = 'anaconda --token {} remove --force conda_test_account/conda-build-test-empty_sections'\
.format(token)
subprocess.check_call(cmd.split())


def test_patch_strip_level():
patchfiles = set(('some/common/prefix/one.txt',
'some/common/prefix/two.txt',
'some/common/prefix/three.txt'))
folders = ('some', 'common', 'prefix')
files = ('one.txt', 'two.txt', 'three.txt')
basedir = os.getcwd()
with TemporaryDirectory() as tmp:
os.chdir(tmp)
os.makedirs(os.path.join(tmp, *folders))
for file in files:
with open(os.path.join(os.path.join(tmp, *folders), file), 'w') as f:
f.write('hello\n')
assert _guess_patch_strip_level(patchfiles, os.getcwd()) == 0
os.chdir(folders[0])
assert _guess_patch_strip_level(patchfiles, os.getcwd()) == 1
os.chdir(folders[1])
assert _guess_patch_strip_level(patchfiles, os.getcwd()) == 2
os.chdir(folders[2])
assert _guess_patch_strip_level(patchfiles, os.getcwd()) == 3
os.chdir(basedir)


def test_patch():
basedir = os.getcwd()
with TemporaryDirectory() as tmp:
os.chdir(tmp)
with open(os.path.join(tmp, 'file-deletion.txt'), 'w') as f:
f.write('hello\n')
with open(os.path.join(tmp, 'file-modification.txt'), 'w') as f:
f.write('hello\n')
patchfile = os.path.join(tmp, 'patch')
with open(patchfile, 'w') as f:
f.write('diff file-deletion.txt file-deletion.txt\n')
f.write('--- file-deletion.txt 2016-06-07 21:55:59.549798700 +0100\n')
f.write('+++ file-deletion.txt 1970-01-01 01:00:00.000000000 +0100\n')
f.write('@@ -1 +0,0 @@\n')
f.write('-hello\n')
f.write('diff file-creation.txt file-creation.txt\n')
f.write('--- file-creation.txt 1970-01-01 01:00:00.000000000 +0100\n')
f.write('+++ file-creation.txt 2016-06-07 21:55:59.549798700 +0100\n')
f.write('@@ -0,0 +1 @@\n')
f.write('+hello\n')
f.write('diff file-modification.txt file-modification.txt.new\n')
f.write('--- file-modification.txt 2016-06-08 18:23:08.384136600 +0100\n')
f.write('+++ file-modification.txt.new 2016-06-08 18:23:37.565136200 +0100\n')
f.write('@@ -1 +1 @@\n')
f.write('-hello\n')
f.write('+43770\n')
f.close()
apply_patch(tmp, patchfile)
assert not os.path.exists(os.path.join(tmp, 'file-deletion.txt'))
assert os.path.exists(os.path.join(tmp, 'file-creation.txt'))
assert os.path.exists(os.path.join(tmp, 'file-modification.txt'))
with open('file-modification.txt', 'r') as modified:
lines = modified.readlines()
assert lines[0] == '43770\n'
os.chdir(basedir)

0 comments on commit cc4eeb6

Please sign in to comment.