Repository: conda/conda

Issue:
conda clean -t fails with FileNotFoundError
```
[root@localhost conda-recipes]# conda clean -t
An unexpected error has occurred, please consider sending the
following traceback to the conda GitHub issue tracker at:

    https://github.com/conda/conda/issues

Include the output of the command 'conda info' in your report.


Traceback (most recent call last):
  File "/root/anaconda/bin/conda", line 5, in <module>
    sys.exit(main())
  File "/home/aaronmeurer/conda/conda/cli/main.py", line 202, in main
    args_func(args, p)
  File "/home/aaronmeurer/conda/conda/cli/main.py", line 207, in args_func
    args.func(args, p)
  File "/home/aaronmeurer/conda/conda/cli/main_clean.py", line 317, in execute
    pkgs_dir, rmlist, totalsize = find_tarballs()
  File "/home/aaronmeurer/conda/conda/cli/main_clean.py", line 108, in find_tarballs
    for fn in os.listdir(pkgs_dir):
FileNotFoundError: [Errno 2] No such file or directory: '/root/.conda/envs/.pkgs'

```



Discussion:

Resolving patch:
diff --git a/conda/cli/main_clean.py b/conda/cli/main_clean.py
--- a/conda/cli/main_clean.py
+++ b/conda/cli/main_clean.py
@@ -8,6 +8,7 @@
 from argparse import RawDescriptionHelpFormatter
 import os
 import sys
+from collections import defaultdict
 
 from os.path import join, getsize, isdir
 from os import lstat, walk, listdir
@@ -102,29 +103,29 @@ def rm_lock(locks, verbose=True):
 
 
 def find_tarballs():
-    pkgs_dir = config.pkgs_dirs[0]
-
-    rmlist = []
-    for fn in os.listdir(pkgs_dir):
-        if fn.endswith('.tar.bz2') or fn.endswith('.tar.bz2.part'):
-            rmlist.append(fn)
-
-    if not rmlist:
-        return pkgs_dir, rmlist, 0
+    pkgs_dirs = defaultdict(list)
+    for pkgs_dir in config.pkgs_dirs:
+        if not isdir(pkgs_dir):
+            continue
+        for fn in os.listdir(pkgs_dir):
+            if fn.endswith('.tar.bz2') or fn.endswith('.tar.bz2.part'):
+                pkgs_dirs[pkgs_dir].append(fn)
 
     totalsize = 0
-    for fn in rmlist:
-        size = getsize(join(pkgs_dir, fn))
-        totalsize += size
+    for pkgs_dir in pkgs_dirs:
+        for fn in pkgs_dirs[pkgs_dir]:
+            size = getsize(join(pkgs_dir, fn))
+            totalsize += size
 
-    return pkgs_dir, rmlist, totalsize
+    return pkgs_dirs, totalsize
 
 
-def rm_tarballs(args, pkgs_dir, rmlist, totalsize, verbose=True):
+def rm_tarballs(args, pkgs_dirs, totalsize, verbose=True):
     if verbose:
-        print('Cache location: %s' % pkgs_dir)
+        for pkgs_dir in pkgs_dirs:
+            print('Cache location: %s' % pkgs_dir)
 
-    if not rmlist:
+    if not any(pkgs_dirs[i] for i in pkgs_dirs):
         if verbose:
             print("There are no tarballs to remove")
         return
@@ -133,12 +134,15 @@ def rm_tarballs(args, pkgs_dir, rmlist, totalsize, verbose=True):
         print("Will remove the following tarballs:")
         print()
 
-        maxlen = len(max(rmlist, key=lambda x: len(str(x))))
-        fmt = "%-40s %10s"
-        for fn in rmlist:
-            size = getsize(join(pkgs_dir, fn))
-            print(fmt % (fn, human_bytes(size)))
-        print('-' * (maxlen + 2 + 10))
+        for pkgs_dir in pkgs_dirs:
+            print(pkgs_dir)
+            print('-'*len(pkgs_dir))
+            fmt = "%-40s %10s"
+            for fn in pkgs_dirs[pkgs_dir]:
+                size = getsize(join(pkgs_dir, fn))
+                print(fmt % (fn, human_bytes(size)))
+            print()
+        print('-' * 51) # From 40 + 1 + 10 in fmt
         print(fmt % ('Total:', human_bytes(totalsize)))
         print()
 
@@ -147,79 +151,82 @@ def rm_tarballs(args, pkgs_dir, rmlist, totalsize, verbose=True):
     if args.json and args.dry_run:
         return
 
-    for fn in rmlist:
-        if verbose:
-            print("removing %s" % fn)
-        os.unlink(os.path.join(pkgs_dir, fn))
+    for pkgs_dir in pkgs_dirs:
+        for fn in pkgs_dirs[pkgs_dir]:
+            if verbose:
+                print("removing %s" % fn)
+            os.unlink(os.path.join(pkgs_dir, fn))
 
 
 def find_pkgs():
     # TODO: This doesn't handle packages that have hard links to files within
     # themselves, like bin/python3.3 and bin/python3.3m in the Python package
-    pkgs_dir = config.pkgs_dirs[0]
     warnings = []
 
-    rmlist = []
-    pkgs = [i for i in listdir(pkgs_dir) if isdir(join(pkgs_dir, i)) and
-        # Only include actual packages
-        isdir(join(pkgs_dir, i, 'info'))]
-    for pkg in pkgs:
-        breakit = False
-        for root, dir, files in walk(join(pkgs_dir, pkg)):
-            if breakit:
-                break
-            for fn in files:
-                try:
-                    stat = lstat(join(root, fn))
-                except OSError as e:
-                    warnings.append((fn, e))
-                    continue
-                if stat.st_nlink > 1:
-                    # print('%s is installed: %s' % (pkg, join(root, fn)))
-                    breakit = True
+    pkgs_dirs = defaultdict(list)
+    for pkgs_dir in config.pkgs_dirs:
+        pkgs = [i for i in listdir(pkgs_dir) if isdir(join(pkgs_dir, i)) and
+            # Only include actual packages
+            isdir(join(pkgs_dir, i, 'info'))]
+        for pkg in pkgs:
+            breakit = False
+            for root, dir, files in walk(join(pkgs_dir, pkg)):
+                if breakit:
                     break
-        else:
-            rmlist.append(pkg)
-
-    if not rmlist:
-        return pkgs_dir, rmlist, warnings, 0, []
+                for fn in files:
+                    try:
+                        stat = lstat(join(root, fn))
+                    except OSError as e:
+                        warnings.append((fn, e))
+                        continue
+                    if stat.st_nlink > 1:
+                        # print('%s is installed: %s' % (pkg, join(root, fn)))
+                        breakit = True
+                        break
+            else:
+                pkgs_dirs[pkgs_dir].append(pkg)
 
     totalsize = 0
-    pkgsizes = []
-    for pkg in rmlist:
-        pkgsize = 0
-        for root, dir, files in walk(join(pkgs_dir, pkg)):
-            for fn in files:
-                # We don't have to worry about counting things twice:  by
-                # definition these files all have a link count of 1!
-                size = lstat(join(root, fn)).st_size
-                totalsize += size
-                pkgsize += size
-        pkgsizes.append(pkgsize)
-
-    return pkgs_dir, rmlist, warnings, totalsize, pkgsizes
-
-
-def rm_pkgs(args, pkgs_dir, rmlist, warnings, totalsize, pkgsizes,
+    pkgsizes = defaultdict(list)
+    for pkgs_dir in pkgs_dirs:
+        for pkg in pkgs_dirs[pkgs_dir]:
+            pkgsize = 0
+            for root, dir, files in walk(join(pkgs_dir, pkg)):
+                for fn in files:
+                    # We don't have to worry about counting things twice:  by
+                    # definition these files all have a link count of 1!
+                    size = lstat(join(root, fn)).st_size
+                    totalsize += size
+                    pkgsize += size
+            pkgsizes[pkgs_dir].append(pkgsize)
+
+    return pkgs_dirs, warnings, totalsize, pkgsizes
+
+
+def rm_pkgs(args, pkgs_dirs, warnings, totalsize, pkgsizes,
             verbose=True):
     if verbose:
-        print('Cache location: %s' % pkgs_dir)
-        for fn, exception in warnings:
-            print(exception)
+        for pkgs_dir in pkgs_dirs:
+            print('Cache location: %s' % pkgs_dir)
+            for fn, exception in warnings:
+                print(exception)
 
-    if not rmlist:
+    if not any(pkgs_dirs[i] for i in pkgs_dirs):
         if verbose:
             print("There are no unused packages to remove")
         return
 
     if verbose:
         print("Will remove the following packages:")
-        print()
-        maxlen = len(max(rmlist, key=lambda x: len(str(x))))
-        fmt = "%-40s %10s"
-        for pkg, pkgsize in zip(rmlist, pkgsizes):
-            print(fmt % (pkg, human_bytes(pkgsize)))
-        print('-' * (maxlen + 2 + 10))
+        for pkgs_dir in pkgs_dirs:
+            print(pkgs_dir)
+            print('-' * len(pkgs_dir))
+            print()
+            fmt = "%-40s %10s"
+            for pkg, pkgsize in zip(pkgs_dirs[pkgs_dir], pkgsizes[pkgs_dir]):
+                print(fmt % (pkg, human_bytes(pkgsize)))
+            print()
+        print('-' * 51) # 40 + 1 + 10 in fmt
         print(fmt % ('Total:', human_bytes(totalsize)))
         print()
 
@@ -228,10 +235,11 @@ def rm_pkgs(args, pkgs_dir, rmlist, warnings, totalsize, pkgsizes,
     if args.json and args.dry_run:
         return
 
-    for pkg in rmlist:
-        if verbose:
-            print("removing %s" % pkg)
-        rm_rf(join(pkgs_dir, pkg))
+    for pkgs_dir in pkgs_dirs:
+        for pkg in pkgs_dirs[pkgs_dir]:
+            if verbose:
+                print("removing %s" % pkg)
+            rm_rf(join(pkgs_dir, pkg))
 
 
 def rm_index_cache():
@@ -314,13 +322,15 @@ def execute(args, parser):
         rm_lock(locks, verbose=not args.json)
 
     if args.tarballs:
-        pkgs_dir, rmlist, totalsize = find_tarballs()
+        pkgs_dirs, totalsize = find_tarballs()
+        first = sorted(pkgs_dirs)[0] if pkgs_dirs else ''
         json_result['tarballs'] = {
-            'pkgs_dir': pkgs_dir,
-            'files': rmlist,
+            'pkgs_dir': first, # Backwards compabitility
+            'pkgs_dirs': dict(pkgs_dirs),
+            'files': pkgs_dirs[first], # Backwards compatibility
             'total_size': totalsize
         }
-        rm_tarballs(args, pkgs_dir, rmlist, totalsize, verbose=not args.json)
+        rm_tarballs(args, pkgs_dirs, totalsize, verbose=not args.json)
 
     if args.index_cache:
         json_result['index_cache'] = {
@@ -329,15 +339,17 @@ def execute(args, parser):
         rm_index_cache()
 
     if args.packages:
-        pkgs_dir, rmlist, warnings, totalsize, pkgsizes = find_pkgs()
+        pkgs_dirs, warnings, totalsize, pkgsizes = find_pkgs()
+        first = sorted(pkgs_dirs)[0] if pkgs_dirs else ''
         json_result['packages'] = {
-            'pkgs_dir': pkgs_dir,
-            'files': rmlist,
+            'pkgs_dir': first, # Backwards compatibility
+            'pkgs_dirs': dict(pkgs_dirs),
+            'files': pkgs_dirs[first], # Backwards compatibility
             'total_size': totalsize,
             'warnings': warnings,
-            'pkg_sizes': dict(zip(rmlist, pkgsizes))
+            'pkg_sizes': {i: dict(zip(pkgs_dirs[i], pkgsizes[i])) for i in pkgs_dirs},
         }
-        rm_pkgs(args, pkgs_dir, rmlist, warnings, totalsize, pkgsizes,
+        rm_pkgs(args, pkgs_dirs,  warnings, totalsize, pkgsizes,
                 verbose=not args.json)
 
     if args.source_cache:


Test patch: