Repository: conda/conda

Issue:
tarball install windows
@msarahan @mingwandroid What _should_ the `file://` url format be on Windows?

```
________________________ IntegrationTests.test_python3 ________________________ 
Traceback (most recent call last):
  File "C:\projects\conda\tests\test_create.py", line 146, in test_python3
    run_command(Commands.INSTALL, prefix, flask_tar_file)
  File "C:\projects\conda\tests\test_create.py", line 104, in run_command
    args.func(args, p)
  File "C:\projects\conda\conda\cli\main_install.py", line 62, in execute
    install(args, parser, 'install')
  File "C:\projects\conda\conda\cli\install.py", line 195, in install
    explicit(args.packages, prefix, verbose=not args.quiet)
  File "C:\projects\conda\conda\misc.py", line 111, in explicit
    index.update(fetch_index(channels, **fetch_args))
  File "C:\projects\conda\conda\fetch.py", line 266, in fetch_index
    for url in iterkeys(channel_urls)]
  File "C:\projects\conda\conda\fetch.py", line 67, in func
    res = f(*args, **kwargs)
  File "C:\projects\conda\conda\fetch.py", line 149, in fetch_repodata
    raise RuntimeError(msg)
RuntimeError: Could not find URL: file:///C|/projects/conda/
---------------------------- Captured stdout call ----------------------------- 
```

The relevant lines to look at here are line 64 in `conda/misc.py`

```
url_p = utils_url_path(url_p).rstrip('/')
```

and line 147 in `conda/utils.py`

```
def url_path(path):
    path = abspath(path)
    if sys.platform == 'win32':
        path = '/' + path.replace(':', '|').replace('\\', '/')
    return 'file://%s' % path
```

Help here is definitely appreciated.



Discussion:
Maybe this might be useful. 
https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/

Python's (3.4+) pathlib module might give a hint

```
In [1]: import pathlib

In [2]: pathlib.Path(r"C:\projects\conda").as_uri()
Out[2]: 'file:///C:/projects/conda'
```

I'm unable to reproduce this, however maybe the code that interprets the file uri might also be relevant.  This would include conda's `LocalFSAdapter()` in connection.py (https://github.com/conda/conda/blob/231e9db898b3d7720b49e9e2050a88be6978fd38/conda/connection.py#L205-L235) which makes a call to `url_to_path()` (https://github.com/conda/conda/blob/231e9db898b3d7720b49e9e2050a88be6978fd38/conda/connection.py#L238-L251).  These functions seem to interpret conda's `file:///C|/projects/conda/` format correctly.

One final observation: line 64 in `misc.py` strips the rightmost `/`, yet the uri in the error message seems to still have one.  Is that supposed to be happening?

Hi @groutr. thanks for looking at this and good to see you again! @kalefranz yeah, you need to keep the :

@mingwandroid no problem.  I hope to keep active here as time permits.


Resolving patch:
diff --git a/conda/config.py b/conda/config.py
--- a/conda/config.py
+++ b/conda/config.py
@@ -195,7 +195,9 @@ def get_rc_urls():
     return rc['channels']
 
 def is_url(url):
-    return url and urlparse.urlparse(url).scheme != ""
+    if url:
+        p = urlparse.urlparse(url)
+        return p.netloc != "" or p.scheme == "file"
 
 def binstar_channel_alias(channel_alias):
     if channel_alias.startswith('file:/'):


Test patch:
diff --git a/tests/test_create.py b/tests/test_create.py
--- a/tests/test_create.py
+++ b/tests/test_create.py
@@ -5,7 +5,7 @@
 from glob import glob
 import json
 from logging import getLogger, Handler
-from os.path import exists, isdir, isfile, join, relpath
+from os.path import exists, isdir, isfile, join, relpath, basename
 import os
 from shlex import split
 from shutil import rmtree, copyfile
@@ -24,7 +24,7 @@
 from conda.cli.main_remove import configure_parser as remove_configure_parser
 from conda.cli.main_update import configure_parser as update_configure_parser
 from conda.config import pkgs_dirs, bits
-from conda.install import linked as install_linked, linked_data_
+from conda.install import linked as install_linked, linked_data_, dist2dirname
 from conda.install import on_win
 from conda.compat import PY3, TemporaryDirectory
 
@@ -63,37 +63,18 @@ def reenable_dotlog(handlers):
     dotlogger.handlers = handlers
 
 
-@contextmanager
-def make_temp_env(*packages):
-    prefix = make_temp_prefix()
-    try:
-        # try to clear any config that's been set by other tests
-        config.rc = config.load_condarc('')
-
-        p = conda_argparse.ArgumentParser()
-        sub_parsers = p.add_subparsers(metavar='command', dest='cmd')
-        create_configure_parser(sub_parsers)
-
-        command = "create -y -q -p {0} {1}".format(escape_for_winpath(prefix), " ".join(packages))
-
-        args = p.parse_args(split(command))
-        args.func(args, p)
-
-        yield prefix
-    finally:
-        rmtree(prefix, ignore_errors=True)
-
-
 class Commands:
     INSTALL = "install"
     UPDATE = "update"
     REMOVE = "remove"
+    CREATE = "create"
 
 
 parser_config = {
     Commands.INSTALL: install_configure_parser,
     Commands.UPDATE: update_configure_parser,
     Commands.REMOVE: remove_configure_parser,
+    Commands.CREATE: create_configure_parser,
 }
 
 
@@ -102,23 +83,38 @@ def run_command(command, prefix, *arguments):
     sub_parsers = p.add_subparsers(metavar='command', dest='cmd')
     parser_config[command](sub_parsers)
 
-    command = "{0} -y -q -p {1} {2}".format(command,
-                                            escape_for_winpath(prefix),
-                                            " ".join(arguments))
+    prefix = escape_for_winpath(prefix)
+    arguments = list(map(escape_for_winpath, arguments))
+    command = "{0} -y -q -p {1} {2}".format(command, prefix, " ".join(arguments))
 
     args = p.parse_args(split(command))
     args.func(args, p)
 
 
+@contextmanager
+def make_temp_env(*packages):
+    prefix = make_temp_prefix()
+    try:
+        # try to clear any config that's been set by other tests
+        config.rc = config.load_condarc('')
+        run_command(Commands.CREATE, prefix, *packages)
+        yield prefix
+    finally:
+        rmtree(prefix, ignore_errors=True)
+
+
 def package_is_installed(prefix, package, exact=False):
+    packages = list(install_linked(prefix))
+    if '::' not in package:
+        packages = list(map(dist2dirname, packages))
     if exact:
-        return any(p == package for p in install_linked(prefix))
-    return any(p.startswith(package) for p in install_linked(prefix))
+        return package in packages
+    return any(p.startswith(package) for p in packages)
 
 
 def assert_package_is_installed(prefix, package, exact=False):
     if not package_is_installed(prefix, package, exact):
-        print([p for p in install_linked(prefix)])
+        print(list(install_linked(prefix)))
         raise AssertionError("package {0} is not in prefix".format(package))
 
 
@@ -147,29 +143,29 @@ def test_create_install_update_remove(self):
             assert not package_is_installed(prefix, 'flask-0.')
             assert_package_is_installed(prefix, 'python-3')
 
-    @pytest.mark.skipif(on_win, reason="windows tarball is broken still")
     @pytest.mark.timeout(300)
     def test_tarball_install_and_bad_metadata(self):
         with make_temp_env("python flask=0.10.1") as prefix:
-            assert_package_is_installed(prefix, 'flask-0.')
+            assert_package_is_installed(prefix, 'flask-0.10.1')
             run_command(Commands.REMOVE, prefix, 'flask')
-            assert not package_is_installed(prefix, 'flask-0.')
+            assert not package_is_installed(prefix, 'flask-0.10.1')
             assert_package_is_installed(prefix, 'python')
 
             # regression test for #2626
             # install tarball with full path
             flask_tar_file = glob(join(pkgs_dirs[0], 'flask-0.*.tar.bz2'))[-1]
-            if not on_win:
-                run_command(Commands.INSTALL, prefix, flask_tar_file)
-                assert_package_is_installed(prefix, 'flask-0.')
+            tar_new_path = join(prefix, basename(flask_tar_file))
+            copyfile(flask_tar_file, tar_new_path)
+            run_command(Commands.INSTALL, prefix, tar_new_path)
+            assert_package_is_installed(prefix, 'flask-0')
 
-                run_command(Commands.REMOVE, prefix, 'flask')
-                assert not package_is_installed(prefix, 'flask-0.')
+            run_command(Commands.REMOVE, prefix, 'flask')
+            assert not package_is_installed(prefix, 'flask-0')
 
             # regression test for #2626
             # install tarball with relative path
-            flask_tar_file = relpath(flask_tar_file)
-            run_command(Commands.INSTALL,  prefix, flask_tar_file)
+            tar_new_path = relpath(tar_new_path)
+            run_command(Commands.INSTALL, prefix, tar_new_path)
             assert_package_is_installed(prefix, 'flask-0.')
 
             # regression test for #2599