Repository: conda/conda

Issue:
add registry modifications to conda init
* enable long paths
* also consider symlinks


Discussion:
For symlinks, Ray thinks it's best to just try enabling development mode.  Probably should be done as a optional CLI flag.

For long paths,

* cpython documentation: https://github.com/python/cpython/blob/e42b705188271da108de42b55d9344642170aa2b/Doc/using/windows.rst#removing-the-max_path-limitation
* Windows installer code: https://github.com/python/cpython/blob/e42b705188271da108de42b55d9344642170aa2b/Tools/msi/bundle/bootstrap/PythonBootstrapperApplication.cpp#L651-L678
The majority of the code changes will be in https://github.com/conda/conda/blob/f055f667b54fd76562b5beda4d2d7df620a59363/conda/core/initialize.py.  The docstring at the top of the module describes the general strategy of how `conda.core.initialize` works.

```
Sections in this module are
  1. top-level functions
  2. plan creators
  3. plan runners
  4. individual operations
  5. helper functions
The top-level functions compose and execute full plans.
A plan is created by composing various individual operations.  The plan data structure is a
list of dicts, where each dict represents an individual operation.  The dict contains two
keys--`function` and `kwargs`--where function is the name of the individual operation function
within this module.
Each individual operation must
  a) return a `Result` (i.e. NEEDS_SUDO, MODIFIED, or NO_CHANGE)
  b) have no side effects if context.dry_run is True
  c) be verbose and descriptive about the changes being made or proposed is context.verbosity >= 1
The plan runner functions take the plan (list of dicts) as an argument, and then coordinate the
execution of each individual operation.  The docstring for `run_plan_elevated()` has details on
how that strategy is implemented.
```

Tests are in the associated https://github.com/conda/conda/blob/f055f667b54fd76562b5beda4d2d7df620a59363/tests/core/test_initialize.py

Resolving patch:
diff --git a/conda/core/initialize.py b/conda/core/initialize.py
--- a/conda/core/initialize.py
+++ b/conda/core/initialize.py
@@ -47,7 +47,7 @@
 from .._vendor.auxlib.ish import dals
 from ..activate import CshActivator, FishActivator, PosixActivator, XonshActivator
 from ..base.context import context
-from ..common.compat import (PY2, ensure_binary, ensure_fs_path_encoding, ensure_unicode, on_mac,
+from ..common.compat import (PY2, ensure_binary, ensure_fs_path_encoding, ensure_text_type, on_mac,
                              on_win, open)
 from ..common.path import (expand, get_bin_directory_short_path, get_python_short_path,
                            get_python_site_packages_short_path, win_path_ok)
@@ -453,6 +453,15 @@ def make_initialize_plan(conda_prefix, shells, for_user, for_system, anaconda_pr
                     'conda_prefix': conda_prefix,
                 },
             })
+            # it would be nice to enable this on a user-level basis, but unfortunately, it is
+            #    a system-level key only.
+            plan.append({
+                'function': init_long_path.__name__,
+                'kwargs': {
+                    'target_path': 'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\'
+                                   'FileSystem\\LongPathsEnabled'
+                }
+            })
         if anaconda_prompt:
             plan.append({
                 'function': install_anaconda_prompt.__name__,
@@ -529,7 +538,8 @@ def run_plan_elevated(plan):
                           file=sys.stderr)
 
                 with open(temp_path) as fh:
-                    _plan = json.loads(ensure_unicode(fh.read()))
+                    _plan = json.loads(ensure_text_type(fh.read()))
+
             finally:
                 if temp_path and lexists(temp_path):
                     rm_rf(temp_path)
@@ -560,7 +570,7 @@ def run_plan_from_stdin():
 
 def run_plan_from_temp_file(temp_path):
     with open(temp_path) as fh:
-        plan = json.loads(ensure_unicode(fh.read()))
+        plan = json.loads(ensure_text_type(fh.read()))
     run_plan(plan)
     with open(temp_path, 'w+b') as fh:
         fh.write(ensure_binary(json.dumps(plan, ensure_ascii=False)))
@@ -1107,6 +1117,29 @@ def init_cmd_exe_registry(target_path, conda_prefix):
         return Result.NO_CHANGE
 
 
+def init_long_path(target_path):
+    win_ver, _, win_rev = context.os_distribution_name_version[1].split('.')
+    # win10, build 14352 was the first preview release that supported this
+    if int(win_ver) >= 10 and int(win_rev) >= 14352:
+        prev_value, value_type = _read_windows_registry(target_path)
+        if prev_value != "1":
+            if context.verbosity:
+                print('\n')
+                print(target_path)
+                print(make_diff(prev_value, "1"))
+            if not context.dry_run:
+                _write_windows_registry(target_path, "1", winreg.REG_DWORD)
+            return Result.MODIFIED
+        else:
+            return Result.NO_CHANGE
+    else:
+        if context.verbosity:
+            print('\n')
+            print('Not setting long path registry key; Windows version must be at least 10 with '
+                  'the fall 2016 "Anniversary update" or newer.')
+            return Result.NO_CHANGE
+
+
 def remove_conda_in_sp_dir(target_path):
     # target_path: site_packages_dir
     modified = False


Test patch:
diff --git a/tests/core/test_initialize.py b/tests/core/test_initialize.py
--- a/tests/core/test_initialize.py
+++ b/tests/core/test_initialize.py
@@ -274,6 +274,8 @@ def test_make_initialize_plan_cmd_exe(self):
             assert len(steps) == 2
             steps = tuple(step for step in plan if step['function'] == 'install_anaconda_prompt')
             assert len(steps) == 2
+            steps = tuple(step for step in plan if step['function'] == 'init_long_path')
+            assert len(steps) == 1
 
     def test_make_entry_point(self):
         with tempdir() as conda_temp_prefix:
@@ -770,6 +772,34 @@ def _read_windows_registry_mock(target_path):
         expected = "echo hello & \"c:\\Users\\Lars\\miniconda\\condacmd\\conda_hook.bat\" & echo \"world\""
         assert c.stdout.strip().splitlines()[-1][1:] == expected
 
+    @pytest.mark.skipif(not on_win, reason="win-only test")
+    def test_init_enable_long_path(self):
+        self.dummy_value = "0"
+
+        def _read_windows_registry_mock(target_path):
+            return self.dummy_value, "REG_DWORD"
+
+        def _write_windows_registry_mock(target_path, value, dtype):
+            self.dummy_value = value
+
+        from conda.core import initialize
+        orig_read_windows_registry = initialize._read_windows_registry
+        initialize._read_windows_registry = _read_windows_registry_mock
+        orig_write_windows_registry = initialize._write_windows_registry
+        initialize._write_windows_registry = _write_windows_registry_mock
+        orig_join = initialize.join
+        initialize.join = ntpath.join
+
+        try:
+            target_path = r'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\LongPathsEnabled'
+            assert initialize._read_windows_registry(target_path)[0] == "0"
+            initialize.init_long_path(target_path)
+            assert initialize._read_windows_registry(target_path)[0] == "1"
+        finally:
+            initialize._read_windows_registry = orig_read_windows_registry
+            initialize._write_windows_registry = orig_write_windows_registry
+            initialize.join = orig_join
+
     def test_init_sh_system(self):
         with tempdir() as td:
             target_path = join(td, 'conda.sh')