From 427146e4d942deeb40a94a5d2e8a3976fd60b9f5 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Fri, 6 Mar 2020 19:35:45 +0100 Subject: [PATCH 01/22] Default for build/detect_binary_files_with_prefix should be True --- conda_build/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda_build/build.py b/conda_build/build.py index f01ac7f7d8..777c000268 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -973,7 +973,7 @@ def record_prefix_files(m, files_with_prefix): print("Files containing CONDA_PREFIX") print("-----------------------------") - detect_binary_files_with_prefix = m.get_value('build/detect_binary_files_with_prefix', False) + detect_binary_files_with_prefix = m.get_value('build/detect_binary_files_with_prefix', True) with open(join(m.config.info_dir, 'has_prefix'), 'w') as fo: for pfix, mode, fn in files_with_prefix: ignored_because = None From 613a4c29a8ac8b28448fed81d6f86c62d19db819 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 14 Apr 2020 12:09:18 +0200 Subject: [PATCH 02/22] Test fix for latest prefix replacement trouble Sorry about this @mbargull --- conda_build/build.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/conda_build/build.py b/conda_build/build.py index 777c000268..81dab85502 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -958,8 +958,9 @@ def record_prefix_files(m, files_with_prefix): # We need to cache these as otherwise the fact we remove from this in a for loop later # that also checks it has elements. len_binary_has_prefix_files = len(binary_has_prefix_files) + len_text_has_prefix_files = len(text_has_prefix_files) - if files_with_prefix and not m.noarch: + if files_with_prefix: if utils.on_win: # Paths on Windows can contain spaces, so we need to quote the # paths. Fortunately they can't contain quotes, so we don't have @@ -977,8 +978,8 @@ def record_prefix_files(m, files_with_prefix): with open(join(m.config.info_dir, 'has_prefix'), 'w') as fo: for pfix, mode, fn in files_with_prefix: ignored_because = None - if (fn in binary_has_prefix_files or (not len_binary_has_prefix_files or - detect_binary_files_with_prefix and mode == 'binary')): + if (fn in binary_has_prefix_files or ((not len_binary_has_prefix_files or + detect_binary_files_with_prefix) and mode == 'binary')): if fn in binary_has_prefix_files: if mode != 'binary': mode = 'binary' @@ -988,10 +989,11 @@ def record_prefix_files(m, files_with_prefix): "`build/binary_has_prefix_files`".format(fn)) if fn in binary_has_prefix_files: binary_has_prefix_files.remove(fn) - elif fn in text_has_prefix_files or mode == 'text': + elif (fn in text_has_prefix_files or (not len_text_has_prefix_files and mode == 'text') or + os.path.dirname(fn) == 'python-scripts'): if mode != 'text': mode = 'text' - elif fn in text_has_prefix_files: + elif fn in text_has_prefix_files and not len_text_has_prefix_files: print("File {} force-identified as 'text', " "But it is 'text' anyway, suggest removing it from " "`build/has_prefix_files`".format(fn)) From 51f454cc0008b0c06fcae8be555d9e3f27c83eb2 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 14 Apr 2020 14:15:02 +0200 Subject: [PATCH 03/22] Bye bye macOS-10.13, why can we not test on old OSes? --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e70df8f1b1..337cec4319 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -87,7 +87,7 @@ jobs: - job: 'macOS' pool: - vmImage: 'macOS-10.13' + vmImage: 'macOS-10.14' strategy: maxParallel: 10 matrix: From dcb17e3f958b19d2ee1e979a5fcfcf9435e75a29 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 14 Apr 2020 15:39:24 +0200 Subject: [PATCH 04/22] Fix Miniconda3 URL for macOS AP testing --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 337cec4319..ac0e9d94f4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -132,7 +132,7 @@ jobs: - script: | echo "Installing Miniconda" set -x -e - curl -o $(Build.StagingDirectory)/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh + curl -o $(Build.StagingDirectory)/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh chmod +x $(Build.StagingDirectory)/miniconda.sh $(Build.StagingDirectory)/miniconda.sh -b -p $(Build.StagingDirectory)/miniconda source ci/azurepipelines/activate_conda "$(Build.StagingDirectory)/miniconda/bin/python" From 295fc8b613b7bc3b4832743cd367abb660f5ac3c Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 14 Apr 2020 16:17:36 +0200 Subject: [PATCH 05/22] Update test_recipe_builds[has_prefix_files] as has_prefix_files is explicit-inclusion-only when given as a list --- tests/test-recipes/metadata/has_prefix_files/build.sh | 2 +- .../test-recipes/metadata/has_prefix_files/run_test.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test-recipes/metadata/has_prefix_files/build.sh b/tests/test-recipes/metadata/has_prefix_files/build.sh index e0e5743be8..98a08e4ff7 100644 --- a/tests/test-recipes/metadata/has_prefix_files/build.sh +++ b/tests/test-recipes/metadata/has_prefix_files/build.sh @@ -1,3 +1,3 @@ -echo $PREFIX > $PREFIX/automatic-prefix +echo $PREFIX > $PREFIX/unlisted-text-prefix echo /opt/anaconda1anaconda2anaconda3 > $PREFIX/has-prefix python $RECIPE_DIR/write_binary_has_prefix.py diff --git a/tests/test-recipes/metadata/has_prefix_files/run_test.py b/tests/test-recipes/metadata/has_prefix_files/run_test.py index ba75a12346..080538c330 100644 --- a/tests/test-recipes/metadata/has_prefix_files/run_test.py +++ b/tests/test-recipes/metadata/has_prefix_files/run_test.py @@ -6,19 +6,19 @@ def main(): prefix = os.environ['PREFIX'].replace("\\", "/") - with open(join(prefix, 'automatic-prefix')) as f: + with open(join(prefix, 'unlisted-text-prefix')) as f: data = f.read() - print('automatic-prefix') + print('unlisted-text-prefix') print(data) - assert prefix in data, prefix + " not found in " + data + assert prefix not in data, prefix + " not found in unlisted-text-prefix" + data with open(join(prefix, 'has-prefix')) as f: data = f.read() print('has-prefix') print(data) - assert prefix in data, prefix + " not found in " + data + assert prefix in data, prefix + " not found in has-prefix" + data if sys.platform == 'win32': forward_slash_prefix = prefix.replace('\\', '/') @@ -33,7 +33,7 @@ def main(): data = f.read() print('binary-has-prefix') print(data) - assert prefix.encode('utf-8') in data, prefix + " not found in " + data + assert prefix.encode('utf-8') in data, prefix + " not found in binary-has-prefix" + data if __name__ == '__main__': main() From 1a9ae0f7fb2dbb3197c7bfb880b3d06990efac36 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 14 Apr 2020 17:58:42 +0200 Subject: [PATCH 06/22] And fix the binary one again --- conda_build/build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conda_build/build.py b/conda_build/build.py index 81dab85502..2a0d1ecea4 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -974,7 +974,8 @@ def record_prefix_files(m, files_with_prefix): print("Files containing CONDA_PREFIX") print("-----------------------------") - detect_binary_files_with_prefix = m.get_value('build/detect_binary_files_with_prefix', True) + detect_binary_files_with_prefix = m.get_value('build/detect_binary_files_with_prefix', + not len_binary_has_prefix_files and not utils.on_win) with open(join(m.config.info_dir, 'has_prefix'), 'w') as fo: for pfix, mode, fn in files_with_prefix: ignored_because = None From fe7b221bb2c87feb6fbbda836c3ad3b17060b13c Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 5 May 2020 09:16:04 +0200 Subject: [PATCH 07/22] Fix entry_points_have_prefix_noarch_has_prefix_files on Windows --- .../meta.yaml | 43 +++++++++++++++++++ .../run_test.bat | 4 ++ 2 files changed, 47 insertions(+) create mode 100644 tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/meta.yaml create mode 100644 tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/run_test.bat diff --git a/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/meta.yaml b/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/meta.yaml new file mode 100644 index 0000000000..c2b938fb20 --- /dev/null +++ b/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/meta.yaml @@ -0,0 +1,43 @@ +{% set name = "ephpnhpf" %} +{% set version = "0.0.1" %} + +package: + name: {{ name|lower }} + version: {{ version }} + +source: + path: . + +build: + number: 0 + noarch: python + script: + - {{ PYTHON }} -m pip install . --no-deps --ignore-installed --no-cache-dir -vvv + # We use 'somewhere' instead of 'bin' here because conda-build is quite broken in what it considers + # and entry point script when you use noarch: python, involving finding files in 'bin' that contain + # prefix and making assumptions about that (we could check for python in the shebang at least, but + # even that doesn't indicate it is definitely an entry_point script!). + - mkdir -p ${PREFIX}/somewhere # [not win] + - echo ${PREFIX} > ${PREFIX}/somewhere/explicitly-listed-text-file-containing-prefix # [not win] + - echo ${PREFIX} > ${PREFIX}/somewhere/explicitly-not-listed-text-file-containing-prefix # [not win] + - mkdir %PREFIX%\somewhere # [win] + - echo %PREFIX% > %PREFIX%\somewhere\explicitly-listed-text-file-containing-prefix # [win] + - echo %PREFIX% > %PREFIX%\somewhere\explicitly-not-listed-text-file-containing-prefix # [win] + has_prefix_files: + - somewhere/explicitly-listed-text-file-containing-prefix + # It is not possible to build noarch: python packages on Windows without specifying the entry points + # as conda-build will complain about the pip-created exe. + entry_points: # [win] + - test_entry_points_have_prefix_CASED = entry_points_have_prefix:main.main # [win] + +requirements: + host: + - python + - pip + - setuptools + run: + - python + +test: + requires: + - ripgrep diff --git a/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/run_test.bat b/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/run_test.bat new file mode 100644 index 0000000000..eee79e73af --- /dev/null +++ b/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/run_test.bat @@ -0,0 +1,4 @@ +:: This one is noarch: python so conda creates the entry points. +%CONDA_PREFIX%\Scripts\test_entry_points_have_prefix_CASED.exe +if %ErrorLevel% NEQ 0 exit /b 1 +exit /b 0 From 5c3cf7e4febd411b4c45a7657221d70dcc07df6a Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Fri, 3 Apr 2020 13:27:35 +0200 Subject: [PATCH 08/22] jsonify any files in RECIPE_DIR/info_yaml.d --- conda_build/build.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/conda_build/build.py b/conda_build/build.py index 2a0d1ecea4..0acca9d6f2 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -735,6 +735,30 @@ def copy_readme(m): "as README.md and README.rst", file=sys.stderr) +def jsonify_info_yamls(m): + iyd = "info_yaml.d" + ijd = "info_json.d" + src = join(dirname(m.meta_path), iyd) + res = [] + if os.path.exists(src) and isdir(src): + for root, dirs, files in os.walk(src): + for file in files: + file = join(root, file) + bn, ext = os.path.splitext(os.path.basename(file)) + if ext == '.yaml': + dst = join(m.config.info_dir, ijd, bn+'.json') + try: + os.makedirs(os.path.dirname(dst)) + except: + pass + with open(file, 'r') as i, open(dst, 'w') as o: + import yaml + yaml = yaml.full_load(i) + json.dump(yaml, o, sort_keys=True, indent=2, separators=(',', ': ')) + res.append(join(os.path.basename(m.config.info_dir), ijd, bn+'.json')) + return res + + def copy_license(m): license_files = utils.ensure_list(m.get_value('about/license_file', [])) if not license_files: @@ -1194,6 +1218,7 @@ def create_info_files(m, files, prefix): copy_readme(m) copy_license(m) copy_recipe_log(m) + files.extend(jsonify_info_yamls(m)) create_all_test_files(m, test_dir=join(m.config.info_dir, 'test')) if m.config.copy_test_source_files: From 20c1da0cefeffa3b67ef7f81f26253333332080a Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Wed, 18 Mar 2020 18:19:15 +0100 Subject: [PATCH 09/22] Respect keep_old_work, do not rm_rf(self.build_folder) when either it or dirty are set .. same thing with host prefix. The code and concepts behind --dirty, --keep-old-work and also perhaps build/no_move_top_level_workdir_loops seem a bit muddled and overlapping. --dirty should *only* deal with finding a new croot, and have no part in whether to keep stuff around or not. That should be left to a new option, like: --keep=work,build-prefix,host-prefix,test-prefix,test-tmp where --keep-old-work is deprecated but enables all of these. Also, 'keep' might not be the best word, call it what we do, --retain-via-rename=work,.. --- conda_build/build.py | 13 ++++++++++++- conda_build/config.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/conda_build/build.py b/conda_build/build.py index 0acca9d6f2..18815bf7fb 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -1613,7 +1613,18 @@ def bundle_conda(output, metadata, env, stats, **kw): # clean out host prefix so that this output's files don't interfere with other outputs # We have a backup of how things were before any output scripts ran. That's # restored elsewhere. - utils.rm_rf(metadata.config.host_prefix) + + if metadata.config.keep_old_work: + prefix = metadata.config.host_prefix + dest = os.path.join(os.path.dirname(prefix), + '_'.join(('_h_env_moved', metadata.dist(), + metadata.config.host_subdir))) + print("Renaming host env directory, ", prefix, " to ", dest) + if os.path.exists(dest): + utils.rm_rf(dest) + shutil.move(prefix, dest) + else: + utils.rm_rf(metadata.config.host_prefix) return final_outputs diff --git a/conda_build/config.py b/conda_build/config.py index 9f07339e2d..02f9f1c925 100644 --- a/conda_build/config.py +++ b/conda_build/config.py @@ -760,7 +760,7 @@ def subdirs_same(self): def clean(self, remove_folders=True): # build folder is the whole burrito containing envs and source folders # It will only exist if we download source, or create a build or test environment - if remove_folders and not getattr(self, 'dirty'): + if remove_folders and not getattr(self, 'dirty') and not getattr(self, 'keep_old_work'): if self.build_id: if os.path.isdir(self.build_folder): rm_rf(self.build_folder) From 51658d0e03268bea164793d331ad1458ee359db8 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Wed, 1 Apr 2020 16:31:32 +0200 Subject: [PATCH 10/22] At the end of the env activation script, use 'set +e' so that any errors from the last command do not exit the interactive shell (very annoying when testing package build failures). We then must 'set -e' just after sourcing the env script. --- conda_build/build.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/conda_build/build.py b/conda_build/build.py index 18815bf7fb..491c40a71b 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -2474,6 +2474,8 @@ def _write_test_run_script(metadata, test_run_script, test_env_script, py_files, test_env_script=test_env_script)) if utils.on_win: tf.write("IF %ERRORLEVEL% NEQ 0 exit 1\n") + else: + tf.write('set {trace}-e\n'.format(trace=trace)) if py_files: test_python = metadata.config.test_python # use pythonw for import tests when osx_is_app is set @@ -2565,6 +2567,9 @@ def write_test_scripts(metadata, env_vars, py_files, pl_files, lua_files, r_file test_env=metadata.config.test_prefix)) if utils.on_win: tf.write("IF %ERRORLEVEL% NEQ 0 exit 1\n") + # In-case people source this, it's essential errors are not fatal in an interactive shell. + if not utils.on_win: + tf.write('set +e\n') _write_test_run_script(metadata, test_run_script, test_env_script, py_files, pl_files, lua_files, r_files, shell_files, trace) From 59116c065bd9765cf15064f6d950aae88b9d5f9d Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 5 May 2020 09:03:17 +0200 Subject: [PATCH 11/22] Rewrite apply_patch Reverse failed patches so they do not leave behind newly created files for bits that manged to apply OK (causing subsequent patches to potentially fail). Rewrite apply_patch to behave more sanely, esp. in the face of fully-correct, binary patches. .. also, try the same patch modifications we try on Windows on the other OSes. There is no reason not to. --- conda_build/source.py | 125 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 24 deletions(-) diff --git a/conda_build/source.py b/conda_build/source.py index 7060785387..da1b99cddf 100644 --- a/conda_build/source.py +++ b/conda_build/source.py @@ -551,6 +551,64 @@ def _get_patch_file_details(path): def apply_patch(src_dir, path, config, git=None): + def patch_or_reverse(patch, patch_args, cwd, stdout, stderr): + # An old reference: https://unix.stackexchange.com/a/243748/34459 + # + # I am worried that '--ignore-whitespace' may be destructive. If so we should + # avoid passing it, particularly in the initial (most likely to succeed) calls. + # + # From here-in I define a 'native' patch as one which has: + # 1. LF for the patch block metadata. + # 2. CRLF or LF for the actual patched lines matching those of the source lines. + # + # Calls to a raw 'patch' are destructive in various ways: + # 1. It leaves behind .rej and .orig files + # 2. If you pass it a patch with incorrect CRLF changes and do not pass --binary and + # if any of those blocks *can* be applied, then the whole file gets written out with + # LF. This cannot be reversed either; the text changes will be reversed but not + # line-feed changes (since all line-endings get changed, not just those of the of + # patched lines) + # 3. If patching fails, the bits that succeeded remain, so patching is not at all + # atomic. + # + # Still, we do our best to mitigate all of this as follows: + # 1. We disable .orig and .rej that for GNU patch via a temp file * + # 2 (1). We check for native application of a native patch (--binary, without --ignore-whitespace) + # 2 (2). We defer destructive calls to this until after the non-destructive ones. + # 3. When patch indicates failure, we call it with -R to reverse the damage. + # + # * Some may bemoan the loss of these, but they it is fairly random which patch and patch + # attempt they apply to so their informational value is low, besides that, they are ugly. + # (and destructive to the future patchability of the source tree). + # + import tempfile + temp_name = os.path.join(tempfile.gettempdir(), next(tempfile._get_candidate_names())) + patch_args.append('-r') + patch_args.append(temp_name) + patch_args = ['--no-backup-if-mismatch', '--batch'] + patch_args + log = get_logger(__name__) + try: + log.debug("Applying with\n{} {}".format(patch, patch_args)) + check_call_env([patch] + patch_args, cwd=cwd, stdout=stdout, stderr=stderr) + # You can use this to pretend the patch failed so as to test reversal! + # raise CalledProcessError(-1, ' '.join([patch] + patch_args)) + except Exception as e: + try: + if '--ignore-whitespace' in patch_args: + patch_args.remove('--ignore-whitespace') + patch_args.insert(0, '-R') + patch_args.append('--binary') + patch_args.append('--force') + log.debug("Reversing with\n{} {}".format(patch, patch_args)) + check_call_env([patch] + patch_args, cwd=cwd, stdout=stdout, stderr=stderr) + except: + pass + raise e + finally: + if os.path.exists(temp_name): + os.unlink(temp_name) + + exception = None if not isfile(path): sys.exit('Error: no such patch: %s' % path) @@ -587,45 +645,64 @@ def apply_patch(src_dir, path, config, git=None): or conda, m2-patch (Windows), """ % (os.pathsep.join(external.dir_paths))) patch_strip_level = _guess_patch_strip_level(files, src_dir) - patch_args = ['-p%d' % patch_strip_level, '--ignore-whitespace', '-i', path] - - # line endings are a pain. - # https://unix.stackexchange.com/a/243748/34459 + path_args = ['-i', path] + patch_args = ['-p%d' % patch_strip_level] try: log = get_logger(__name__) + # This is the case we check first of all as it is the case that allows a properly line-ended + # patch to apply correctly to a properly line-ended source tree, modifying it following the + # patch chunks exactly. + patch_or_reverse(patch, patch_args + ['--binary'] + path_args, + cwd=src_dir, stdout=stdout, stderr=stderr) + except CalledProcessError as e: + # Capture the first exception + exception = e if config.verbose: - log.info("Trying to apply patch as-is") - check_call_env([patch] + patch_args, cwd=src_dir, stdout=stdout, stderr=stderr) - except CalledProcessError: - if sys.platform == 'win32': + log.info("Applying patch natively failed. " + "Trying to apply patch non-binary with --ignore-whitespace") + try: + patch_or_reverse(patch, patch_args + ['--ignore-whitespace'] + path_args, + cwd=src_dir, stdout=stdout, stderr=stderr) + except CalledProcessError as e: unix_ending_file = _ensure_unix_line_endings(path) - patch_args[-1] = unix_ending_file + path_args[-1] = unix_ending_file try: if config.verbose: - log.info("Applying unmodified patch failed. " - "Convert to unix line endings and trying again.") - check_call_env([patch] + patch_args, cwd=src_dir, stdout=stdout, stderr=stderr) - except: + log.info("Applying natively *and* non-binary failed! " + "Converting to unix line endings and trying again. " + "WARNING :: This is destructive to the source file line-endings.") + # If this succeeds, it will change the source files' CRLFs to LFs. This can + # mess things up both for subsequent attempts (this line-ending change is not + # reversible) but worse, for subsequent, correctly crafted (I'm calling these + # "native" from now on) patches. + patch_or_reverse(patch, patch_args + ['--ignore-whitespace'] + path_args, + cwd=src_dir, stdout=stdout, stderr=stderr) + except CalledProcessError: if config.verbose: - log.info("Applying unix patch failed. " - "Convert to CRLF line endings and trying again with --binary.") - patch_args.insert(0, '--binary') + log.warning("Applying natively, non-binary *and* unix attempts all failed!? " + "Converting to CRLF line endings and trying again with " + "--ignore-whitespace and --binary. This can be destructive (even" + "with attempted reversal) to the source files' line-endings.") win_ending_file = _ensure_win_line_endings(path) - patch_args[-1] = win_ending_file + path_args[-1] = win_ending_file try: - check_call_env([patch] + patch_args, cwd=src_dir, stdout=stdout, stderr=stderr) + patch_or_reverse(patch, patch_args + ['--ignore-whitespace', '--binary'] + path_args, + cwd=src_dir, stdout=stdout, stderr=stderr) except: - raise + pass + else: + exception = None finally: if os.path.exists(win_ending_file): - os.remove(win_ending_file) # clean up .patch_win file + os.remove(win_ending_file) # clean up .patch_unix file + else: + exception = None finally: if os.path.exists(unix_ending_file): - os.remove(unix_ending_file) # clean up .patch_unix file - else: - raise - + os.remove(unix_ending_file) + if exception: + raise exception def provide(metadata): """ From 237b0d4b643475516c2d2fb0e9cb71d4e4961c78 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Tue, 5 May 2020 09:03:27 +0200 Subject: [PATCH 12/22] macho thing, otool --- conda_build/os_utils/macho.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda_build/os_utils/macho.py b/conda_build/os_utils/macho.py index b916f0eb3f..159dd0178b 100644 --- a/conda_build/os_utils/macho.py +++ b/conda_build/os_utils/macho.py @@ -199,7 +199,7 @@ def otool(path, build_prefix=None, cb_filter=is_dylib_info): # here so also check that we do not get 'useful' output. if len(lines_split) < 10 and (re.match('.*(is not a Mach-O|invalid|expected|unexpected).*', lines, re.MULTILINE)): - raise CalledProcessError + raise CalledProcessError(-1, otool) return _get_matching_load_commands(lines_split, cb_filter) From f4b2a131c77fd80704c6f4c3aa4440471730a063 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Sun, 26 Apr 2020 17:10:34 +0200 Subject: [PATCH 13/22] Filter out '/.AppleDouble' folders from find_recipe These files are left behind by macOS when it needs to ratain its extended attributes through round-trips to 'foreign' file-systems. And do we need two different bits of code globbing for meta.yamls? No, we do not. --- conda_build/api.py | 15 ++++++++------- conda_build/utils.py | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/conda_build/api.py b/conda_build/api.py index 6080e37edb..6400ea8c66 100644 --- a/conda_build/api.py +++ b/conda_build/api.py @@ -182,13 +182,14 @@ def build(recipe_paths_or_metadata, post=None, need_source_download=True, paths = _expand_globs(string_paths, os.getcwd()) recipes = [] for recipe in paths: - if (os.path.isdir(recipe) or - (os.path.isfile(recipe) and - os.path.basename(recipe) in ('meta.yaml', 'conda.yaml'))): - try: - recipes.append(find_recipe(recipe)) - except IOError: - continue + if not os.sep+'.AppleDouble' in recipe: + if (os.path.isdir(recipe) or + (os.path.isfile(recipe) and + os.path.basename(recipe) in ('meta.yaml', 'conda.yaml'))): + try: + recipes.append(find_recipe(recipe)) + except IOError: + continue metadata = [m for m in recipe_paths_or_metadata if hasattr(m, 'config')] recipes.extend(metadata) diff --git a/conda_build/utils.py b/conda_build/utils.py index 3d32d9e890..cb7cfa5fa3 100644 --- a/conda_build/utils.py +++ b/conda_build/utils.py @@ -1210,6 +1210,7 @@ def find_recipe(path): if os.path.isfile(path) and os.path.basename(path) in ["meta.yaml", "conda.yaml"]: return os.path.dirname(path) results = rec_glob(path, ["meta.yaml", "conda.yaml"]) + results = [r for r in results if not os.sep+'.AppleDouble'+os.sep in r] if len(results) > 1: base_recipe = os.path.join(path, "meta.yaml") if base_recipe in results: From 5631f77c944354c248c0216c34727c8533b3ce7d Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 30 Apr 2020 11:10:18 +0200 Subject: [PATCH 14/22] Many improvements to overlinking detection Changes :: Uses lief 0.10.1 now (no code changes were needed for this update) Fixes _map_file_to_package for finding '.dylib' and '.so' without suffixes beyond that Added our FakeDist to run-prefix, improve library_nature, do not fail if ccache exists Fixes detection of where packages comes from. We must avoid using 'canonical' channel names (such as 'default', needing instead 'pkgs/main') when determining which channels packages come from. To do this, we use a new function, linked_data_no_multichannels(). Let's hope we never end up comparing these with ones gotten via linked_data() Add local_output_folder to get_build_index()'s locally cached things. Ensure we pass 'channel_urls' as a list and add the 'local' channel to that. Disambiguate 'prefix' DSO files in the build/host throughout ovelink detection. Change ERROR :: {} not in prefix_owners to a more helpful warning and add a comment though it might be better to just remove this warning and let the final info detail any issues (for they will show up as overlinking errors). Notes :: This is still not the speed boost that this stuff needs, so I am hoping the update to 0.10.1 helps with that. Looking at the history, it should, somewhat. Case-insensitive lookup for Windows (hopefully only for files in the sysroot - which is being referred to as the whitelist in the messages ATM unfortunately - still remains to be done (and will probably get squashed into this commit). --- conda_build/index.py | 4 + conda_build/inspect_pkg.py | 11 +- conda_build/os_utils/ldd.py | 24 ++-- conda_build/os_utils/liefldd.py | 11 +- conda_build/os_utils/pyldd.py | 12 +- conda_build/post.py | 211 ++++++++++++++++++++++---------- conda_build/source.py | 2 +- conda_build/utils.py | 13 ++ 8 files changed, 201 insertions(+), 87 deletions(-) diff --git a/conda_build/index.py b/conda_build/index.py index c797f90af8..8cdb58ae81 100644 --- a/conda_build/index.py +++ b/conda_build/index.py @@ -98,6 +98,7 @@ def map(self, func, *iterables): local_index_timestamp = 0 cached_index = None local_subdir = "" +local_output_folder = "" cached_channels = [] channel_data = {} @@ -126,6 +127,7 @@ def get_build_index(subdir, bldpkgs_dir, output_folder=None, clear_cache=False, **kwargs): global local_index_timestamp global local_subdir + global local_output_folder global cached_index global cached_channels global channel_data @@ -144,6 +146,7 @@ def get_build_index(subdir, bldpkgs_dir, output_folder=None, clear_cache=False, if (clear_cache or not os.path.isfile(index_file) or local_subdir != subdir or + local_output_folder != output_folder or mtime > local_index_timestamp or cached_channels != channel_urls): @@ -232,6 +235,7 @@ def get_build_index(subdir, bldpkgs_dir, output_folder=None, clear_cache=False, channel_data['defaults'] = superchannel local_index_timestamp = os.path.getmtime(index_file) local_subdir = subdir + local_output_folder = output_folder cached_channels = channel_urls return cached_index, local_index_timestamp, channel_data diff --git a/conda_build/inspect_pkg.py b/conda_build/inspect_pkg.py index f988f338aa..9c61b8202e 100644 --- a/conda_build/inspect_pkg.py +++ b/conda_build/inspect_pkg.py @@ -33,15 +33,22 @@ def dist_files(prefix, dist): return set(meta['files']) if meta else set() -def which_package(in_prefix_path, prefix): +def which_package(in_prefix_path, prefix, avoid_canonical_channel_name = False): """ given the path of a conda installed file iterate over the conda packages the file came from. Usually the iteration yields only one package. """ norm_ipp = normcase(in_prefix_path.replace(os.sep, '/')) - for dist in linked(prefix): + from conda_build.utils import linked_data_no_multichannels + if avoid_canonical_channel_name: + fn = linked_data_no_multichannels + else: + fn = linked_data + for dist in fn(prefix): + # dfiles = set(dist.get('files', [])) dfiles = dist_files(prefix, dist) + # TODO :: This is completely wrong when the env is on a case-sensitive FS! if any(norm_ipp == normcase(w) for w in dfiles): yield dist diff --git a/conda_build/os_utils/ldd.py b/conda_build/os_utils/ldd.py index 3f982a81cb..2e1c1498b5 100644 --- a/conda_build/os_utils/ldd.py +++ b/conda_build/os_utils/ldd.py @@ -85,16 +85,26 @@ def get_linkages(obj_files, prefix, sysroot): return res +from conda_build.utils import linked_data_no_multichannels @memoized -def get_package_obj_files(dist, prefix): - data = linked_data(prefix).get(dist) +def get_package_files(dist, prefix): + files = [] + if hasattr(dist, 'get'): + files = dist.get('files') + else: + data = linked_data_no_multichannels(prefix).get(dist) + if data: + files = data.get('files', []) + return files +@memoized +def get_package_obj_files(dist, prefix): res = [] - if data: - for f in data.get('files', []): - path = join(prefix, f) - if is_codefile(path): - res.append(f) + files = get_package_files(dist, prefix) + for f in files: + path = join(prefix, f) + if is_codefile(path): + res.append(f) return res diff --git a/conda_build/os_utils/liefldd.py b/conda_build/os_utils/liefldd.py index c0deb914ef..494959864a 100644 --- a/conda_build/os_utils/liefldd.py +++ b/conda_build/os_utils/liefldd.py @@ -413,7 +413,7 @@ def inspect_linkages_lief(filename, resolve_filenames=True, recurse=True, # We do not include C:\Windows nor C:\Windows\System32 in this list. They are added in # get_rpaths() instead since we need to carefully control the order. default_paths = ['$SYSROOT/System32/Wbem', '$SYSROOT/System32/WindowsPowerShell/v1.0'] - results = set() + results = {} rpaths_by_binary = dict() parents_by_filename = dict({filename: None}) while todo: @@ -451,7 +451,7 @@ def inspect_linkages_lief(filename, resolve_filenames=True, recurse=True, these_orig = [('$RPATH/' + lib if not lib.startswith('/') and not lib.startswith('$') and # noqa binary.format != lief.EXE_FORMATS.MACHO else lib) for lib in libraries] - for orig in these_orig: + for lib, orig in zip(libraries, these_orig): resolved = _get_resolved_location(binary, orig, exedir, @@ -460,10 +460,11 @@ def inspect_linkages_lief(filename, resolve_filenames=True, recurse=True, default_paths=default_paths, sysroot=sysroot) if resolve_filenames: - results.add(resolved[0]) - parents_by_filename[resolved[0]] = filename2 + rec = {'orig': orig, 'resolved': os.path.normpath(resolved[0]), 'rpaths': rpaths_transitive} else: - results.add(orig) + rec = {'orig': orig, 'rpaths': rpaths_transitive} + results[lib] = rec + parents_by_filename[resolved[0]] = filename2 if recurse: if os.path.exists(resolved[0]): todo.append([resolved[0], lief.parse(resolved[0])]) diff --git a/conda_build/os_utils/pyldd.py b/conda_build/os_utils/pyldd.py index f310a3335e..b65e5ed01c 100644 --- a/conda_build/os_utils/pyldd.py +++ b/conda_build/os_utils/pyldd.py @@ -1138,16 +1138,18 @@ def inspect_linkages(filename, resolve_filenames=True, recurse=True, already_seen = set() todo = set([filename]) done = set() - results = set() + results = {} while todo != done: filename = next(iter(todo - done)) uniqueness_key, these_orig, these_resolved = _inspect_linkages_this( filename, sysroot=sysroot, arch=arch) if uniqueness_key not in already_seen: - if resolve_filenames: - results.update(these_resolved) - else: - results.update(these_orig) + for orig, resolved in zip(these_orig, these_resolved): + if resolve_filenames: + rec = {'orig': orig, 'resolved': os.path.normpath(resolved)} + else: + rec = {'orig': orig} + results[orig] = rec if recurse: todo.update(these_resolved) already_seen.add(uniqueness_key) diff --git a/conda_build/post.py b/conda_build/post.py index 1a116f48c0..a85103a1f3 100644 --- a/conda_build/post.py +++ b/conda_build/post.py @@ -31,7 +31,7 @@ get_linkages_memoized, get_rpaths_raw, get_runpaths_raw, set_rpath) from conda_build.os_utils.pyldd import codefile_type -from conda_build.os_utils.ldd import get_package_obj_files +from conda_build.os_utils.ldd import get_package_files, get_package_obj_files from conda_build.index import get_build_index from conda_build.inspect_pkg import which_package from conda_build.exceptions import (OverLinkingError, OverDependingError, RunPathError) @@ -570,7 +570,7 @@ def determine_package_nature(pkg, prefix, subdir, bldpkgs_dir, output_folder, ch dsos = [f for f in codefiles for ext in ('.dylib', '.so', '.dll', '.pyd') if ext in f] # we don't care about the actual run_exports value, just whether or not run_exports are present. # We can use channeldata and it'll be a more reliable source (no disk race condition nonsense) - _, _, channeldata = get_build_index(subdir=subdir, + _, _, channeldata1 = get_build_index(subdir=subdir, bldpkgs_dir=bldpkgs_dir, output_folder=output_folder, channel_urls=channel_urls, @@ -578,7 +578,13 @@ def determine_package_nature(pkg, prefix, subdir, bldpkgs_dir, output_folder, ch verbose=False, clear_cache=False) channel_used = pkg.channel - channeldata = channeldata.get(channel_used) + channeldata = channeldata1.get(channel_used) + # If the Dists end up coming from a multichannel such as 'defaults' + # instead of a real channel such as 'pkgs/main' then this assert + # can fire. To prevent that we use our own linked_data_no_multichannels() + # instead of conda's linked_data() + # The `or isinstance(pkg, FakeDist)` covers the case of an empty local channel. + assert channeldata or isinstance(pkg, FakeDist) if channeldata and pkg.name in channeldata['packages']: run_exports = channeldata['packages'][pkg.name].get('run_exports', {}) @@ -587,27 +593,54 @@ def determine_package_nature(pkg, prefix, subdir, bldpkgs_dir, output_folder, ch def library_nature(pkg, prefix, subdir, bldpkgs_dirs, output_folder, channel_urls): ''' - Result :: "non-library", "plugin library", "dso library", "run-exports library" + Result :: "non-library", + "interpreted library (Python|R|Python,R)", + "plugin library (Python|R|Python,R)", + "dso library", + "run-exports library", + "interpreter (R)" + "interpreter (Python)" .. in that order, i.e. if have both dsos and run_exports, it's a run_exports_library. ''' dsos, run_exports, _ = determine_package_nature(pkg, prefix, subdir, bldpkgs_dirs, output_folder, channel_urls) + if pkg.name == 'python': + return "interpreter (Python)" + elif pkg.name == 'r-base': + return "interpreter (R)" if run_exports: return "run-exports library" elif len(dsos): # If all DSOs are under site-packages or R/lib/ - dsos_without_plugins = [dso for dso in dsos - if not any(part for part in ('lib/R/library', 'site-packages') - if part in dso)] + python_dsos = [dso for dso in dsos if 'site-packages' in dso] + r_dsos = [dso for dso in dsos if 'lib/R/library' in dso] + dsos_without_plugins = [dso for dso in dsos if not dso in r_dsos + python_dsos] if len(dsos_without_plugins): return "dso library" else: - return "plugin library" + if python_dsos and r_dsos: + return "plugin library (Python,R)" + elif python_dsos: + return "plugin library (Python)" + elif r_dsos: + return "plugin library (R)" + else: + files = get_package_files(pkg, prefix) + python_files = [f for f in files if 'site-packages' in f] + r_files = [f for f in files if 'lib/R/library' in f] + if python_files and r_files: + return "interpreted library (Python,R)" + elif python_files: + return "interpreted library (Python)" + elif r_files: + return "interpreted library (R)" + return "non-library" def dists_from_names(names, prefix): results = [] - pkgs = linked_data(prefix) + from conda_build.utils import linked_data_no_multichannels + pkgs = linked_data_no_multichannels(prefix) for name in names: for pkg in pkgs: if pkg.quad[0] == name: @@ -616,12 +649,18 @@ def dists_from_names(names, prefix): class FakeDist: - def __init__(self, name, version, build_number, build_str): + def __init__(self, name, version, build_number, build_str, channel, files): self.name = name self.quad = [name] self.version = version self.build_number = build_number self.build_string = build_str + self.channel = channel + self.files = files + + def get(self, name): + if name == 'files': + return self.files DEFAULT_MAC_WHITELIST = ['/opt/X11/', @@ -703,36 +742,42 @@ def _collect_needed_dsos(sysroots_files, files, run_prefix, sysroot_substitution build_prefix = build_prefix.replace(os.sep, '/') run_prefix = run_prefix.replace(os.sep, '/') needed = get_linkages_memoized(path, resolve_filenames=True, recurse=False, - sysroot=sysroot, - envroot=run_prefix) - if sysroot: - needed = [n.replace(sysroot, sysroot_substitution) if n.startswith(sysroot) - else n for n in needed] - # We do not want to do this substitution when merging build and host prefixes. - if build_prefix != run_prefix: - needed = [n.replace(build_prefix, build_prefix_substitution) if n.startswith(build_prefix) - else n for n in needed] - needed = [relpath(n, run_prefix).replace(os.sep, '/') if n.startswith(run_prefix) - else n for n in needed] + sysroot=sysroot, envroot=run_prefix) + for lib, res in needed.items(): + resolved = res['resolved'].replace(os.sep, '/') + if sysroot and resolved.startswith(sysroot): + resolved = resolved.replace(sysroot, sysroot_substitution) + # We do not want to do this substitution when merging build and host prefixes. + if build_prefix != run_prefix and resolved.startswith(build_prefix): + resolved = resolved.replace(build_prefix, build_prefix_substitution) + if resolved.startswith(run_prefix): + resolved = relpath(resolved, run_prefix).replace(os.sep, '/') + res['resolved'] = resolved needed_dsos_for_file[f] = needed - all_needed_dsos = all_needed_dsos.union(needed) - all_needed_dsos.add(f) + all_needed_dsos = all_needed_dsos.union(set(info['resolved'] for f, info in needed.items())) return all_needed_dsos, needed_dsos_for_file -def _map_file_to_package(files, run_prefix, build_prefix, all_needed_dsos, pkg_vendored_dist, ignore_list_syms, sysroot_substitution, enable_static): +def _map_file_to_package(files, run_prefix, build_prefix, all_needed_dsos, pkg_vendored_dist, ignore_list_syms, + sysroot_substitution, enable_static): # Form a mapping of file => package + prefix_owners = {} contains_dsos = {} contains_static_libs = {} # Used for both dsos and static_libs all_lib_exports = {} + if all_needed_dsos: for prefix in (run_prefix, build_prefix): + all_lib_exports[prefix] = {} + prefix_owners[prefix] = {} for subdir2, _, filez in os.walk(prefix): for file in filez: + if 'libz.so.1' in file: + print("Debug this {}".format(file)) fp = join(subdir2, file) - dynamic_lib = any(fnmatch(fp, ext) for ext in ('*.so.*', '*.dylib.*', '*.dll')) and \ + dynamic_lib = any(fnmatch(fp, ext) for ext in ('*.so*', '*.dylib*', '*.dll')) and \ codefile_type(fp, skip_symlinks=False) is not None static_lib = any(fnmatch(fp, ext) for ext in ('*.a', '*.lib')) # Looking at all the files is very slow. @@ -741,39 +786,41 @@ def _map_file_to_package(files, run_prefix, build_prefix, all_needed_dsos, pkg_v rp = normpath(relpath(fp, prefix)) if dynamic_lib and not any(rp == normpath(w) for w in all_needed_dsos): continue - if any(rp == normpath(w) for w in all_lib_exports): + if any(rp == normpath(w) for w in all_lib_exports[prefix]): continue - owners = prefix_owners[rp] if rp in prefix_owners else [] + owners = prefix_owners[prefix][rp] if rp in prefix_owners[prefix] else [] # Self-vendoring, not such a big deal but may as well report it? if not len(owners): if any(rp == normpath(w) for w in files): owners.append(pkg_vendored_dist) - new_pkgs = list(which_package(rp, prefix)) + new_pkgs = list(which_package(rp, prefix, avoid_canonical_channel_name=True)) # Cannot filter here as this means the DSO (eg libomp.dylib) will not be found in any package # [owners.append(new_pkg) for new_pkg in new_pkgs if new_pkg not in owners # and not any([fnmatch(new_pkg.name, i) for i in ignore_for_statics])] for new_pkg in new_pkgs: if new_pkg not in owners: owners.append(new_pkg) - prefix_owners[rp] = owners - if len(prefix_owners[rp]): + prefix_owners[prefix][rp] = owners + if len(prefix_owners[prefix][rp]): exports = set(e for e in get_exports_memoized(fp, enable_static=enable_static) if not any(fnmatch(e, pattern) for pattern in ignore_list_syms)) - all_lib_exports[rp] = exports + all_lib_exports[prefix][rp] = exports # Check codefile_type to filter out linker scripts. if dynamic_lib: - contains_dsos[prefix_owners[rp][0]] = True + contains_dsos[prefix_owners[prefix][rp][0]] = True elif static_lib: if sysroot_substitution in fp: - if (prefix_owners[rp][0].name.startswith('gcc_impl_linux') or - prefix_owners[rp][0].name == 'llvm'): + if (prefix_owners[prefix][rp][0].name.startswith('gcc_impl_linux') or + prefix_owners[prefix][rp][0].name == 'llvm'): continue - print("sysroot in {}, owner is {}".format(fp, prefix_owners[rp][0])) - contains_static_libs[prefix_owners[rp][0]] = True + print("sysroot in {}, owner is {}".format(fp, prefix_owners[prefix][rp][0])) + # Hmm, not right, muddies the prefixes again. + contains_static_libs[prefix_owners[prefix][rp][0]] = True + return prefix_owners, contains_dsos, contains_static_libs, all_lib_exports -def _get_fake_pkg_dist(pkg_name, pkg_version, build_str, build_number): +def _get_fake_pkg_dist(pkg_name, pkg_version, build_str, build_number, channel, files): pkg_vendoring_name = pkg_name pkg_vendoring_version = str(pkg_version) pkg_vendoring_build_str = build_str @@ -785,7 +832,9 @@ def _get_fake_pkg_dist(pkg_name, pkg_version, build_str, build_number): return FakeDist(pkg_vendoring_name, pkg_vendoring_version, pkg_vendoring_build_number, - pkg_vendoring_build_str), pkg_vendoring_key + pkg_vendoring_build_str, + channel, + files), pkg_vendoring_key def _print_msg(errors, text, verbose): @@ -848,7 +897,7 @@ def _lookup_in_system_whitelists(errors, whitelist, needed_dso, sysroots_files, ' nor the whitelist?!'. format(msg_prelude, n_dso_p), verbose=verbose) else: - _print_msg(errors, "{}: {} not found in sysroot, is this binary repackaging?" + _print_msg(errors, "{}: {} not found in packages nor in sysroot, is this binary repackaging?" " .. do you need to use install_name_tool/patchelf?". format(msg_prelude, needed_dso), verbose=verbose) elif not in_whitelist: @@ -861,7 +910,7 @@ def _lookup_in_prefix_packages(errors, needed_dso, files, run_prefix, whitelist, in_prefix_dso = normpath(needed_dso) n_dso_p = "Needed DSO {}".format(in_prefix_dso) and_also = " (and also in this package)" if in_prefix_dso in files else "" - pkgs = list(which_package(in_prefix_dso, run_prefix)) + pkgs = list(which_package(in_prefix_dso, run_prefix, avoid_canonical_channel_name=True)) in_pkgs_in_run_reqs = [pkg for pkg in pkgs if pkg.quad[0] in requirements_run] # TODO :: metadata build/inherit_child_run_exports (for vc, mro-base-impl). for pkg in in_pkgs_in_run_reqs: @@ -907,11 +956,12 @@ def _show_linking_messages(files, errors, needed_dsos_for_file, build_prefix, ru filetype = codefile_type(path) if not filetype or filetype not in filetypes_for_platform[subdir.split('-')[0]]: continue - warn_prelude = "WARNING ({},{})".format(pkg_name, f) - err_prelude = " ERROR ({},{})".format(pkg_name, f) - info_prelude = " INFO ({},{})".format(pkg_name, f) + warn_prelude = "WARNING ({},{})".format(pkg_name, f.replace(os.sep, '/')) + err_prelude = " ERROR ({},{})".format(pkg_name, f.replace(os.sep, '/')) + info_prelude = " INFO ({},{})".format(pkg_name, f.replace(os.sep, '/')) msg_prelude = err_prelude if error_overlinking else warn_prelude + # TODO :: Determine this much earlier, storing in needed_dsos_for_file in _collect_needed_dsos() try: runpaths, _, _ = get_runpaths_raw(path) except: @@ -924,8 +974,12 @@ def _show_linking_messages(files, errors, needed_dsos_for_file, build_prefix, ru runpaths, path), verbose=verbose) needed = needed_dsos_for_file[f] - for needed_dso in needed: + for needed_dso, needed_dso_info in needed.items(): needed_dso = needed_dso.replace('/', os.sep) + # Should always be the case, even when we fail to resolve the original value is stored here + # as it is still a best attempt and informative. + if 'resolved' in needed_dso_info: + needed_dso = needed_dso_info['resolved'] if not needed_dso.startswith(os.sep) and not needed_dso.startswith('$'): _lookup_in_prefix_packages(errors, needed_dso, files, run_prefix, whitelist, info_prelude, msg_prelude, warn_prelude, verbose, requirements_run, lib_packages, lib_packages_used) @@ -963,22 +1017,24 @@ def check_overlinking_impl(pkg_name, pkg_version, build_str, build_number, subdi # Used to detect overlinking (finally) requirements_run = [req.split(' ')[0] for req in requirements_run] packages = dists_from_names(requirements_run, run_prefix) + # Not sure which to use between: + local_channel = output_folder.replace('\\', '/') if utils.on_win else output_folder[1:] + # and: + local_channel = dirname(bldpkgs_dirs).replace('\\', '/') if utils.on_win else dirname(bldpkgs_dirs)[1:] + + pkg_vendored_dist, pkg_vendoring_key = _get_fake_pkg_dist(pkg_name, pkg_version, build_str, build_number, + local_channel, files) + packages.append(pkg_vendored_dist) ignore_list = utils.ensure_list(ignore_run_exports) if subdir.startswith('linux'): ignore_list.append('libgcc-ng') + package_nature = {package: library_nature(package, run_prefix, subdir, bldpkgs_dirs, output_folder, channel_urls) for package in packages} lib_packages = set([package for package in packages if package.quad[0] not in ignore_list and - package_nature[package] != 'non-library']) - # The last package of requirements_run is this package itself, add it as being used - # incase it qualifies as a library package. - if len(packages) and packages[-1] in lib_packages: - lib_packages_used = set((packages[-1],)) - else: - lib_packages_used = set() - - pkg_vendored_dist, pkg_vendoring_key = _get_fake_pkg_dist(pkg_name, pkg_version, build_str, build_number) + [package] != 'non-library']) + lib_packages_used = set((pkg_vendored_dist,)) ignore_list_syms = ['main', '_main', '*get_pc_thunk*', '___clang_call_terminate', '_timeout'] # ignore_for_statics = ['gcc_impl_linux*', 'compiler-rt*', 'llvm-openmp*', 'gfortran_osx*'] @@ -1025,18 +1081,32 @@ def check_overlinking_impl(pkg_name, pkg_version, build_str, build_number, subdi for f in files_to_inspect: needed = needed_dsos_for_file[f] - for needed_dso in needed: + for needed_dso, needed_dso_info in needed.items(): + orig = needed_dso + resolved = needed_dso_info['resolved'] + rpaths = needed_dso_info['rpaths'] if (error_overlinking and - not needed_dso.startswith('/') and - not needed_dso.startswith(sysroot_substitution) and - not needed_dso.startswith(build_prefix_substitution) and - needed_dso not in prefix_owners and - needed_dso not in files): + not resolved.startswith('/') and + not resolved.startswith(sysroot_substitution) and + not resolved.startswith(build_prefix_substitution) and + resolved not in prefix_owners[run_prefix] and + resolved not in files): in_whitelist = False if not build_is_host: - in_whitelist = any([fnmatch(needed_dso, w) for w in whitelist]) + in_whitelist = any([fnmatch(orig, w) for w in whitelist]) if not in_whitelist: - print(" ERROR :: {} not in prefix_owners".format(needed_dso)) + if resolved in prefix_owners[build_prefix]: + print(" ERROR :: {} in prefix_owners[build_prefix]".format(needed_dso)) + elif not needed_dso.startswith('$PATH'): + # DSOs with '$RPATH' in them at this stage are 'unresolved'. Though instead of + # letting them through through like this, I should detect that they were not + # resolved and change them back to how they were stored in the consumer DSO/elf + # e.g. an elf will have a DT_NEEDED of just 'zlib.so.1' and to standardize + # processing across platforms I prefixed them all with $RPATH. That should be + # un-done so that this error message is more clearly related to the consumer.. + print("WARNING :: For consumer: '{}' with rpaths: '{}'\n" + "WARNING :: .. the package containing '{}' could not be found in the run prefix".format( + f, rpaths, needed_dso)) # Should the whitelist be expanded before the 'not in prefix_owners' check? # i.e. Do we want to be able to use the whitelist to allow missing files in general? If so move this up to @@ -1052,16 +1122,23 @@ def check_overlinking_impl(pkg_name, pkg_version, build_str, build_number, subdi warn_prelude = "WARNING ({})".format(pkg_name) err_prelude = " ERROR ({})".format(pkg_name) for lib in lib_packages - lib_packages_used: - if package_nature[lib] == 'run-exports library': + if package_nature[lib] in ('run-exports library', 'dso library'): msg_prelude = err_prelude if error_overdepending else warn_prelude elif package_nature[lib] == 'plugin library': msg_prelude = info_prelude else: msg_prelude = warn_prelude - _print_msg(errors, "{}: {} package {} in requirements/run but it is not used " - "(i.e. it is overdepending or perhaps statically linked? " - "If that is what you want then add it to `build/ignore_run_exports`)" - .format(msg_prelude, package_nature[lib], lib), verbose=verbose) + found_interpreted_and_interpreter = False + if 'interpreter' in package_nature[lib] and 'interpreted' in package_nature[pkg_vendored_dist]: + found_interpreted_and_interpreter = True + if found_interpreted_and_interpreter: + _print_msg(errors, "{}: Interpreted package '{}' is interpreted by '{}'".format( + info_prelude, pkg_vendored_dist.name, lib.name), verbose=verbose) + else: + _print_msg(errors, "{}: {} package {} in requirements/run but it is not used " + "(i.e. it is overdepending or perhaps statically linked? " + "If that is what you want then add it to `build/ignore_run_exports`)" + .format(msg_prelude, package_nature[lib], lib), verbose=verbose) if len(errors): if exception_on_error: runpaths_errors = [error for error in errors if re.match(r".*runpaths.*found in.*", error)] @@ -1106,7 +1183,7 @@ def check_overlinking(m, files, host_prefix=None): files, m.config.bldpkgs_dir, m.config.output_folder, - m.config.channel_urls, + utils.ensure_list(m.config.channel_urls) + ['local'], m.config.enable_static) diff --git a/conda_build/source.py b/conda_build/source.py index da1b99cddf..99831af58e 100644 --- a/conda_build/source.py +++ b/conda_build/source.py @@ -637,7 +637,7 @@ def patch_or_reverse(patch, patch_args, cwd, stdout, stderr): if config.verbose: print('Applying patch: %r' % path) patch = external.find_executable('patch', config.build_prefix) - if patch is None: + if patch is None or len(patch) == 0: sys.exit("""\ Error: Cannot use 'git' (not a git repo and/or patch) and did not find 'patch' in: %s diff --git a/conda_build/utils.py b/conda_build/utils.py index cb7cfa5fa3..cd577b9fda 100644 --- a/conda_build/utils.py +++ b/conda_build/utils.py @@ -1914,3 +1914,16 @@ def download_channeldata(channel_url): else: data = channeldata_cache[channel_url] return data + + +def linked_data_no_multichannels(prefix): + """ + Return a dictionary of the linked packages in prefix, with correct channels, hopefully. + cc @kalefranz. + """ + from conda.core.prefix_data import PrefixData + from conda.models.dist import Dist + pd = PrefixData(prefix) + from conda.common.compat import itervalues + return {Dist.from_string(prefix_record.fn, channel_override=prefix_record.channel.name): + prefix_record for prefix_record in itervalues(pd._prefix_records)} From 8d7fd18454166491afbaa50bbb2e5704bbbedad3 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 14:30:01 +0200 Subject: [PATCH 15/22] Tidy up a lot of messages .. New link location: None for example --- conda_build/build.py | 2 +- conda_build/cli/main_build.py | 3 +++ conda_build/os_utils/liefldd.py | 16 ++++++++-------- conda_build/post.py | 6 ++++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/conda_build/build.py b/conda_build/build.py index 491c40a71b..81447d9927 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -1025,7 +1025,7 @@ def record_prefix_files(m, files_with_prefix): if fn in text_has_prefix_files: text_has_prefix_files.remove(fn) else: - ignored_because = " :: Not in build/%s_has_prefix_files" % (mode) + ignored_because = " (not in build/%s_has_prefix_files)" % (mode) print("{fn} ({mode}): {action}{reason}".format(fn=fn, mode=mode, action="Ignoring" if ignored_because else "Patching", diff --git a/conda_build/cli/main_build.py b/conda_build/cli/main_build.py index 07a5e59123..64d112ee09 100644 --- a/conda_build/cli/main_build.py +++ b/conda_build/cli/main_build.py @@ -480,3 +480,6 @@ def main(): " Otherwise, run conda clean --lock".format(e.lock_file)) sys.exit(1) return + +if __name__ == '__main__': + main() diff --git a/conda_build/os_utils/liefldd.py b/conda_build/os_utils/liefldd.py index 494959864a..0eb3ed1fdc 100644 --- a/conda_build/os_utils/liefldd.py +++ b/conda_build/os_utils/liefldd.py @@ -477,17 +477,17 @@ def get_linkages(filename, resolve_filenames=True, recurse=True, # When we switch to lief, want to ensure these results do not change. # We do not support Windows yet with pyldd. result_pyldd = [] - if codefile_type(filename) not in ('DLLfile', 'EXEfile'): - result_pyldd = inspect_linkages_pyldd(filename, resolve_filenames=resolve_filenames, recurse=recurse, - sysroot=sysroot, arch=arch) - if not have_lief: + debug = False + if not have_lief or debug: + if codefile_type(filename) not in ('DLLfile', 'EXEfile'): + result_pyldd = inspect_linkages_pyldd(filename, resolve_filenames=resolve_filenames, recurse=recurse, + sysroot=sysroot, arch=arch) + if not have_lief: + return result_pyldd return result_pyldd - if not have_lief: - return result_pyldd - result_lief = inspect_linkages_lief(filename, resolve_filenames=resolve_filenames, recurse=recurse, sysroot=sysroot, envroot=envroot, arch=arch) - if result_pyldd and set(result_lief) != set(result_pyldd): + if debug and result_pyldd and set(result_lief) != set(result_pyldd): print("WARNING: Disagreement in get_linkages(filename={}, resolve_filenames={}, recurse={}, sysroot={}, envroot={}, arch={}):\n lief: {}\npyldd: {}\n (using lief)". format(filename, resolve_filenames, recurse, sysroot, envroot, arch, result_lief, result_pyldd)) return result_lief diff --git a/conda_build/post.py b/conda_build/post.py index a85103a1f3..7b841a3f89 100644 --- a/conda_build/post.py +++ b/conda_build/post.py @@ -346,9 +346,9 @@ def find_lib(link, prefix, files, path=None): def osx_ch_link(path, link_dict, host_prefix, build_prefix, files): link = link_dict['name'] - print("Fixing linking of %s in %s" % (link, path)) if build_prefix != host_prefix and link.startswith(build_prefix): link = link.replace(build_prefix, host_prefix) + print("Fixing linking of %s in %s" % (link, path)) print(".. seems to be linking to a compiler runtime, replacing build prefix with " "host prefix and") if not codefile_type(link): @@ -358,11 +358,13 @@ def osx_ch_link(path, link_dict, host_prefix, build_prefix, files): print(".. fixing linking of %s in %s instead" % (link, path)) link_loc = find_lib(link, host_prefix, files, path) - print("New link location is %s" % (link_loc)) if not link_loc: return + print("Fixing linking of %s in %s" % (link, path)) + print("New link location is %s" % (link_loc)) + lib_to_link = relpath(dirname(link_loc), 'lib') # path_to_lib = utils.relative(path[len(prefix) + 1:]) From 5cb5d0133096ed2c9570674d9d7e86b8deb8d939 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 10:35:07 +0200 Subject: [PATCH 16/22] Look for filename matches case insensitively in post. This may be too liberal and we may want to do it only for Windows exes/dlls. --- conda_build/cli/main_build.py | 3 --- conda_build/os_utils/liefldd.py | 16 +++++++++++++++- conda_build/post.py | 18 ++++++++++++------ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/conda_build/cli/main_build.py b/conda_build/cli/main_build.py index 64d112ee09..07a5e59123 100644 --- a/conda_build/cli/main_build.py +++ b/conda_build/cli/main_build.py @@ -480,6 +480,3 @@ def main(): " Otherwise, run conda clean --lock".format(e.lock_file)) sys.exit(1) return - -if __name__ == '__main__': - main() diff --git a/conda_build/os_utils/liefldd.py b/conda_build/os_utils/liefldd.py index 0eb3ed1fdc..10ceebafe3 100644 --- a/conda_build/os_utils/liefldd.py +++ b/conda_build/os_utils/liefldd.py @@ -459,8 +459,22 @@ def inspect_linkages_lief(filename, resolve_filenames=True, recurse=True, rpaths_transitive=rpaths_transitive, default_paths=default_paths, sysroot=sysroot) + path_fixed = os.path.normpath(resolved[0]) + # Test, randomise case. We only allow for the filename part to be random, and we allow that + # only for Windows DLLs. We may need a special case for Lib (from Python) vs lib (from R) + # too, but in general we want to enforce case checking as much as we can since even Windowws + # can be run case-sensitively if the user wishes. + # + # if binary.format == lief.EXE_FORMATS.PE: + # import random + # path_fixed = os.path.dirname(path_fixed) + os.sep + \ + # ''.join(random.choice((str.upper, str.lower))(c) for c in os.path.basename(path_fixed)) + # if random.getrandbits(1): + # path_fixed = path_fixed.replace(os.sep + 'lib' + os.sep, os.sep + 'Lib' + os.sep) + # else: + # path_fixed = path_fixed.replace(os.sep + 'Lib' + os.sep, os.sep + 'lib' + os.sep) if resolve_filenames: - rec = {'orig': orig, 'resolved': os.path.normpath(resolved[0]), 'rpaths': rpaths_transitive} + rec = {'orig': orig, 'resolved': path_fixed, 'rpaths': rpaths_transitive} else: rec = {'orig': orig, 'rpaths': rpaths_transitive} results[lib] = rec diff --git a/conda_build/post.py b/conda_build/post.py index 7b841a3f89..e513a9554d 100644 --- a/conda_build/post.py +++ b/conda_build/post.py @@ -2,7 +2,7 @@ from collections import defaultdict, OrderedDict from functools import partial -from fnmatch import fnmatch, filter as fnmatch_filter +from fnmatch import fnmatch, filter as fnmatch_filter, translate as fnmatch_translate from os.path import (basename, commonprefix, dirname, exists, isabs, isdir, isfile, islink, join, normpath, realpath, relpath, sep, splitext) import io @@ -564,7 +564,6 @@ def assert_relative_osx(path, host_prefix, build_prefix): def determine_package_nature(pkg, prefix, subdir, bldpkgs_dir, output_folder, channel_urls): - dsos = [] run_exports = None lib_prefix = pkg.name.startswith('lib') codefiles = get_package_obj_files(pkg, prefix) @@ -586,7 +585,7 @@ def determine_package_nature(pkg, prefix, subdir, bldpkgs_dir, output_folder, ch # can fire. To prevent that we use our own linked_data_no_multichannels() # instead of conda's linked_data() # The `or isinstance(pkg, FakeDist)` covers the case of an empty local channel. - assert channeldata or isinstance(pkg, FakeDist) + assert isinstance(channeldata, dict) or isinstance(pkg, FakeDist) if channeldata and pkg.name in channeldata['packages']: run_exports = channeldata['packages'][pkg.name].get('run_exports', {}) @@ -846,6 +845,12 @@ def _print_msg(errors, text, verbose): print(text) +def caseless_sepless_fnmatch(paths, pat): + match = re.compile("(?i)"+fnmatch_translate(pat.replace('\\', '/'))).match + matches = [path for path in paths if match(pat.replace('\\', '/'), re.IGNORECASE)] + return matches + + def _lookup_in_system_whitelists(errors, whitelist, needed_dso, sysroots_files, msg_prelude, info_prelude, sysroot_prefix, sysroot_substitution, verbose): # A system or ignored dependency. We should be able to find it in one of the CDT or @@ -856,10 +861,11 @@ def _lookup_in_system_whitelists(errors, whitelist, needed_dso, sysroots_files, else: replacements = [needed_dso] in_whitelist = False - # It takes a very long time to glob in C:/Windows so cache it. + # It takes a very long time to glob in C:/Windows so we do not do that. for replacement in replacements: needed_dso_w = needed_dso.replace(sysroot_substitution, replacement) - in_whitelist = any([fnmatch(needed_dso_w, w) for w in whitelist]) + # We should pass in multiple paths at once to this, but the code isn't structured for that. + in_whitelist = [caseless_sepless_fnmatch([needed_dso_w], w) for w in whitelist] if in_whitelist: n_dso_p = "Needed DSO {}".format(needed_dso_w) _print_msg(errors, '{}: {} found in the whitelist'. @@ -910,7 +916,7 @@ def _lookup_in_system_whitelists(errors, whitelist, needed_dso, sysroots_files, def _lookup_in_prefix_packages(errors, needed_dso, files, run_prefix, whitelist, info_prelude, msg_prelude, warn_prelude, verbose, requirements_run, lib_packages, lib_packages_used): in_prefix_dso = normpath(needed_dso) - n_dso_p = "Needed DSO {}".format(in_prefix_dso) + n_dso_p = "Needed DSO {}".format(in_prefix_dso.replace('\\', '/')) and_also = " (and also in this package)" if in_prefix_dso in files else "" pkgs = list(which_package(in_prefix_dso, run_prefix, avoid_canonical_channel_name=True)) in_pkgs_in_run_reqs = [pkg for pkg in pkgs if pkg.quad[0] in requirements_run] From c54d75cc716c9948e67af902a6cb5b1c4824e88d Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 14:42:58 +0200 Subject: [PATCH 17/22] pep8 --- conda_build/api.py | 2 +- conda_build/build.py | 4 ++-- conda_build/inspect_pkg.py | 5 ++--- conda_build/os_utils/ldd.py | 5 +++-- conda_build/post.py | 5 ++--- conda_build/source.py | 2 +- conda_build/utils.py | 2 +- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/conda_build/api.py b/conda_build/api.py index 6400ea8c66..497a2f29c6 100644 --- a/conda_build/api.py +++ b/conda_build/api.py @@ -182,7 +182,7 @@ def build(recipe_paths_or_metadata, post=None, need_source_download=True, paths = _expand_globs(string_paths, os.getcwd()) recipes = [] for recipe in paths: - if not os.sep+'.AppleDouble' in recipe: + if os.sep + '.AppleDouble' not in recipe: if (os.path.isdir(recipe) or (os.path.isfile(recipe) and os.path.basename(recipe) in ('meta.yaml', 'conda.yaml'))): diff --git a/conda_build/build.py b/conda_build/build.py index 81447d9927..e7fff220b9 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -746,7 +746,7 @@ def jsonify_info_yamls(m): file = join(root, file) bn, ext = os.path.splitext(os.path.basename(file)) if ext == '.yaml': - dst = join(m.config.info_dir, ijd, bn+'.json') + dst = join(m.config.info_dir, ijd, bn + '.json') try: os.makedirs(os.path.dirname(dst)) except: @@ -755,7 +755,7 @@ def jsonify_info_yamls(m): import yaml yaml = yaml.full_load(i) json.dump(yaml, o, sort_keys=True, indent=2, separators=(',', ': ')) - res.append(join(os.path.basename(m.config.info_dir), ijd, bn+'.json')) + res.append(join(os.path.basename(m.config.info_dir), ijd, bn + '.json')) return res diff --git a/conda_build/inspect_pkg.py b/conda_build/inspect_pkg.py index 9c61b8202e..9180926fd9 100644 --- a/conda_build/inspect_pkg.py +++ b/conda_build/inspect_pkg.py @@ -21,8 +21,7 @@ from conda_build.utils import (groupby, getter, comma_join, rm_rf, package_has_file, get_logger, ensure_list) -from conda_build.conda_interface import (iteritems, specs_from_args, is_linked, linked_data, linked, - get_index) +from conda_build.conda_interface import (iteritems, specs_from_args, is_linked, linked_data, get_index) from conda_build.conda_interface import display_actions, install_actions from conda_build.conda_interface import memoized @@ -33,7 +32,7 @@ def dist_files(prefix, dist): return set(meta['files']) if meta else set() -def which_package(in_prefix_path, prefix, avoid_canonical_channel_name = False): +def which_package(in_prefix_path, prefix, avoid_canonical_channel_name=False): """ given the path of a conda installed file iterate over the conda packages the file came from. Usually the iteration yields diff --git a/conda_build/os_utils/ldd.py b/conda_build/os_utils/ldd.py index 2e1c1498b5..6f390634cc 100644 --- a/conda_build/os_utils/ldd.py +++ b/conda_build/os_utils/ldd.py @@ -7,11 +7,12 @@ from conda_build.conda_interface import memoized from conda_build.conda_interface import untracked -from conda_build.conda_interface import linked_data from conda_build.os_utils.macho import otool from conda_build.os_utils.pyldd import codefile_class, inspect_linkages, machofile, is_codefile +from conda_build.utils import linked_data_no_multichannels + LDD_RE = re.compile(r'\s*(.*?)\s*=>\s*(.*?)\s*\(.*\)') LDD_NOT_FOUND_RE = re.compile(r'\s*(.*?)\s*=>\s*not found') @@ -85,7 +86,6 @@ def get_linkages(obj_files, prefix, sysroot): return res -from conda_build.utils import linked_data_no_multichannels @memoized def get_package_files(dist, prefix): files = [] @@ -97,6 +97,7 @@ def get_package_files(dist, prefix): files = data.get('files', []) return files + @memoized def get_package_obj_files(dist, prefix): res = [] diff --git a/conda_build/post.py b/conda_build/post.py index e513a9554d..76d5635f08 100644 --- a/conda_build/post.py +++ b/conda_build/post.py @@ -21,7 +21,6 @@ from conda_build.os_utils import external from conda_build.conda_interface import PY3 from conda_build.conda_interface import lchmod -from conda_build.conda_interface import linked_data from conda_build.conda_interface import walk_prefix from conda_build.conda_interface import TemporaryDirectory from conda_build.conda_interface import md5_file @@ -614,7 +613,7 @@ def library_nature(pkg, prefix, subdir, bldpkgs_dirs, output_folder, channel_url # If all DSOs are under site-packages or R/lib/ python_dsos = [dso for dso in dsos if 'site-packages' in dso] r_dsos = [dso for dso in dsos if 'lib/R/library' in dso] - dsos_without_plugins = [dso for dso in dsos if not dso in r_dsos + python_dsos] + dsos_without_plugins = [dso for dso in dsos if dso not in r_dsos + python_dsos] if len(dsos_without_plugins): return "dso library" else: @@ -846,7 +845,7 @@ def _print_msg(errors, text, verbose): def caseless_sepless_fnmatch(paths, pat): - match = re.compile("(?i)"+fnmatch_translate(pat.replace('\\', '/'))).match + match = re.compile("(?i)" + fnmatch_translate(pat.replace('\\', '/'))).match matches = [path for path in paths if match(pat.replace('\\', '/'), re.IGNORECASE)] return matches diff --git a/conda_build/source.py b/conda_build/source.py index 99831af58e..132ae24ceb 100644 --- a/conda_build/source.py +++ b/conda_build/source.py @@ -664,7 +664,7 @@ def patch_or_reverse(patch, patch_args, cwd, stdout, stderr): try: patch_or_reverse(patch, patch_args + ['--ignore-whitespace'] + path_args, cwd=src_dir, stdout=stdout, stderr=stderr) - except CalledProcessError as e: + except CalledProcessError as e: # [noqa] unix_ending_file = _ensure_unix_line_endings(path) path_args[-1] = unix_ending_file try: diff --git a/conda_build/utils.py b/conda_build/utils.py index cd577b9fda..d8d52d395d 100644 --- a/conda_build/utils.py +++ b/conda_build/utils.py @@ -1210,7 +1210,7 @@ def find_recipe(path): if os.path.isfile(path) and os.path.basename(path) in ["meta.yaml", "conda.yaml"]: return os.path.dirname(path) results = rec_glob(path, ["meta.yaml", "conda.yaml"]) - results = [r for r in results if not os.sep+'.AppleDouble'+os.sep in r] + results = [r for r in results if os.sep + '.AppleDouble' + os.sep not in r] if len(results) > 1: base_recipe = os.path.join(path, "meta.yaml") if base_recipe in results: From 119e4340c55e74eefef26febe844c6fccc08951c Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 16:05:32 +0200 Subject: [PATCH 18/22] ensure_list() does not, it ensures __iter__ --- conda_build/post.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda_build/post.py b/conda_build/post.py index 76d5635f08..d5d91f6bea 100644 --- a/conda_build/post.py +++ b/conda_build/post.py @@ -1190,7 +1190,7 @@ def check_overlinking(m, files, host_prefix=None): files, m.config.bldpkgs_dir, m.config.output_folder, - utils.ensure_list(m.config.channel_urls) + ['local'], + list(m.config.channel_urls) + ['local'], m.config.enable_static) From ba70deebada9fc38108bd23d8e8e3bdd0c659e6b Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 17:55:27 +0200 Subject: [PATCH 19/22] Add missing test file fixing entry_points_have_prefix_noarch_has_prefix_files --- .../setup.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/setup.py diff --git a/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/setup.py b/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/setup.py new file mode 100644 index 0000000000..3556a445d3 --- /dev/null +++ b/tests/test-recipes/metadata/entry_points_have_prefix_noarch_has_prefix_files/setup.py @@ -0,0 +1,17 @@ +from setuptools import setup + +name = 'noarch_python_test_package' + +setup( + name=name, + version='1.0', + author='Almar', + author_email='almar@notmyemail.com', + url='http://continuum.io', + license='(new) BSD', + description='testing noarch python package building', + platforms='any', + provides=[name], + py_modules=[name], + entry_points={'console_scripts': ['%s_script = %s:main' % (name, name)], }, +) From 4a23f1b7846650b5d597f76faa67b844e17e7647 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 18:06:17 +0200 Subject: [PATCH 20/22] pep8 --- conda_build/source.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conda_build/source.py b/conda_build/source.py index 132ae24ceb..ea03a8cf89 100644 --- a/conda_build/source.py +++ b/conda_build/source.py @@ -664,7 +664,7 @@ def patch_or_reverse(patch, patch_args, cwd, stdout, stderr): try: patch_or_reverse(patch, patch_args + ['--ignore-whitespace'] + path_args, cwd=src_dir, stdout=stdout, stderr=stderr) - except CalledProcessError as e: # [noqa] + except CalledProcessError as e: # noqa unix_ending_file = _ensure_unix_line_endings(path) path_args[-1] = unix_ending_file try: @@ -704,6 +704,7 @@ def patch_or_reverse(patch, patch_args, cwd, stdout, stderr): if exception: raise exception + def provide(metadata): """ given a recipe_dir: From a4590341175b1ff23e80c8ffd3453267fe8f1f45 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 19:17:11 +0200 Subject: [PATCH 21/22] Revert a part of my post/overlink fixes so it does not hold up the release The downside is that some packages may not get picked up. I need to check that though. It cannot remain this broken. The problem seems to be that depending on how the channel was identified to conda when it made the Dist is how it will remain and one created from 'defaults' is not equal to one created from 'pkgs/main'. During conda-build post, our channels seem to come from 'pkgs/main' but at other times they come from 'defaults'. --- conda_build/os_utils/ldd.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/conda_build/os_utils/ldd.py b/conda_build/os_utils/ldd.py index 6f390634cc..78c75872cb 100644 --- a/conda_build/os_utils/ldd.py +++ b/conda_build/os_utils/ldd.py @@ -7,13 +7,11 @@ from conda_build.conda_interface import memoized from conda_build.conda_interface import untracked +from conda_build.conda_interface import linked_data from conda_build.os_utils.macho import otool from conda_build.os_utils.pyldd import codefile_class, inspect_linkages, machofile, is_codefile -from conda_build.utils import linked_data_no_multichannels - - LDD_RE = re.compile(r'\s*(.*?)\s*=>\s*(.*?)\s*\(.*\)') LDD_NOT_FOUND_RE = re.compile(r'\s*(.*?)\s*=>\s*not found') @@ -92,7 +90,7 @@ def get_package_files(dist, prefix): if hasattr(dist, 'get'): files = dist.get('files') else: - data = linked_data_no_multichannels(prefix).get(dist) + data = linked_data(prefix).get(dist) if data: files = data.get('files', []) return files From c06b6b5da72dacbd432d10ee09562c0b66abbd77 Mon Sep 17 00:00:00 2001 From: Ray Donnelly Date: Thu, 14 May 2020 20:28:17 +0200 Subject: [PATCH 22/22] Skip test involving sympy on python 2.7 (let the rot commence) --- tests/test_api_skeleton.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_api_skeleton.py b/tests/test_api_skeleton.py index b3f3fbc4eb..e903651d21 100644 --- a/tests/test_api_skeleton.py +++ b/tests/test_api_skeleton.py @@ -412,6 +412,8 @@ def test_setuptools_test_requirements(testing_workdir): assert m.meta['test']['requires'] == ['nose >=1.0'] +@pytest.mark.skipif(sys.version[:3] == "2.7", + reason="sympy is python 3.5 and above") def test_pypi_section_order_preserved(testing_workdir): """ Test whether sections have been written in the correct order.