Repository: conan-io/conan
Pull request #17518 (2024-12-22 20:51:16)

Make profile show useful to aggregate profiles
Changelog: Feature: `conan profile show` can now select which context's profile to show
Changelog: Fix: `conan profile show` does not pollute stdout with information titles
Docs: Omit

Close https://github.com/conan-io/conan/issues/11155



Patch:
diff --git a/conan/cli/commands/profile.py b/conan/cli/commands/profile.py
index c188d9c00..16457a164 100644
--- a/conan/cli/commands/profile.py
+++ b/conan/cli/commands/profile.py
@@ -9,35 +9,44 @@ from conan.errors import ConanException
 from conans.util.files import save
 
 
-def print_profiles(profiles):
-    host, build = profiles
-    cli_out_write("Host profile:")
-    cli_out_write(host.dumps())
-    cli_out_write("Build profile:")
-    cli_out_write(build.dumps())
+def _print_profiles(profiles):
+    if "host" in profiles:
+        ConanOutput().info("Host profile:")
+        cli_out_write(profiles["host"].dumps())
+    if "build" in profiles:
+        ConanOutput().info("Build profile:")
+        cli_out_write(profiles["build"].dumps())
 
 
 def profiles_list_cli_output(profiles):
-    cli_out_write("Profiles found in the cache:")
+    ConanOutput().info("Profiles found in the cache:")
     for p in profiles:
         cli_out_write(p)
 
 
-def json_profiles(profiles):
-    host, build = profiles
-    result = {"host": host.serialize(),
-              "build": build.serialize()}
+def _json_profiles(profiles):
+    result = {}
+    if "host" in profiles:
+        result["host"] = profiles["host"].serialize()
+    if "build" in profiles:
+        result["build"] = profiles["build"].serialize()
     cli_out_write(json.dumps(result))
 
 
-@conan_subcommand(formatters={"text": print_profiles, "json": json_profiles})
+@conan_subcommand(formatters={"text": _print_profiles, "json": _json_profiles})
 def profile_show(conan_api, parser, subparser, *args):
     """
     Show aggregated profiles from the passed arguments.
     """
     add_profiles_args(subparser)
+    subparser.add_argument("-cx", "--context", choices=["host", "build"])
     args = parser.parse_args(*args)
-    result = conan_api.profiles.get_profiles_from_args(args)
+    profiles = conan_api.profiles.get_profiles_from_args(args)
+    result = {}
+    if not args.context or args.context == "host":
+        result["host"] = profiles[0]
+    if not args.context or args.context == "build":
+        result["build"] = profiles[1]
     return result
 
 


Test patch:
diff --git a/test/functional/test_profile_detect_api.py b/test/functional/test_profile_detect_api.py
index 077092f3d..22ed57578 100644
--- a/test/functional/test_profile_detect_api.py
+++ b/test/functional/test_profile_detect_api.py
@@ -30,11 +30,10 @@ class TestProfileDetectAPI:
             """)
 
         client.save({"profile1": tpl1})
-        client.run("profile show -pr=profile1")
+        client.run("profile show -pr=profile1 --context=host")
         #FIXME: check update setting
         update = str(int(detect_api.detect_msvc_update("194")) % 10)
         expected = textwrap.dedent(f"""\
-            Host profile:
             [settings]
             compiler=msvc
             compiler.cppstd=14
@@ -63,13 +62,12 @@ class TestProfileDetectAPI:
             """)
 
         client.save({"profile1": tpl1})
-        client.run("profile show -pr=profile1")
+        client.run("profile show -pr=profile1 --context=host")
         libc_name, libc_version = detect_api.detect_libc()
         assert libc_name is not None
         assert libc_version is not None
         _, version, _ = detect_api.detect_gcc_compiler()
         expected = textwrap.dedent(f"""\
-            Host profile:
             [settings]
             compiler=gcc
             compiler.version={version}
@@ -93,3 +91,27 @@ class TestProfileDetectAPI:
         client.run("profile show -pr=profile1")
         sdk_version = detect_api.detect_sdk_version(sdk="macosx")
         assert f"os.sdk_version={sdk_version}" in client.out
+
+@pytest.mark.parametrize("context", [None, "host", "build"])
+@pytest.mark.parametrize("f", ["json", "text"])
+def test_profile_show_aggregate_usecase(context, f):
+    tc = TestClient(light=True)
+
+    context_arg = f"--context {context}" if context else ""
+    tc.run(f'profile show {context_arg} -s:h="os=Windows" -s:b="os=Linux" --format={f}')
+
+    if context == "host":
+        if f == "text":
+            assert "Host profile:" not in tc.stdout
+            assert "Host profile:" in tc.stderr
+        assert "Linux" not in tc.out
+    if context == "build":
+        if f == "text":
+            assert "Build profile:" not in tc.stdout
+            assert "Build profile:" in tc.stderr
+        assert "Windows" not in tc.out
+
+    if context in (None, "host"):
+        assert "Windows" in tc.out
+    if context in (None, "build"):
+        assert "Linux" in tc.out
diff --git a/test/integration/configuration/test_profile_jinja.py b/test/integration/configuration/test_profile_jinja.py
index 5b4a67fa7..a985f992c 100644
--- a/test/integration/configuration/test_profile_jinja.py
+++ b/test/integration/configuration/test_profile_jinja.py
@@ -241,12 +241,11 @@ class TestProfileDetectAPI:
             """)
 
         client.save({"profile1": tpl1})
-        client.run("profile show -pr=profile1")
+        client.run("profile show -pr=profile1 --context=host")
         pr = client.get_default_host_profile()
         the_os = pr.settings['os']
         arch = pr.settings['arch']
         expected = textwrap.dedent(f"""\
-            Host profile:
             [settings]
             arch={arch}
             os={the_os}