Repository: conan-io/conan

Issue:
[question][suggestion] Listing profile should be recursive
Hello,

I use multiple profiles, and those are organized in subdirectories:
   + `~/.conan/profiles/application/x64_gcc6_app1`
   + `~/.conan/profiles/application/x64_msvc_app1`
   + `~/.conan/profiles/compilers/x64_gcc6`
   + `~/.conan/profiles/compilers/x64_msvc`

The "applications" profile include other profiles etc. This works pretty well, so I assume 
using subdirectory in profiles is supported and is not a problem.

However, the `conan profile list` command does not list profiles contained in subdirectories.
I believe it should recursively search for profile files rather than only list the files available directly in the `~/.conan/profiles` directory.

I'm wondering if there is a particular reason why the search is limited to the `~/.conan/profiles` directory and if you'd be open to changing this behavior.



Discussion:
It completely makes sense, there is no reason to list only the first level files. I will add it to queue, not the highest priority, but it is really easy and an improvement.

Thanks!

Resolving patch:
diff --git a/conans/client/cmd/profile.py b/conans/client/cmd/profile.py
--- a/conans/client/cmd/profile.py
+++ b/conans/client/cmd/profile.py
@@ -21,13 +21,18 @@ def _get_profile_keys(key):
 
 
 def cmd_profile_list(cache_profiles_path, output):
-    folder = cache_profiles_path
-    if os.path.exists(folder):
-        return [name for name in os.listdir(folder)
-                if not os.path.isdir(os.path.join(folder, name))]
-    else:
+    profiles = []
+    if os.path.exists(cache_profiles_path):
+        for current_directory, _, files in os.walk(cache_profiles_path, followlinks=True):
+            for filename in files:
+                rel_path = os.path.relpath(os.path.join(current_directory, filename),
+                                           cache_profiles_path)
+                profiles.append(rel_path)
+
+    if not profiles:
         output.info("No profiles defined")
-        return []
+    profiles.sort()
+    return profiles
 
 
 def cmd_profile_create(profile_name, cache_profiles_path, output, detect=False):


Test patch:
diff --git a/conans/test/functional/command/profile_test.py b/conans/test/functional/command/profile_test.py
--- a/conans/test/functional/command/profile_test.py
+++ b/conans/test/functional/command/profile_test.py
@@ -4,6 +4,7 @@
 from conans.test.utils.profiles import create_profile
 from conans.test.utils.tools import TestClient
 from conans.util.files import load
+import platform
 
 
 class ProfileTest(unittest.TestCase):
@@ -29,14 +30,26 @@ def empty_test(self):
 
     def list_test(self):
         client = TestClient()
-        create_profile(client.cache.profiles_path, "profile3")
-        create_profile(client.cache.profiles_path, "profile1")
-        create_profile(client.cache.profiles_path, "profile2")
+        profiles = ["profile1", "profile2", "profile3",
+                    "nested" + os.path.sep + "profile4",
+                    "nested" + os.path.sep + "two" + os.path.sep + "profile5",
+                    "nested" + os.path.sep + "profile6"]
+        if platform.system() != "Windows":
+            profiles.append("symlink_me" + os.path.sep + "profile7")
+        for profile in profiles:
+            create_profile(client.cache.profiles_path, profile)
+
+        if platform.system() != "Windows":
+            os.symlink(os.path.join(client.cache.profiles_path, 'symlink_me'),
+                       os.path.join(client.cache.profiles_path, 'link'))
+            # profile7 will be shown twice because it is symlinked.
+            profiles.append("link" + os.path.sep + "profile7")
+
         # Make sure local folder doesn't interact with profiles
         os.mkdir(os.path.join(client.current_folder, "profile3"))
         client.run("profile list")
-        self.assertEqual(list(["profile1", "profile2", "profile3"]),
-                         list(str(client.user_io.out).splitlines()))
+        profiles.sort()
+        self.assertEqual(profiles, list(str(client.out).splitlines()))
 
     def show_test(self):
         client = TestClient()