From 633f1e485f6c87f0fc15acf0fe13cb2666ce0bd4 Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Sat, 13 Feb 2016 21:35:10 -0600 Subject: [PATCH 1/4] remove info/recipe.json from conda package --- conda_build/build.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/conda_build/build.py b/conda_build/build.py index ae0d591a45..e54f8d34a6 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -210,10 +210,6 @@ def create_info_files(m, files, include_recipe=True): with open(join(config.info_dir, 'index.json'), **mode_dict) as fo: json.dump(info_index, fo, indent=2, sort_keys=True) - if include_recipe: - with open(join(config.info_dir, 'recipe.json'), **mode_dict) as fo: - json.dump(m.meta, fo, indent=2, sort_keys=True) - if sys.platform == 'win32': # make sure we use '/' path separators in metadata files = [f.replace('\\', '/') for f in files] From efcd26c52e4d3eeecbda2126056cb012fbc1e14a Mon Sep 17 00:00:00 2001 From: Ilan Schnell Date: Sat, 13 Feb 2016 21:39:24 -0600 Subject: [PATCH 2/4] update test --- .../test-recipes/metadata/extra_freeform_metadata/meta.yaml | 4 ++++ .../metadata/extra_freeform_metadata/run_test.py | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test-recipes/metadata/extra_freeform_metadata/meta.yaml b/tests/test-recipes/metadata/extra_freeform_metadata/meta.yaml index 36d698a075..f125976ce7 100644 --- a/tests/test-recipes/metadata/extra_freeform_metadata/meta.yaml +++ b/tests/test-recipes/metadata/extra_freeform_metadata/meta.yaml @@ -2,6 +2,10 @@ package: name: conda-build-test-extra-metadata version: 0.1 +test: + requires: + - pyyaml + extra: custom: metadata however: {we: want} diff --git a/tests/test-recipes/metadata/extra_freeform_metadata/run_test.py b/tests/test-recipes/metadata/extra_freeform_metadata/run_test.py index 47bde18a8c..4b8a25ff98 100644 --- a/tests/test-recipes/metadata/extra_freeform_metadata/run_test.py +++ b/tests/test-recipes/metadata/extra_freeform_metadata/run_test.py @@ -1,5 +1,6 @@ import os import json +import yaml def main(): @@ -9,9 +10,10 @@ def main(): with open(info_file, 'r') as fh: info = json.load(fh) - source_file = os.path.join(info['link']['source'], 'info', 'recipe.json') + source_file = os.path.join(info['link']['source'], + 'info', 'recipe', 'meta.yaml') with open(source_file, 'r') as fh: - source = json.load(fh) + source = yaml.load(fh) assert source['extra'] == {"custom": "metadata", "however": {"we": "want"}} From 43f3d488900683a461c28eb8ff7cdde9f038f3b1 Mon Sep 17 00:00:00 2001 From: Mike Sarahan Date: Wed, 8 Jun 2016 11:29:07 -0500 Subject: [PATCH 3/4] Add quantified cod and coverage badges to readme --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 7e3901d3b8..38386ae35e 100644 --- a/README.rst +++ b/README.rst @@ -10,6 +10,13 @@ conda-build .. image:: https://anaconda.org/conda-team/conda-build/badges/build.svg :target: https://anaconda.org/conda-team/conda-build/ + +.. image:: https://www.quantifiedcode.com/api/v1/project/1960a96404aa431bab5d834edff1cf85/badge.svg + :target: https://www.quantifiedcode.com/app/project/1960a96404aa431bab5d834edff1cf85 + :alt: Code issues + +.. image:: https://codecov.io/gh/conda/conda-build/branch/master/graph/badge.svg + :target: https://codecov.io/gh/conda/conda-build Installation From d474ca6a0fa46de4986efa6e6514042582e03b91 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 7 Jun 2016 11:36:21 +0100 Subject: [PATCH 4/4] patch: Automatically determine patch strip level This allows us to directly use patches generated using `git format-patch` and also those authored by external entities without needing to add metadata (although this may not be a bad idea). Salvaging something from the time spent on `patch.py` (which I intend to see through at some time) though it is a genuinely useful feature that will save time and make creating and upstreaming patches a little easier. Also added a test for this new feature and a test for `patch` itself. --- conda_build/source.py | 74 +++++++++++++++++++++++++++++++------ tests/test_build_recipes.py | 61 ++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 12 deletions(-) diff --git a/conda_build/source.py b/conda_build/source.py index e6c3bb49a9..d1050837d0 100644 --- a/conda_build/source.py +++ b/conda_build/source.py @@ -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 @@ -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): @@ -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 diff --git a/tests/test_build_recipes.py b/tests/test_build_recipes.py index e98024b435..f0b475aa41 100644 --- a/tests/test_build_recipes.py +++ b/tests/test_build_recipes.py @@ -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") @@ -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)