Repository: conda/conda-build

Issue:
source download fails on windows on the 2nd rebuild
It seems sources of recipes that have no hash specified are now being re-downloaded everytime CB is run. While this seems a sane choice since no integrity check can be performed, it introduces a new problem: It downloads the source archive with its original name and then renames it to contain the hash. While this works on unix OS's because they silently move one file over another, this fails on windows with:

```
Source cache directory is: C:\Users\gabm\Miniconda3\conda-bld\src_cache
No hash (md5, sha1, sha256) provided.  Source download forced.  Add hash to recipe to use source cache.
WARNING:conda_build.source:No hash (md5, sha1, sha256) provided.  Source download forced.  Add hash to recipe to use source cache.
Downloading source to cache: v3.3.1.tar.gz
Downloading https://github.com/google/protobuf/archive/v3.3.1.tar.gz
Success
Traceback (most recent call last):
  File "C:\Users\gabm\Miniconda3\Scripts\conda-build-script.py", line 10, in <module>
    sys.exit(main())
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\cli\main_build.py", line 388, in main
    execute(sys.argv[1:])
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\cli\main_build.py", line 379, in execute
    noverify=args.no_verify)
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\api.py", line 185, in build
    need_source_download=need_source_download, config=config, variants=variants)
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\build.py", line 1748, in build_tree
    notest=notest,
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\build.py", line 1010, in build
    try_download(m, no_download_source=False)
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\render.py", line 381, in try_download
    source.provide(metadata)
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\source.py", line 614, in provide
    timeout=metadata.config.timeout, locking=metadata.config.locking)
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\source.py", line 129, in unpack
    src_path = download_to_cache(cache_folder, recipe_path, source_dict)
  File "C:\Users\gabm\Miniconda3\lib\site-packages\conda_build\source.py", line 104, in download_to_cache
    os.rename(path, dest_path)
FileExistsError: [WinError 183] Cannot create a file when that file already exists: 'C:\\Users\\gabm\\Miniconda3\\conda-bld\\src_cache\\v3.3.1.tar.gz' -> 'C:\\Users\\gabm\\Miniconda3\\conda-bld\\src_cache\\v3.3.1_30f23a45c6.tar.gz'
```


Discussion:
Thanks for pointing this out. I'll fix it ASAP.

Resolving patch:
diff --git a/conda_build/skeletons/rpm.py b/conda_build/skeletons/rpm.py
--- a/conda_build/skeletons/rpm.py
+++ b/conda_build/skeletons/rpm.py
@@ -177,7 +177,7 @@ def cache_file(src_cache, url, fn=None, checksummer=hashlib.sha256):
         source = dict({'url': url, 'fn': fn})
     else:
         source = dict({'url': url})
-    cached_path = download_to_cache(src_cache, '', source)
+    cached_path, _ = download_to_cache(src_cache, '', source)
     csum = checksummer()
     csum.update(open(cached_path, 'rb').read())
     csumstr = csum.hexdigest()
@@ -216,7 +216,7 @@ def rpm_url_generate(url_dirname, rpm_name, version, release, platform, src_cach
     """
     result = rpm_filename_generate(rpm_name, version, release, platform)
     url = join(url_dirname, result)
-    path = download_to_cache(src_cache, '', dict({'url': url}))
+    path, _ = download_to_cache(src_cache, '', dict({'url': url}))
     assert path, "Failed to cache generated RPM url {}".format(result)
     return url
 
diff --git a/conda_build/source.py b/conda_build/source.py
--- a/conda_build/source.py
+++ b/conda_build/source.py
@@ -42,7 +42,7 @@ def download_to_cache(cache_folder, recipe_path, source_dict):
     if not isdir(cache_folder):
         os.makedirs(cache_folder)
 
-    fn = source_dict['fn'] if 'fn' in source_dict else basename(source_dict['url'])
+    unhashed_fn = fn = source_dict['fn'] if 'fn' in source_dict else basename(source_dict['url'])
     hash_added = False
     for hash_type in ('md5', 'sha1', 'sha256'):
         if hash_type in source_dict:
@@ -101,10 +101,11 @@ def download_to_cache(cache_folder, recipe_path, source_dict):
         if not hashed:
             hashed = hashsum_file(path, 'sha256')
         dest_path = append_hash_to_fn(path, hashed)
-        os.rename(path, dest_path)
+        if not os.path.isfile(dest_path):
+            os.rename(path, dest_path)
         path = dest_path
 
-    return path
+    return path, unhashed_fn
 
 
 def hoist_single_extracted_folder(nested_folder):
@@ -126,13 +127,14 @@ def hoist_single_extracted_folder(nested_folder):
 def unpack(source_dict, src_dir, cache_folder, recipe_path, croot, verbose=False,
            timeout=90, locking=True):
     ''' Uncompress a downloaded source. '''
-    src_path = download_to_cache(cache_folder, recipe_path, source_dict)
+    src_path, unhashed_fn = download_to_cache(cache_folder, recipe_path, source_dict)
 
     if not isdir(src_dir):
         os.makedirs(src_dir)
     if verbose:
         print("Extracting download")
     with TemporaryDirectory(dir=croot) as tmpdir:
+        unhashed_dest = os.path.join(tmpdir, unhashed_fn)
         if src_path.lower().endswith(('.tar.gz', '.tar.bz2', '.tgz', '.tar.xz',
                 '.tar', 'tar.z')):
             tar_xf(src_path, tmpdir)
@@ -143,11 +145,11 @@ def unpack(source_dict, src_dir, cache_folder, recipe_path, croot, verbose=False
             # This allows test_files or about.license_file to locate files in the wheel,
             # as well as `pip install name-version.whl` as install command
             unzip(src_path, tmpdir)
-            copy_into(src_path, tmpdir, timeout, locking=locking)
+            copy_into(src_path, unhashed_dest, timeout, locking=locking)
         else:
             # In this case, the build script will need to deal with unpacking the source
             print("Warning: Unrecognized source format. Source file will be copied to the SRC_DIR")
-            copy_into(src_path, tmpdir, timeout, locking=locking)
+            copy_into(src_path, unhashed_dest, timeout, locking=locking)
         flist = os.listdir(tmpdir)
         folder = os.path.join(tmpdir, flist[0])
         if len(flist) == 1 and os.path.isdir(folder):


Test patch:
diff --git a/tests/test-recipes/metadata/wheel_source/meta.yaml b/tests/test-recipes/metadata/wheel_source/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/wheel_source/meta.yaml
@@ -0,0 +1,15 @@
+package:
+  name: test_wheel_recipe
+  version: 1.2.3
+
+source:
+  url: https://files.pythonhosted.org/packages/34/c1/8806f99713ddb993c5366c362b2f908f18269f8d792aff1abfd700775a77/click-6.7-py2.py3-none-any.whl
+
+build:
+  # this recipe is testing that the hashing of source files doesn't break our simpler file naming here
+  script: pip install click-6.7-py2.py3-none-any.whl
+
+requirements:
+  build:
+    - python
+    - pip