Repository: conda/conda-build

Issue:
info directory can be clobbered by a package build
Sometimes packages create a directory named “info”, this can lead to a mysterious error at the end of the build when conda-build verifies the tarball. See

https://groups.google.com/a/continuum.io/forum/m/#!topic/conda/edRGxwVCTRw

For an example where this happened with fftw-2.

Perhaps in the long run “info” could be changed to something a bit more obfuscated like “__conda_info”


Discussion:

Resolving patch:
diff --git a/conda_build/convert.py b/conda_build/convert.py
--- a/conda_build/convert.py
+++ b/conda_build/convert.py
@@ -11,8 +11,6 @@
 import glob
 import json
 import hashlib
-import ntpath
-import posixpath
 import os
 import re
 import shutil
@@ -20,6 +18,8 @@
 import tarfile
 import tempfile
 
+from conda_build.utils import filter_info_files
+
 
 def retrieve_c_extensions(file_path, show_imports=False):
     """Check tarfile for compiled C files with '.pyd' or '.so' suffixes.
@@ -273,15 +273,14 @@ def update_lib_contents(lib_directory, temp_dir, target_platform, file_path):
         os.rename(os.path.join(temp_dir, 'lib'), os.path.join(temp_dir, 'Lib'))
 
     elif target_platform == 'unix':
-        try:
-            for lib_file in glob.iglob('{}/**' .format(lib_directory)):
-                python_version = retrieve_python_version(file_path)
-                new_lib_file = re.sub('Lib', os.path.join('lib', python_version), lib_file)
-                os.renames(lib_file, new_lib_file)
+        for lib_file in glob.iglob('{}/**' .format(lib_directory)):
+            python_version = retrieve_python_version(file_path)
+            new_lib_file = re.sub('Lib', os.path.join('lib', python_version), lib_file)
+            os.renames(lib_file, new_lib_file)
 
-            os.rename(ntpath.join(temp_dir, 'Lib'), posixpath.join(temp_dir, 'lib'))
-
-        except OSError:
+        try:
+            os.rename(os.path.join(temp_dir, 'Lib'), os.path.join(temp_dir, 'lib'))
+        except:
             pass
 
 
@@ -521,17 +520,15 @@ def update_files_file(temp_dir, verbose):
     """
     files_file = os.path.join(temp_dir, 'info/files')
 
-    with open(files_file, 'w+') as files:
+    with open(files_file, 'w') as files:
         file_paths = []
         for dirpath, dirnames, filenames in os.walk(temp_dir):
-            for filename in filenames:
-                package_file_path = os.path.join(
-                    dirpath, filename).replace(temp_dir, '').lstrip(os.sep)
-                if not package_file_path.startswith('info'):
-                    file_paths.append(package_file_path)
-
-                    if verbose:
-                        print('Updating {}' .format(package_file_path))
+            relative_dir = os.path.relpath(dirpath, temp_dir)
+            filenames = [os.path.join(relative_dir, f) for f in filenames]
+            for filename in filter_info_files(filenames, ''):
+                file_paths.append(filename)
+                if verbose:
+                    print('Updating {}' .format(filename))
 
         for file_path in sorted(file_paths):
             files.write(file_path + '\n')
@@ -556,10 +553,10 @@ def create_target_archive(file_path, temp_dir, platform, output_dir):
 
     with tarfile.open(destination, 'w:bz2') as target:
         for dirpath, dirnames, filenames in os.walk(temp_dir):
+            relative_dir = os.path.relpath(dirpath, temp_dir)
+            filenames = [os.path.join(relative_dir, f) for f in filenames]
             for filename in filenames:
-                destination_file_path = os.path.join(
-                    dirpath, filename).replace(temp_dir, '').lstrip(os.sep)
-                target.add(os.path.join(dirpath, filename), arcname=destination_file_path)
+                target.add(os.path.join(temp_dir, filename), arcname=filename)
 
 
 def convert_between_unix_platforms(file_path, output_dir, platform, dependencies, verbose):
diff --git a/conda_build/tarcheck.py b/conda_build/tarcheck.py
--- a/conda_build/tarcheck.py
+++ b/conda_build/tarcheck.py
@@ -4,7 +4,7 @@
 from os.path import basename
 import tarfile
 
-from conda_build.utils import codec
+from conda_build.utils import codec, filter_info_files
 
 
 def dist_fn(fn):
@@ -37,10 +37,10 @@ def info_files(self):
         if len(lista) != len(seta):
             raise Exception('info/files: duplicates')
 
-        listb = [m.path for m in self.t.getmembers()
-                 if not (m.path.startswith('info/') or m.isdir())]
-        setb = set(listb)
-        if len(listb) != len(setb):
+        files_in_tar = [m.path for m in self.t.getmembers()]
+        files_in_tar = filter_info_files(files_in_tar, '')
+        setb = set(files_in_tar)
+        if len(files_in_tar) != len(setb):
             raise Exception('info_files: duplicate members')
 
         if seta == setb:
diff --git a/conda_build/utils.py b/conda_build/utils.py
--- a/conda_build/utils.py
+++ b/conda_build/utils.py
@@ -1002,6 +1002,29 @@ def filter_files(files_list, prefix, filter_patterns=('(.*[\\\\/])?\.git[\\\\/].
             os.path.islink(os.path.join(prefix, f))]
 
 
+def filter_info_files(files_list, prefix):
+    return filter_files(files_list, prefix, filter_patterns=(
+                    'info/index.json',
+                    'info/files',
+                    'info/paths.json',
+                    'info/about.json',
+                    'info/has_prefix',
+                    'info/hash_input_files',   # legacy, not used anymore
+                    'info/hash_input.json',
+                    'info/run_exports.yaml',
+                    'info/git',
+                    'info/recipe/.*',
+                    'info/recipe.tar',
+                    'info/test/.*',
+                    'info/LICENSE.txt',
+                    'info/requires',
+                    'info/meta',
+                    'info/platform',
+                    'info/no_link',
+                    'info/link.json',
+            ))
+
+
 def rm_rf(path, config=None):
     if on_win:
         # native windows delete is potentially much faster


Test patch:
diff --git a/tests/test-recipes/metadata/arbitrary_file_in_info_dir/meta.yaml b/tests/test-recipes/metadata/arbitrary_file_in_info_dir/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/arbitrary_file_in_info_dir/meta.yaml
@@ -0,0 +1,12 @@
+package:
+  name: test_arbitrary_file_in_info_dir
+  version: 1.0
+
+build:
+  script: mkdir -p $PREFIX/info && echo "test" > $PREFIX/info/testfile   # [unix]
+  script: mkdir %PREFIX%\info && echo "test" > $PREFIX/info/testfile   # [win]
+
+test:
+  commands:
+    - test -f $PREFIX/info/testfile  # [unix]
+    - if not exist %PREFIX%\info\testfile  exit 1  # [win]