Repository: conan-io/conan
Pull request: Alternative way of setting properties that depend on generator specific features.
Changelog: Feature: Add `set_property` and `get_property` to set properties and access them in generators. Can be set only for a specific generator or as a default value for all of them.
Changelog: Feature: Use `set_property` and `get_property` to support custom defined content in `pkg_config` generator.
Changelog: Feature: Add new property names: `cmake_target_name`, `cmake_file_name`, `pkg_config_name` and `cmake_build_modules` that can be used for multiple generators of the same type allowing also an easier migration of `names`, `filenames` and `build_modules` properties to this model.
Docs: https://github.com/conan-io/docs/pull/2082

Closes: https://github.com/conan-io/conan/issues/7661 , https://github.com/conan-io/conan/issues/8600

Set properties of the _cpp_info_ that are related to generators (things like names, filenames, build_modules...) with `set_property` and `get_property`. Tests with `filenames`, `names` and `build_modules` properties.
Using new properties names `cmake_target_name`, `cmake_file_name`, `pkg_config_name` and `cmake_build_modules` will make the migration a little bit less risky. The new property name will be given preferences over the old one.

This:

``` python
self.cpp_info.components["mycomponent"].names["cmake_find_package"] = "mycomponent-name"
self.cpp_info.components["mycomponent"].names["cmake_find_package_multi"] = "mycomponent-name"
```
would be expressed as this:

``` python
self.cpp_info.components["mycomponent"].set_property("cmake_target_name", "mycomponent-name")
```

It's also possible to make it specific for generator but that most of cases it won't be necessary:

``` python
self.cpp_info.components["mycomponent"].set_property("cmake_target_name", "mypkg-name", "cmake_find_package")
```

Also, not specifying the generator will set this value as a default for every generator. So if you set:

``` python
self.cpp_info.components["mycomponent"].set_property("my-property", "mycomponent-name")
```

A new custom generator can access that information with:

``` python
for pkg_name, cpp_info in self.deps_build_info.dependencies:
    names = cpp_info.get_property("my-property", generator=self.name))
```

Related to: https://github.com/conan-io/conan/issues/8600, https://github.com/conan-io/conan/pull/8568 and https://github.com/conan-io/conan/issues/7661

#tags: slow

Problem statement:
[feature] Allow arbitrary sections in pkg_config .pc files
pkg_config files can contain extra information, next to include paths/library paths/libs/cflags/requires/...
This is used by some programs to pass around configuration paths.

Because the generated .pc files by conan, are missing this information, configuration fails.

e.g. glib uses this. When using the files generated by conan, the following error appears:
```
Run-time dependency gio-2.0 found: YES 2.65.1
Dependency gio-2.0 found: YES 2.65.1 (cached)
Using 'PKG_CONFIG_PATH' from environment with value: '/home/maarten/.conan/data/glib-networking/2.65.1/_/_/build/eac566419e1470979e589f72ef984a2778d4785a'
Run-time dependency gmodule-2.0 found: YES 2.65.1
WARNING: pkgconfig variable 'giomoduledir' not defined for dependency gio-2.0.

source_subfolder/meson.build:68:0: ERROR: Assert failed: GIO_MODULE_DIR is missing from gio-2.0.pc
```

The `gio-2.0.pc` file, installed in my system contains:
```
prefix=/usr
libdir=${prefix}/lib64
includedir=${prefix}/include

datadir=${prefix}/share
schemasdir=${datadir}/glib-2.0/schemas
bindir=${prefix}/bin
giomoduledir=${libdir}/gio/modules
glib_compile_schemas=${bindir}/glib-compile-schemas
glib_compile_resources=${bindir}/glib-compile-resources
gdbus_codegen=${bindir}/gdbus-codegen

Name: GIO
Description: glib I/O library
Version: 2.60.7
Requires: glib-2.0, gobject-2.0
Requires.private: gmodule-no-export-2.0, zlib, mount >= 2.23, libselinux
Libs: -L${libdir} -lgio-2.0
Libs.private: -ldl -pthread -lresolv
Cflags: -I${includedir}
[maarten@localhost eac5664
```

The generated `gio-2.0.pc` contains:
```
prefix=/home/maarten/.conan/data/glib/2.65.1/_/_/package/f9b484c68439b8cf20fdfbb3f876d6cf74a4f970
libdir=${prefix}/lib
includedir=${prefix}/include

Name: glib-gio-2.0
Description: Conan package: glib-gio-2.0
Version: 2.65.1
Libs: -L${libdir} -lgio-2.0  -lresolv  -ldl  -Wl,-rpath="${libdir}"
Cflags: -I${includedir}
Requires: glib-2.0 gobject-2.0 gmodule-2.0 zlib mount libselinux
```

The generated pc file is missing a lot of keys, of which some are custom.
So it would be useful to add custom key/values to pkg_config files.

This feature is used by glib/glib-networking.

Hints from issue discussion:
this blocks https://github.com/conan-io/conan-center-index/pull/3663
This prevents generation of proper .pc files like the gst-plugins-base's gstreamer-video-1.0.pc 
another example where it is needed: https://github.com/GNOME/gobject-introspection/blob/master/meson.build#L243
Thanks for the feedback. I am trying to understand the scope of this information, and why would it be exclusive to the .pc files. How is CMake dealing with this library, is it even possible to consume it at all without using those .pc files? How CMake maps those ``giomoduledir=${libdir}/gio/modules, glib_compile_schemas=${bindir}/glib-compile-schemas, glib_compile_resources=${bindir}/glib-compile-resources`` variables?  

Because the recipe in ConanCenter maybe could add one of the new ``build_modules`` specific for the ``pkgconfig`` generator, though we would need a way to inject it, I think this is done at the moment only by the CMake generators.
regarding gobject-introspection, the information is probably never consumed from cmake : https://ubuntu.pkgs.org/20.10/ubuntu-main-amd64/libgirepository1.0-dev_1.66.1-1_amd64.deb.html
Wayland-protocols too: https://ubuntu.pkgs.org/20.10/ubuntu-main-arm64/wayland-protocols_1.20-1_all.deb.html
gst-plugins-base too: https://ubuntu.pkgs.org/20.04/ubuntu-main-amd64/libgstreamer-plugins-base1.0-dev_1.16.2-4_amd64.deb.html
glib too: https://ubuntu.pkgs.org/20.04/ubuntu-main-amd64/libglib2.0-dev_2.64.2-1~fakesync1_amd64.deb.html

On the other hand, the pkg-config variables can be consumed from cmake (via pkg-config) using https://cmake.org/cmake/help/latest/module/FindPkgConfig.html#command:pkg_get_variable

EDIT: I don't think `build_modules` is a proper solution to this problem, because we need to add simple key-value pairs to .pc files already generated by conan, not add new .pc files to existing recipes. Something like `self.cpp_info.variables['pkg_config'].variable_name = "variable value"`. The values are always simple strings, because pkg-config has its own expansion system

Commit message: remove argument

Patch to review:
diff --git a/conans/client/generators/pkg_config.py b/conans/client/generators/pkg_config.py
index 079d354d09e..1a6e4ef7d48 100644
--- a/conans/client/generators/pkg_config.py
+++ b/conans/client/generators/pkg_config.py
@@ -81,6 +81,10 @@ def _pc_file_content(self, name, cpp_info, requires_gennames):
             includedir_vars = varnames
             lines.extend(dir_lines)
 
+        custom_content = cpp_info.get_property("custom_content", self.name)
+        if custom_content:
+            lines.append(custom_content)
+
         lines.append("")
         lines.append("Name: %s" % name)
         description = cpp_info.description or "Conan package: %s" % name
diff --git a/conans/model/build_info.py b/conans/model/build_info.py
index 88fc0cd3e90..20a25bd218e 100644
--- a/conans/model/build_info.py
+++ b/conans/model/build_info.py
@@ -104,6 +104,7 @@ class _CppInfo(object):
 
     def __init__(self):
         self._name = None
+        self._generator_properties = {}
         self.names = {}
         self.system_libs = []  # Ordered list of system libraries
         self.includedirs = []  # Ordered list of include paths
@@ -150,11 +151,11 @@ def _filter_paths(self, paths):
     @property
     def build_modules_paths(self):
         if self._build_modules_paths is None:
-            if isinstance(self.build_modules, list):  # FIXME: This should be just a plain dict
+            if isinstance(self.get_build_modules(), list):  # FIXME: This should be just a plain dict
                 conan_v2_error("Use 'self.cpp_info.build_modules[\"<generator>\"] = "
-                               "{the_list}' instead".format(the_list=self.build_modules))
-                self.build_modules = BuildModulesDict.from_list(self.build_modules)
-            tmp = dict_to_abs_paths(BuildModulesDict(self.build_modules), self.rootpath)
+                               "{the_list}' instead".format(the_list=self.get_build_modules()))
+                self.build_modules = BuildModulesDict.from_list(self.get_build_modules())
+            tmp = dict_to_abs_paths(BuildModulesDict(self.get_build_modules()), self.rootpath)
             self._build_modules_paths = tmp
         return self._build_modules_paths
 
@@ -209,15 +210,49 @@ def name(self):
     def name(self, value):
         self._name = value
 
+    # TODO: Deprecate for 2.0. Only cmake and pkg_config generators should access this.
+    #  Use get_property for 2.0
     def get_name(self, generator):
-        return self.names.get(generator, self._name)
+        property_name = "cmake_target_name" if "cmake" in generator else "pkg_config_name"
+        return self.get_property(property_name, generator) or self.names.get(generator, self._name)
 
+    # TODO: Deprecate for 2.0. Only cmake generators should access this. Use get_property for 2.0
     def get_filename(self, generator):
-        result = self.filenames.get(generator)
+        result = self.get_property("cmake_file_name", generator) or self.filenames.get(generator)
         if result:
             return result
         return self.get_name(generator)
 
+    # TODO: Deprecate for 2.0. Use get_property for 2.0
+    def get_build_modules(self):
+        default_values_dict = self._generator_properties.get("conan_default_generators_value")
+        default_build_modules_value = default_values_dict.get("cmake_build_modules") if default_values_dict else None
+        ret_dict = {"cmake_find_package": default_build_modules_value,
+                    "cmake_find_package_multi": default_build_modules_value,
+                    "cmake": default_build_modules_value,
+                    "cmake_multi": default_build_modules_value} if default_build_modules_value else {}
+        for generator, values in self._generator_properties.items():
+            if values.get("cmake_build_modules") and generator != "conan_default_generators_value":
+                ret_dict[generator] = values.get("cmake_build_modules")
+        return ret_dict if ret_dict else self.build_modules
+
+    def set_property(self, property_name, value, generator=None):
+        generator = generator or "conan_default_generators_value"
+        gen_dict = self._generator_properties.get(generator)
+        if gen_dict:
+            gen_dict.update({property_name: value})
+        else:
+            self._generator_properties.update({generator: {property_name: value}})
+
+    def get_property(self, property_name, generator=None):
+        generator = generator or "conan_default_generators_value"
+        gen_dict = self._generator_properties.get(generator)
+        if gen_dict and gen_dict.get(property_name):
+            return gen_dict.get(property_name)
+        else:
+            gen_dict = self._generator_properties.get("conan_default_generators_value")
+            return gen_dict.get(property_name) if gen_dict else None
+
     # Compatibility for 'cppflags' (old style property to allow decoration)
     def get_cppflags(self):
         conan_v2_error("'cpp_info.cppflags' is deprecated, use 'cxxflags' instead")
@@ -354,7 +389,7 @@ def _raise_incorrect_components_definition(self, package_name, package_requires)
              self.cxxflags or
              self.sharedlinkflags or
              self.exelinkflags or
-             self.build_modules or
+             self.get_build_modules() or
              self.requires):
             raise ConanException("self.cpp_info.components cannot be used with self.cpp_info "
                                  "global values at the same time")
@@ -427,7 +462,7 @@ def merge_lists(seq1, seq2):
         self.frameworkdirs = merge_lists(self.frameworkdirs, dep_cpp_info.framework_paths)
         self.libs = merge_lists(self.libs, dep_cpp_info.libs)
         self.frameworks = merge_lists(self.frameworks, dep_cpp_info.frameworks)
-        self.build_modules = merge_dicts(self.build_modules, dep_cpp_info.build_modules_paths)
+        self.build_modules = merge_dicts(self.get_build_modules(), dep_cpp_info.build_modules_paths)
         self.requires = merge_lists(self.requires, dep_cpp_info.requires)
         self.rootpaths.append(dep_cpp_info.rootpath)
 
@@ -437,13 +472,12 @@ def merge_lists(seq1, seq2):
         self.cflags = merge_lists(dep_cpp_info.cflags, self.cflags)
         self.sharedlinkflags = merge_lists(dep_cpp_info.sharedlinkflags, self.sharedlinkflags)
         self.exelinkflags = merge_lists(dep_cpp_info.exelinkflags, self.exelinkflags)
-
         if not self.sysroot:
             self.sysroot = dep_cpp_info.sysroot
 
     @property
     def build_modules_paths(self):
-        return self.build_modules
+        return self.get_build_modules()
 
     @property
     def include_paths(self):
diff --git a/conans/test/functional/generators/pkg_config_test.py b/conans/test/functional/generators/pkg_config_test.py
index f24fe8167d5..d045403ae7b 100644
--- a/conans/test/functional/generators/pkg_config_test.py
+++ b/conans/test/functional/generators/pkg_config_test.py
@@ -202,3 +202,59 @@ def test_empty_include(self):
         pc = client.load("pkg.pc")
         self.assertNotIn("libdir=${prefix}/lib", pc)
         self.assertNotIn("includedir=${prefix}/include", pc)
+
+    def test_custom_content(self):
+        # https://github.com/conan-io/conan/issues/7661
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+            from conans.tools import save
+            import os
+            import textwrap
+
+            class PkgConfigConan(ConanFile):
+                def package(self):
+                    save(os.path.join(self.package_folder, "include" ,"file"), "")
+                    save(os.path.join(self.package_folder, "lib" ,"file"), "")
+
+                def package_info(self):
+                    custom_content = textwrap.dedent(\"""
+                            datadir=${prefix}/share
+                            schemasdir=${datadir}/mylib/schemas
+                            bindir=${prefix}/bin
+                        \""")
+                    self.cpp_info.set_property("custom_content", custom_content, "pkg_config")
+                    self.cpp_info.includedirs = ["include"]
+                    self.cpp_info.libdirs = ["lib"]
+            """)
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . pkg/0.1@")
+        client.run("install pkg/0.1@ -g pkg_config")
+
+        pc_content = client.load("pkg.pc")
+        self.assertIn("libdir=${prefix}/lib", pc_content)
+        self.assertIn("datadir=${prefix}/share", pc_content)
+        self.assertIn("schemasdir=${datadir}/mylib/schemas", pc_content)
+        self.assertIn("bindir=${prefix}/bin", pc_content)
+        self.assertIn("Name: pkg", pc_content)
+
+    def test_custom_content_components(self):
+        conanfile = textwrap.dedent("""
+            from conans import ConanFile
+            from conans.tools import save
+            import os
+            import textwrap
+
+            class PkgConfigConan(ConanFile):
+                def package_info(self):
+                    self.cpp_info.components["mycomponent"].set_property("custom_content",
+                                                                         "componentdir=${prefix}/mydir",
+                                                                         "pkg_config")
+            """)
+        client = TestClient()
+        client.save({"conanfile.py": conanfile})
+        client.run("create . pkg/0.1@")
+        client.run("install pkg/0.1@ -g pkg_config")
+
+        pc_content = client.load("mycomponent.pc")
+        self.assertIn("componentdir=${prefix}/mydir", pc_content)
diff --git a/conans/test/integration/generators/cpp_info_set_generator_properties_test.py b/conans/test/integration/generators/cpp_info_set_generator_properties_test.py
new file mode 100644
index 00000000000..fb3b3c27fcd
--- /dev/null
+++ b/conans/test/integration/generators/cpp_info_set_generator_properties_test.py
@@ -0,0 +1,266 @@
+import os
+import textwrap
+
+import pytest
+
+from conans.test.assets.genconanfile import GenConanfile
+from conans.test.utils.tools import TestClient
+
+
+@pytest.fixture(scope="module")
+def setup_client():
+    client = TestClient()
+    custom_generator = textwrap.dedent("""
+        from conans.model import Generator
+        from conans import ConanFile
+        from conans.model.conan_generator import GeneratorComponentsMixin
+        import textwrap
+        import os
+
+        class custom_generator(GeneratorComponentsMixin, Generator):
+            name = "custom_generator"
+            @property
+            def filename(self):
+                return "my-generator.txt"
+
+            def _get_components_custom_names(self, pkg_name, cpp_info):
+                ret=[]
+                for comp_name, comp in self.sorted_components(cpp_info).items():
+                    comp_genname = comp.get_property("custom_name", generator=self.name)
+                    ret.append("{}:{}".format(comp.name, comp_genname))
+                return ret
+
+            @property
+            def content(self):
+                info = []
+                for pkg_name, cpp_info in self.deps_build_info.dependencies:
+                    info.append("{}:{}".format(pkg_name, cpp_info.get_property("custom_name", self.name)))
+                    info.extend(self._get_components_custom_names(pkg_name, cpp_info))
+                return os.linesep.join(info)
+        """)
+    client.save({"custom_generator.py": custom_generator})
+    client.run("config install custom_generator.py -tf generators")
+
+    build_module = textwrap.dedent("""
+        message("I am a build module")
+        """)
+
+    another_build_module = textwrap.dedent("""
+        message("I am another build module")
+        """)
+
+    client.save({"consumer.py": GenConanfile("consumer", "1.0").with_requires("mypkg/1.0").
+                with_generator("custom_generator").with_generator("cmake_find_package").
+                with_generator("cmake_find_package_multi").with_generator("pkg_config").
+                with_setting("build_type"),
+                "mypkg_bm.cmake": build_module, "mypkg_anootherbm.cmake": another_build_module})
+    return client
+
+
+def get_files_contents(folder, filenames):
+    ret = []
+    for filename in filenames:
+        with open(os.path.join(folder, filename)) as properties_package_file:
+            ret.append(properties_package_file.read())
+    return ret
+
+
+def test_same_results_components(setup_client):
+    client = setup_client
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            exports_sources = ["mypkg_bm.cmake"]
+            def package(self):
+                self.copy("mypkg_bm.cmake", dst="lib")
+            def package_info(self):
+                self.cpp_info.set_property("cmake_file_name", "MyFileName")
+                self.cpp_info.components["mycomponent"].libs = ["mycomponent-lib"]
+                self.cpp_info.components["mycomponent"].set_property("cmake_target_name", "mycomponent-name")
+                self.cpp_info.components["mycomponent"].set_property("cmake_build_modules", [os.path.join("lib", "mypkg_bm.cmake")])
+                self.cpp_info.components["mycomponent"].set_property("custom_name", "mycomponent-name", "custom_generator")
+        """)
+
+    client.save({"mypkg.py": mypkg})
+    client.run("export mypkg.py")
+    client.run("install consumer.py --build missing -s build_type=Release")
+
+    with open(os.path.join(client.current_folder, "my-generator.txt")) as custom_gen_file:
+        assert "mycomponent:mycomponent-name" in custom_gen_file.read()
+
+    files_to_compare = ["FindMyFileName.cmake", "MyFileNameConfig.cmake", "MyFileNameTargets.cmake",
+                        "MyFileNameTarget-release.cmake", "MyFileNameConfigVersion.cmake", "mypkg.pc",
+                        "mycomponent.pc"]
+    new_approach_contents = get_files_contents(client.current_folder, files_to_compare)
+
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            exports_sources = ["mypkg_bm.cmake"]
+            def package(self):
+                self.copy("mypkg_bm.cmake", dst="lib")
+            def package_info(self):
+                self.cpp_info.components["mycomponent"].libs = ["mycomponent-lib"]
+                self.cpp_info.filenames["cmake_find_package"] = "MyFileName"
+                self.cpp_info.filenames["cmake_find_package_multi"] = "MyFileName"
+                self.cpp_info.components["mycomponent"].names["cmake_find_package"] = "mycomponent-name"
+                self.cpp_info.components["mycomponent"].names["cmake_find_package_multi"] = "mycomponent-name"
+                self.cpp_info.components["mycomponent"].build_modules.append(os.path.join("lib", "mypkg_bm.cmake"))
+        """)
+    client.save({"mypkg.py": mypkg})
+    client.run("export mypkg.py")
+    client.run("install consumer.py -s build_type=Release")
+
+    old_approach_contents = get_files_contents(client.current_folder, files_to_compare)
+
+    assert new_approach_contents == old_approach_contents
+
+
+def test_same_results_without_components(setup_client):
+    client = setup_client
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            exports_sources = ["mypkg_bm.cmake"]
+            def package(self):
+                self.copy("mypkg_bm.cmake", dst="lib")
+            def package_info(self):
+                self.cpp_info.set_property("cmake_file_name", "MyFileName")
+                self.cpp_info.set_property("cmake_target_name", "mypkg-name")
+                self.cpp_info.set_property("cmake_build_modules",[os.path.join("lib",
+                                                                 "mypkg_bm.cmake")])
+                self.cpp_info.set_property("custom_name", "mypkg-name", "custom_generator")
+        """)
+
+    client.save({"mypkg.py": mypkg})
+    client.run("export mypkg.py")
+
+    client.run("install consumer.py --build missing -s build_type=Release")
+
+    with open(os.path.join(client.current_folder, "my-generator.txt")) as custom_gen_file:
+        assert "mypkg:mypkg-name" in custom_gen_file.read()
+
+    files_to_compare = ["FindMyFileName.cmake", "MyFileNameConfig.cmake", "MyFileNameTargets.cmake",
+                        "MyFileNameTarget-release.cmake", "MyFileNameConfigVersion.cmake", "mypkg.pc"]
+    new_approach_contents = get_files_contents(client.current_folder, files_to_compare)
+
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            exports_sources = ["mypkg_bm.cmake"]
+            def package(self):
+                self.copy("mypkg_bm.cmake", dst="lib")
+            def package_info(self):
+                self.cpp_info.filenames["cmake_find_package"] = "MyFileName"
+                self.cpp_info.filenames["cmake_find_package_multi"] = "MyFileName"
+                self.cpp_info.names["cmake_find_package"] = "mypkg-name"
+                self.cpp_info.names["cmake_find_package_multi"] = "mypkg-name"
+                self.cpp_info.names["custom_generator"] = "mypkg-name"
+                self.cpp_info.build_modules.append(os.path.join("lib", "mypkg_bm.cmake"))
+        """)
+    client.save({"mypkg.py": mypkg})
+    client.run("create mypkg.py")
+    client.run("install consumer.py -s build_type=Release")
+
+    old_approach_contents = get_files_contents(client.current_folder, files_to_compare)
+
+    assert new_approach_contents == old_approach_contents
+
+
+def test_same_results_specific_generators(setup_client):
+    client = setup_client
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            exports_sources = ["mypkg_bm.cmake", "mypkg_anootherbm.cmake"]
+            def package(self):
+                self.copy("mypkg_bm.cmake", dst="lib")
+                self.copy("mypkg_anootherbm.cmake", dst="lib")
+            def package_info(self):
+                self.cpp_info.set_property("cmake_file_name", "MyFileName", "cmake_find_package")
+                self.cpp_info.set_property("cmake_file_name", "MyFileNameMulti", "cmake_find_package_multi")
+                self.cpp_info.set_property("cmake_target_name", "mypkg-name", "cmake_find_package")
+                self.cpp_info.set_property("cmake_target_name", "mypkg-name-multi", "cmake_find_package_multi")
+                self.cpp_info.set_property("cmake_build_modules",[os.path.join("lib",
+                                                                 "mypkg_bm.cmake")], "cmake_find_package")
+                self.cpp_info.set_property("cmake_build_modules",[os.path.join("lib",
+                                                                 "mypkg_anootherbm.cmake")], "cmake_find_package_multi")
+        """)
+
+    client.save({"mypkg.py": mypkg})
+    client.run("export mypkg.py")
+
+    client.run("install consumer.py --build missing -s build_type=Release")
+
+    files_to_compare = ["FindMyFileName.cmake", "MyFileNameMultiConfig.cmake", "MyFileNameMultiTargets.cmake",
+                        "MyFileNameMultiTarget-release.cmake", "MyFileNameMultiConfigVersion.cmake"]
+    new_approach_contents = get_files_contents(client.current_folder, files_to_compare)
+
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            exports_sources = ["mypkg_bm.cmake", "mypkg_anootherbm.cmake"]
+            def package(self):
+                self.copy("mypkg_bm.cmake", dst="lib")
+                self.copy("mypkg_anootherbm.cmake", dst="lib")
+            def package_info(self):
+                self.cpp_info.filenames["cmake_find_package"] = "MyFileName"
+                self.cpp_info.filenames["cmake_find_package_multi"] = "MyFileNameMulti"
+                self.cpp_info.names["cmake_find_package"] = "mypkg-name"
+                self.cpp_info.names["cmake_find_package_multi"] = "mypkg-name-multi"
+                self.cpp_info.build_modules["cmake_find_package"].append(os.path.join("lib", "mypkg_bm.cmake"))
+                self.cpp_info.build_modules["cmake_find_package_multi"].append(os.path.join("lib", "mypkg_anootherbm.cmake"))
+        """)
+    client.save({"mypkg.py": mypkg})
+    client.run("create mypkg.py")
+    client.run("install consumer.py -s build_type=Release")
+
+    old_approach_contents = get_files_contents(client.current_folder, files_to_compare)
+
+    assert new_approach_contents == old_approach_contents
+
+
+def test_pkg_config_names(setup_client):
+    client = setup_client
+    mypkg = textwrap.dedent("""
+        import os
+        from conans import ConanFile, CMake, tools
+        class MyPkg(ConanFile):
+            settings = "build_type"
+            name = "mypkg"
+            version = "1.0"
+            def package_info(self):
+                self.cpp_info.components["mycomponent"].libs = ["mycomponent-lib"]
+                self.cpp_info.components["mycomponent"].set_property("pkg_config_name", "mypkg-config-name")
+        """)
+
+    client.save({"mypkg.py": mypkg})
+    client.run("export mypkg.py")
+    client.run("install consumer.py --build missing")
+
+    with open(os.path.join(client.current_folder, "mypkg-config-name.pc")) as gen_file:
+        assert "mypkg-config-name" in gen_file.read()
diff --git a/conans/test/unittests/model/build_info/generic_properties_test.py b/conans/test/unittests/model/build_info/generic_properties_test.py
new file mode 100644
index 00000000000..ad30708564f
--- /dev/null
+++ b/conans/test/unittests/model/build_info/generic_properties_test.py
@@ -0,0 +1,16 @@
+from conans.model.build_info import _CppInfo
+
+
+def test_set_get_properties():
+    cpp_info = _CppInfo()
+    cpp_info.set_property("my_property", "default_value")
+    assert cpp_info.get_property("my_property") == "default_value"
+    # can you do a get_property for just a family without generator?
+    assert cpp_info.get_property("my_property", generator="cmake_multi") == "default_value"
+    assert cpp_info.get_property("my_property", generator="pkg_config") == "default_value"
+
+    cpp_info.set_property("my_property", "pkg_config_value", generator="pkg_config")
+    assert cpp_info.get_property("my_property", generator="pkg_config") == "pkg_config_value"
+    cpp_info.set_property("other_property", "other_pkg_config_value", generator="pkg_config")
+    assert not cpp_info.get_property("other_property")
+    assert cpp_info.get_property("other_property", generator="pkg_config") == "other_pkg_config_value"


Code review comments:
diff_hunk: @@ -209,15 +210,49 @@ def name(self):
     def name(self, value):
         self._name = value
 
+    # TODO: Deprecate for 2.0. Only cmake and pkg_config generators should access this.
+    #  Use get_property for 2.0
     def get_name(self, generator):
-        return self.names.get(generator, self._name)
+        property_name = "cmake_target_name" if "cmake" in generator else "pkg_config_name"
+        return self.get_property(property_name, generator) or self.names.get(generator, self._name)
 
+    # TODO: Deprecate for 2.0. Only cmake generators should access this. Use get_property for 2.0
     def get_filename(self, generator):
-        result = self.filenames.get(generator)
+        result = self.get_property("cmake_file_name", generator) or self.filenames.get(generator)
         if result:
             return result
         return self.get_name(generator)
 
+    # TODO: Deprecate for 2.0. Use get_property for 2.0
+    def get_build_modules(self):
+        default_values_dict = self._generator_properties.get("conan_default_generators_value")
+        default_build_modules_value = default_values_dict.get("cmake_build_modules") if default_values_dict else None
+        ret_dict = {"cmake_find_package": default_build_modules_value,
+                    "cmake_find_package_multi": default_build_modules_value,
+                    "cmake": default_build_modules_value,
+                    "cmake_multi": default_build_modules_value} if default_build_modules_value else {}
+        for generator, values in self._generator_properties.items():
+            if values.get("cmake_build_modules") and generator != "conan_default_generators_value":
+                ret_dict[generator] = values.get("cmake_build_modules")
+        return ret_dict if ret_dict else self.build_modules
+
+    def set_property(self, property_name, value, generator=None):
+        generator = generator or "conan_default_generators_value"
+        gen_dict = self._generator_properties.get(generator)
+        if gen_dict:
+            gen_dict.update({property_name: value})
+        else:
+            self._generator_properties.update({generator: {property_name: value}})
+
+    def get_property(self, property_name, generator=None):
+        generator = generator or "conan_default_generators_value"
+        gen_dict = self._generator_properties.get(generator)
+        if gen_dict and gen_dict.get(property_name):
+            return gen_dict.get(property_name)
+        else:
+            gen_dict = self._generator_properties.get("conan_default_generators_value")
+            return gen_dict.get(property_name) if gen_dict else None
+
path: conans/model/build_info.py
text: @user1:
```suggestion
        if generator:
            try:
                return self._generator_properties[generator][property_name]
            except KeyError:
                pass
        try:
            return self._generator_properties[None][property_name]
        except KeyError:
            pass
```

- avoid getting twice ``gen_dict.get(property_name)``
- There is a redundant double processing when ``generator=None``, same thing is being evaluated twice.

diff_hunk: @@ -209,15 +210,49 @@ def name(self):
     def name(self, value):
         self._name = value
 
+    # TODO: Deprecate for 2.0. Only cmake and pkg_config generators should access this.
+    #  Use get_property for 2.0
     def get_name(self, generator):
-        return self.names.get(generator, self._name)
+        property_name = "cmake_target_name" if "cmake" in generator else "pkg_config_name"
path: conans/model/build_info.py
text: @user1:
It is a bit confusing this "pkg_config_name" for every other thing not "cmake"

diff_hunk: @@ -209,15 +210,49 @@ def name(self):
     def name(self, value):
         self._name = value
 
+    # TODO: Deprecate for 2.0. Only cmake and pkg_config generators should access this.
+    #  Use get_property for 2.0
     def get_name(self, generator):
-        return self.names.get(generator, self._name)
+        property_name = "cmake_target_name" if "cmake" in generator else "pkg_config_name"
+        return self.get_property(property_name, generator) or self.names.get(generator, self._name)
 
+    # TODO: Deprecate for 2.0. Only cmake generators should access this. Use get_property for 2.0
     def get_filename(self, generator):
-        result = self.filenames.get(generator)
+        result = self.get_property("cmake_file_name", generator) or self.filenames.get(generator)
         if result:
             return result
         return self.get_name(generator)
 
+    # TODO: Deprecate for 2.0. Use get_property for 2.0
+    def get_build_modules(self):
+        default_values_dict = self._generator_properties.get("conan_default_generators_value")
+        default_build_modules_value = default_values_dict.get("cmake_build_modules") if default_values_dict else None
+        ret_dict = {"cmake_find_package": default_build_modules_value,
+                    "cmake_find_package_multi": default_build_modules_value,
+                    "cmake": default_build_modules_value,
+                    "cmake_multi": default_build_modules_value} if default_build_modules_value else {}
+        for generator, values in self._generator_properties.items():
+            if values.get("cmake_build_modules") and generator != "conan_default_generators_value":
+                ret_dict[generator] = values.get("cmake_build_modules")
+        return ret_dict if ret_dict else self.build_modules
+
+    def set_property(self, property_name, value, generator=None):
+        generator = generator or "conan_default_generators_value"
+        gen_dict = self._generator_properties.get(generator)
+        if gen_dict:
+            gen_dict.update({property_name: value})
+        else:
+            self._generator_properties.update({generator: {property_name: value}})
path: conans/model/build_info.py
text: @user1:
```suggestion
        self._generator_properties.setdefault(generator, {})[property_name] = value
```

- Use ``None`` as the value that means "no generator". No need to use a custom string
- Avoid ``mydict.update({k: v})`` why not ``mydict[k] = v``?
- ``setdefault`` is more idiomatic