Repository: conan-io/conan
Pull request: [dev2][package_id] Part 3: Mechanism to add some `conf`s to `package_id` computation
Changelog: Feature: Added mechanism to include some configurations into `package_id` computation via `tools.info.package_id:confs` one.
Docs: omit
Closes: https://github.com/conan-io/conan/issues/11163

Problem statement:
[feature] [package_id] Improve  `ConanInfo` interface to add new `conf` values
Improve the way to add new configurations from `conf` to `ConanInfo.package_id()`. Some proposals:

* Via `[conf]`: having a new configuration to indicate all the configurations to be added like `core.package_id:add=["tools.build:cxxflags"]`
* Via `conanfile.py`: having something like this code:
```python
def package_id():
     self.info.conf.add("tools.build:cxxflags")
```
* ....

Hints from issue discussion:

Commit message: Applying different confs to package ID computation

Patch to review:
diff --git a/conans/client/graph/compute_pid.py b/conans/client/graph/compute_pid.py
index 0d216c8b345..9dfa82f5d5b 100644
--- a/conans/client/graph/compute_pid.py
+++ b/conans/client/graph/compute_pid.py
@@ -3,6 +3,7 @@
 from conans.client.graph.graph import BINARY_INVALID, BINARY_ERROR
 from conans.errors import conanfile_exception_formatter, ConanInvalidConfiguration, \
     ConanErrorConfiguration, conanfile_remove_attr
+from conans.model.conf import Conf
 from conans.model.info import ConanInfo, RequirementsInfo, RequirementInfo, PythonRequiresInfo
 
 
@@ -18,6 +19,7 @@ def compute_package_id(node, new_config):
     embed_mode = new_config.get("core.package_id:default_embed_mode", default="full_mode")
     python_mode = new_config.get("core.package_id:default_python_mode", default="minor_mode")
     build_mode = new_config.get("core.package_id:default_build_mode", default=None)
+    conf_names = new_config.get("core.package_id:confs", default=[], check_type=list)
 
     python_requires = getattr(conanfile, "python_requires", None)
     if python_requires:
@@ -40,11 +42,19 @@ def compute_package_id(node, new_config):
     build_requires_info = RequirementsInfo(build_data)
     python_requires = PythonRequiresInfo(python_requires, python_mode)
 
-    conanfile.info = ConanInfo(conanfile.settings.copy_conaninfo_settings(),
-                               conanfile.options.copy_conaninfo_options(),
-                               reqs_info,
-                               build_requires_info,
-                               python_requires=python_requires)
+    # Creating Conf() object with only the configurations defined by core.package_id:confs
+    conf = Conf()
+    for conf_name in conf_names:
+        value = conanfile.conf.get(conf_name)
+        if value is not None:
+            conf.define(conf_name, value)
+
+    conanfile.info = ConanInfo(settings=conanfile.settings.copy_conaninfo_settings(),
+                               options=conanfile.options.copy_conaninfo_options(),
+                               reqs_info=reqs_info,
+                               build_requires_info=build_requires_info,
+                               python_requires=python_requires,
+                               conf=conf)
     conanfile.original_info = conanfile.info.clone()
 
     run_validate_package_id(conanfile)
diff --git a/conans/model/conf.py b/conans/model/conf.py
index cf56e06fc36..2011d2c7622 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -8,7 +8,6 @@
 BUILT_IN_CONFS = {
     "core:required_conan_version": "Raise if current version does not match the defined range.",
     "core:non_interactive": "Disable interactive user input, raises error if input necessary",
-    "core.package_id:msvc_visual_incompatible": "Allows opting-out the fallback from the new msvc compiler to the Visual Studio compiler existing binaries",
     "core:default_profile": "Defines the default host profile ('default' by default)",
     "core:default_build_profile": "Defines the default build profile (None by default)",
     "core.upload:retry": "Number of retries in case of failure when uploading to Conan server",
@@ -16,6 +15,13 @@
     "core.download:parallel": "Number of concurrent threads to download packages",
     "core.download:retry": "Number of retries in case of failure when downloading from Conan server",
     "core.download:retry_wait": "Seconds to wait between download attempts from Conan server",
+    # Package ID related
+    "core.package_id:confs": "List of configurations that should be included as part of the package_id, e.g., 'tools.build:cxxflags'",
+    "core.package_id:default_unknown_mode": "",
+    "core.package_id:default_non_embed_mode": "",
+    "core.package_id:default_embed_mode": "",
+    "core.package_id:default_python_mode": "",
+    "core.package_id:default_build_mode": "",
     # General HTTP(python-requests) configuration
     "core.net.http:max_retries": "Maximum number of connection retries (requests library)",
     "core.net.http:timeout": "Number of seconds without response to timeout (requests library)",
diff --git a/conans/model/info.py b/conans/model/info.py
index 4a7ebf03c0f..96e31f7dbdf 100644
--- a/conans/model/info.py
+++ b/conans/model/info.py
@@ -379,13 +379,14 @@ def _loads_settings(settings_text):
 class ConanInfo:
 
     def __init__(self, settings=None, options=None, reqs_info=None, build_requires_info=None,
-                 python_requires=None):
+                 python_requires=None, conf=None):
         self.invalid = None
         self.settings = settings
         self.options = options
         self.requires = reqs_info
         self.build_requires = build_requires_info
         self.python_requires = python_requires
+        self.conf = conf
 
     def clone(self):
         """ Useful for build_id implementation and for compatibility()
@@ -397,6 +398,7 @@ def clone(self):
         result.requires = self.requires.copy()
         result.build_requires = self.build_requires.copy()
         result.python_requires = self.python_requires.copy()
+        result.conf = self.conf.copy()
         return result
 
     def dumps(self):
@@ -406,6 +408,8 @@ def dumps(self):
         :return: `str` with the result of joining all the information, e.g.,
             `"[settings]\nos=Windows\n[options]\n[requires]\n"`
         """
+        # FIXME: Refactor this method to not include headers with empty content. It'll break
+        #        one or two tests...
         result = ["[settings]"]
         settings_dumps = self.settings.dumps()
         if settings_dumps:
@@ -424,7 +428,9 @@ def dumps(self):
         if self.build_requires:
             result.append("[build_requires]")
             result.append(self.build_requires.dumps())
-        if hasattr(self, "conf"):
+        conf_dumps = self.conf.dumps()
+        if conf_dumps:
+            # result.append("[conf]")  # Let's do it when apply FIXME above
             result.append(self.conf.dumps())
         result.append("")  # Append endline so file ends with LF
         return '\n'.join(result)
diff --git a/conans/test/integration/package_id/package_id_and_confs_test.py b/conans/test/integration/package_id/package_id_and_confs_test.py
new file mode 100644
index 00000000000..21eb65bccaf
--- /dev/null
+++ b/conans/test/integration/package_id/package_id_and_confs_test.py
@@ -0,0 +1,27 @@
+import textwrap
+
+from conans.test.utils.tools import TestClient
+
+
+def test_package_id_include_confs():
+    client = TestClient()
+    client.save({"global.conf": 'core.package_id:confs=["tools.build:cxxflags", "tools.build:cflags"]'},
+                path=client.cache.cache_folder)
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            settings = "os"
+        """)
+    profile = textwrap.dedent("""
+    include(default)
+    [conf]
+    tools.build:cxxflags=["--flag1", "--flag2"]
+    tools.build:cflags+=["--flag3", "--flag4"]
+    """)
+    client.save({"conanfile.py": conanfile, "profile": profile})
+    client.run('create . --name=pkg --version=0.1 -s os=Windows -pr profile')
+    # client.assert_listed_binary({"pkg/0.1": ("115c314122246da287f43c08de5eeab316596038",
+    #                                          "Build")})
+    # client.run('install --requires=pkg/0.1@ -s os=Windows -s compiler=msvc '
+    #            '-s compiler.version=190 -s build_type=Debug -s compiler.runtime=dynamic')
+    # client.assert_listed_binary({"pkg/0.1": ("115c314122246da287f43c08de5eeab316596038", "Cache")})


Code review comments:
diff_hunk: @@ -18,6 +19,7 @@ def compute_package_id(node, new_config):
     embed_mode = new_config.get("core.package_id:default_embed_mode", default="full_mode")
     python_mode = new_config.get("core.package_id:default_python_mode", default="minor_mode")
     build_mode = new_config.get("core.package_id:default_build_mode", default=None)
+    conf_names = new_config.get("core.package_id:confs", default=[], check_type=list)
path: conans/client/graph/compute_pid.py
text: @user1:
Not sure if we want this to be ``core``, or it would be a ``profile`` one, something that users would prefer to define in the ``profiles``?

@author:
Yes, that was what I was trying to figure out. I opened the draft to see how it looked like, and, definitely, it should go through the user profile 😉

diff_hunk: @@ -8,14 +8,20 @@
 BUILT_IN_CONFS = {
     "core:required_conan_version": "Raise if current version does not match the defined range.",
     "core:non_interactive": "Disable interactive user input, raises error if input necessary",
-    "core.package_id:msvc_visual_incompatible": "Allows opting-out the fallback from the new msvc compiler to the Visual Studio compiler existing binaries",
     "core:default_profile": "Defines the default host profile ('default' by default)",
     "core:default_build_profile": "Defines the default build profile (None by default)",
     "core.upload:retry": "Number of retries in case of failure when uploading to Conan server",
     "core.upload:retry_wait": "Seconds to wait between upload attempts to Conan server",
     "core.download:parallel": "Number of concurrent threads to download packages",
     "core.download:retry": "Number of retries in case of failure when downloading from Conan server",
     "core.download:retry_wait": "Seconds to wait between download attempts from Conan server",
+    # Package ID related
path: conans/model/conf.py
text: @user1:
Recall that BUILT_IN_CONFS is no longer used in the whole project. The command ``conan config list`` was removed. Probably we should remove this?