Repository: conan-io/conan
Pull request #17758 (2025-02-12 10:01:40)

Ignore `cgi` deprecation warning on CI 
Changelog: omit
Docs: omit

From https://github.com/conan-io/conan/issues/17736,
> Fix the WebOb warning, might need to update the test-dependency WebTest

Until https://github.com/Pylons/webob/issues/437 is resolved, the best approach is to use the `legacy-cgi` package, which will allow Conan to run in Python environments greater than 3.12.

### Edit
Recent versions of `WebOb` uses `legacy-cgi` as a substitute of `cgi` deprecated module since python 3.13.
This means it is safe to ignore the `pytest` warning complaining about `cgi` deprecation.

Also, updated python versions used in `linux-tests.yml`.

- [x] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop2/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one.


memsharded: > Recent versions of `webob` already pull `legacy-cgi` as a dependency, and `WebTest` does not seem to restrict `webob` from beeing upgraded. This _should_ not be necessary. (Not tested, just wondering why this is an issue here)

this is weird then, we don't have WebTest constrained (``WebTest>=3.0.0, <4``, and latest is 3.0.4), but we are still getting the warnings of webob in CI

Patch:
diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py
index be3967ffd..dd3b7670a 100644
--- a/conan/tools/cmake/cmakedeps2/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps2/target_configuration.py
@@ -34,17 +34,21 @@ class TargetConfigurationTemplate2:
         return f"{f}-Targets{build}-{config}.cmake"
 
     def _requires(self, info, components):
-        result = []
+        result = {}
         requires = info.parsed_requires()
         pkg_name = self._conanfile.ref.name
+        pkg_type = info.type
+        assert isinstance(pkg_type, PackageType), f"Pkg type {pkg_type} {type(pkg_type)}"
         transitive_reqs = self._cmakedeps.get_transitive_requires(self._conanfile)
+
         if not requires and not components:  # global cpp_info without components definition
             # require the pkgname::pkgname base (user defined) or INTERFACE base target
-            targets = []
             for d in transitive_reqs.values():
                 dep_target = self._cmakedeps.get_property("cmake_target_name", d)
-                targets.append(dep_target or f"{d.ref.name}::{d.ref.name}")
-            return targets
+                dep_target = dep_target or f"{d.ref.name}::{d.ref.name}"
+                link = not (pkg_type is PackageType.SHARED and d.package_type is PackageType.SHARED)
+                result[dep_target] = link
+            return result
 
         for required_pkg, required_comp in requires:
             if required_pkg is None:  # Points to a component of same package
@@ -53,7 +57,9 @@ class TargetConfigurationTemplate2:
                 dep_target = self._cmakedeps.get_property("cmake_target_name", self._conanfile,
                                                           required_comp)
                 dep_target = dep_target or f"{pkg_name}::{required_comp}"
-                result.append(dep_target)
+                link = not (pkg_type is PackageType.SHARED and
+                            dep_comp.package_type is PackageType.SHARED)
+                result[dep_target] = link
             else:  # Different package
                 try:
                     dep = transitive_reqs[required_pkg]
@@ -73,12 +79,15 @@ class TargetConfigurationTemplate2:
                         default_target = f"{required_pkg}::{required_comp}"
                     dep_target = self._cmakedeps.get_property("cmake_target_name", dep, comp)
                     dep_target = dep_target or default_target
-                    result.append(dep_target)
+                    link = not (pkg_type is PackageType.SHARED and
+                                dep_comp.package_type is PackageType.SHARED)
+                    result[dep_target] = link
         return result
 
     @property
     def _context(self):
         cpp_info = self._conanfile.cpp_info.deduce_full_cpp_info(self._conanfile)
+        assert isinstance(cpp_info.type, PackageType)
         pkg_name = self._conanfile.ref.name
         # fallback to consumer configuration if it doesn't have build_type
         config = self._conanfile.settings.get_safe("build_type", self._cmakedeps.configuration)
@@ -150,10 +159,10 @@ class TargetConfigurationTemplate2:
 
         includedirs = ";".join(self._path(i, pkg_folder, pkg_folder_var)
                                for i in info.includedirs) if info.includedirs else ""
-        requires = ";".join(self._requires(info, components))
+        requires = self._requires(info, components)
+        assert isinstance(requires, dict)
         defines = " ".join(info.defines)
         # TODO: Missing escaping?
-        # TODO: Missing link language
         # FIXME: Filter by lib traits!!!!!
         if not self._require.headers:  # If not depending on headers, paths and
             includedirs = defines = None
@@ -171,7 +180,7 @@ class TargetConfigurationTemplate2:
         if info.frameworks:
             ConanOutput(scope=str(self._conanfile)).warning("frameworks not supported yet in new CMakeDeps generator")
 
-        if info.libs:
+        if info.libs:  # TODO: to change to location
             if len(info.libs) != 1:
                 raise ConanException(f"New CMakeDeps only allows 1 lib per component:\n"
                                      f"{self._conanfile}: {info.libs}")
@@ -203,15 +212,14 @@ class TargetConfigurationTemplate2:
         if libs and root_target_name not in libs:
             # Add a generic interface target for the package depending on the others
             if cpp_info.default_components is not None:
-                all_requires = []
+                all_requires = {}
                 for defaultc in cpp_info.default_components:
                     target_name = self._cmakedeps.get_property("cmake_target_name", self._conanfile,
                                                                defaultc)
                     comp_name = target_name or f"{pkg_name}::{defaultc}"
-                    all_requires.append(comp_name)
-                all_requires = ";".join(all_requires)
+                    all_requires[comp_name] = True  # It is an interface, full link
             else:
-                all_requires = ";".join(libs.keys())
+                all_requires = {k: True for k in libs.keys()}
             libs[root_target_name] = {"type": "INTERFACE",
                                       "requires": all_requires}
 
@@ -339,10 +347,29 @@ class TargetConfigurationTemplate2:
         set_target_properties({{lib}} PROPERTIES IMPORTED_IMPLIB_{{config}}
                               "{{lib_info["link_location"]}}")
         {% endif %}
+
         {% if lib_info.get("requires") %}
-        set_target_properties({{lib}} PROPERTIES INTERFACE_LINK_LIBRARIES
-                              "{{config_wrapper(config, lib_info["requires"])}}")
+        # Information of transitive dependencies
+        {% for require_target, link in lib_info["requires"].items() %}
+        # Requirement {{require_target}} => Full link: {{link}}
+
+        {% if link %}
+        # set property allows to append, and lib_info[requires] will iterate
+        set_property(TARGET {{lib}} APPEND PROPERTY INTERFACE_LINK_LIBRARIES
+                     "{{config_wrapper(config, require_target)}}")
+        {% else %}
+        if(${CMAKE_VERSION} VERSION_LESS "3.27")
+            message(FATAL_ERROR "The 'CMakeToolchain' generator only works with CMake >= 3.27")
+        endif()
+        # If the headers trait is not there, this will do nothing
+        target_link_libraries({{lib}} INTERFACE
+                              $<COMPILE_ONLY:{{config_wrapper(config, require_target)}}> )
+        set_property(TARGET {{lib}} APPEND PROPERTY IMPORTED_LINK_DEPENDENT_LIBRARIES_{{config}}
+                     {{require_target}})
         {% endif %}
+        {% endfor %}
+        {% endif %}
+
         {% if lib_info.get("system_libs") %}
         target_link_libraries({{lib}} INTERFACE {{lib_info["system_libs"]}})
         {% endif %}
diff --git a/conan/tools/sbom/cyclonedx.py b/conan/tools/sbom/cyclonedx.py
index 22c308a93..0e0770e0b 100644
--- a/conan/tools/sbom/cyclonedx.py
+++ b/conan/tools/sbom/cyclonedx.py
@@ -1,12 +1,32 @@
 
 
-def cyclonedx_1_4(graph, name=None, add_build=False, add_tests=False, **kwargs):
+def cyclonedx_1_4(conanfile, name=None, add_build=False, add_tests=False, **kwargs):
     """
-    (Experimental) Generate cyclone 1.4 sbom with json format
+    (Experimental) Generate cyclone 1.4 SBOM with JSON format
+
+    Creates a CycloneDX 1.4 Software Bill of Materials (SBOM) from a given dependency graph.
+
+
+
+    Parameters:
+        conanfile: The conanfile instance.
+        name (str, optional): Custom name for the metadata field.
+        add_build (bool, optional, default=False): Include build dependencies.
+        add_tests (bool, optional, default=False): Include test dependencies.
+
+    Returns:
+        The generated CycloneDX 1.4 document as a string.
+
+    Example usage:
+    ```
+    cyclonedx(conanfile, name="custom_name", add_build=True, add_test=True, **kwargs)
+    ```
+
     """
     import uuid
     import time
     from datetime import datetime, timezone
+    graph = conanfile.subgraph
 
     has_special_root_node = not (getattr(graph.root.ref, "name", False) and getattr(graph.root.ref, "version", False) and getattr(graph.root.ref, "revision", False))
     special_id = str(uuid.uuid4())


Test patch:
diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml
index 7e447a6c3..2cbfeaf15 100644
--- a/.github/workflows/linux-tests.yml
+++ b/.github/workflows/linux-tests.yml
@@ -49,7 +49,7 @@ jobs:
       options: --user conan
     strategy:
       matrix:
-        python-version: [3.12.3, 3.9.2, 3.8.6, 3.6.15]
+        python-version: [3.13, 3.9, 3.8, 3.6]
         test-type: [unittests, integration, functional]
         include:
         - test-type: unittests
@@ -94,7 +94,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.12, 3.9]
+        python-version: [3.13, 3.9]
     name: Docker Runner Tests (${{ matrix.python-version }})
     steps:
       - name: Set up Python ${{ matrix.python-version }}
diff --git a/.github/workflows/osx-tests.yml b/.github/workflows/osx-tests.yml
index 339ab9f72..767132ca8 100644
--- a/.github/workflows/osx-tests.yml
+++ b/.github/workflows/osx-tests.yml
@@ -141,6 +141,7 @@ jobs:
             ~/Applications/CMake/3.15.7
             ~/Applications/CMake/3.19.7
             ~/Applications/CMake/3.23.5
+            ~/Applications/CMake/3.27.9
             ~/Applications/bazel/6.5.0
             ~/Applications/bazel/7.4.1
             ~/Applications/bazel/8.0.0
diff --git a/pytest.ini b/pytest.ini
index 61ceaf180..bf8bb6be5 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -4,3 +4,5 @@ testpaths = 'test'
 markers =
    docker_runner: Mark tests that require Docker to run.
    artifactory_ready: These tests can be run against a full Artifactory
+filterwarnings =
+    ignore:'cgi' is deprecated:DeprecationWarning
diff --git a/test/functional/sbom/test_cyclonedx.py b/test/functional/sbom/test_cyclonedx.py
index b24f3beb7..8c7d1f351 100644
--- a/test/functional/sbom/test_cyclonedx.py
+++ b/test/functional/sbom/test_cyclonedx.py
@@ -16,7 +16,7 @@ from conan.api.output import ConanOutput
 from conan.tools.sbom import cyclonedx_1_4
 
 def post_package(conanfile):
-    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph, add_build=%s, add_tests=%s)
+    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile, add_build=%s, add_tests=%s)
     metadata_folder = conanfile.package_metadata_folder
     file_name = "sbom.cdx.json"
     with open(os.path.join(metadata_folder, file_name), 'w') as f:
@@ -138,7 +138,7 @@ from conan.api.output import ConanOutput
 from conan.tools.sbom import cyclonedx_1_4
 
 def post_generate(conanfile):
-    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile.subgraph, name=%s)
+    sbom_cyclonedx_1_4 = cyclonedx_1_4(conanfile, name=%s)
     generators_folder = conanfile.generators_folder
     file_name = "sbom.cdx.json"
     os.mkdir(os.path.join(generators_folder, "sbom"))
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new.py b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new.py
index 4ee67b740..64a993e5e 100644
--- a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new.py
+++ b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new.py
@@ -219,7 +219,7 @@ class TestLibs:
             assert "Conan: Target declared imported STATIC library 'engine::engine'" in c.out
 
     # if not using cmake >= 3.23 the intermediate gamelib_test linkage fail
-    @pytest.mark.tool("cmake", "3.23")
+    @pytest.mark.tool("cmake", "3.27")
     @pytest.mark.parametrize("shared", [False, True])
     def test_multilevel(self, shared):
         # TODO: make this shared fixtures in conftest for multi-level shared testing
@@ -338,8 +338,8 @@ class TestLibsIntegration:
         c.run(f"install app -c tools.cmake.cmakedeps:new={new_value} -g CMakeDeps")
         targets_cmake = c.load("app/pkg-Targets-release.cmake")
         assert "find_dependency(MyDep REQUIRED CONFIG)" in targets_cmake
-        assert 'set_target_properties(pkg::pkg PROPERTIES INTERFACE_LINK_LIBRARIES\n' \
-               '                      "$<$<CONFIG:RELEASE>:MyTargetDep>"' in targets_cmake
+        assert 'set_property(TARGET pkg::pkg APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n' \
+               '             "$<$<CONFIG:RELEASE>:MyTargetDep>")' in targets_cmake
 
 
 class TestLibsLinkageTraits:
@@ -364,6 +364,7 @@ class TestLibsLinkageTraits:
         assert "engine/0.1: Hello World Release!"
         assert "game/0.1: Hello World Release!"
 
+    @pytest.mark.tool("cmake", "3.27")
     @pytest.mark.parametrize("shared", [False, True])
     def test_transitive_headers(self, shared):
         c = TestClient()
diff --git a/test/integration/graph/test_replace_requires.py b/test/integration/graph/test_replace_requires.py
index dbde54b94..4b832ebac 100644
--- a/test/integration/graph/test_replace_requires.py
+++ b/test/integration/graph/test_replace_requires.py
@@ -451,8 +451,8 @@ class TestReplaceRequiresTransitiveGenerators:
         cmake = c.load("app/openssl-Targets-release.cmake")
         assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
         assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake
-        assert "set_target_properties(openssl::openssl PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
-               '                      "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
+        assert "set_property(TARGET openssl::openssl APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n" \
+               '             "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
 
     @pytest.mark.parametrize("diamond", [True, False])
     def test_openssl_components(self, diamond):
@@ -523,8 +523,8 @@ class TestReplaceRequiresTransitiveGenerators:
         cmake = c.load("app/openssl-Targets-release.cmake")
         assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
         assert "add_library(openssl::crypto STATIC IMPORTED)" in cmake
-        assert "set_target_properties(openssl::crypto PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
-               '                      "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
+        assert "set_property(TARGET openssl::crypto APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n" \
+               '             "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
 
     @pytest.mark.parametrize("diamond", [True, False])
     @pytest.mark.parametrize("explicit_requires", [True, False])
@@ -601,8 +601,8 @@ class TestReplaceRequiresTransitiveGenerators:
         assert "find_dependency(ZLIB REQUIRED CONFIG)" in cmake
         assert "add_library(openssl::openssl STATIC IMPORTED)" in cmake
         # It should access the generic zlib-ng target
-        assert "set_target_properties(openssl::openssl PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
-               '                      "$<$<CONFIG:RELEASE>:zlib-ng::zlib-ng>")' in cmake
+        assert "set_property(TARGET openssl::openssl APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n" \
+               '             "$<$<CONFIG:RELEASE>:zlib-ng::zlib-ng>")' in cmake
 
     @pytest.mark.parametrize("diamond", [True, False])
     @pytest.mark.parametrize("package_requires", [False, True])
@@ -682,8 +682,8 @@ class TestReplaceRequiresTransitiveGenerators:
         assert "add_library(openssl::crypto STATIC IMPORTED)" in cmake
         if package_requires:
             # The generic package requirement uses the package name zlib-ng
-            assert "set_target_properties(openssl::crypto PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
-                   '                      "$<$<CONFIG:RELEASE>:zlib-ng::zlib-ng>")' in cmake
+            assert "set_property(TARGET openssl::crypto APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n" \
+                   '             "$<$<CONFIG:RELEASE>:zlib-ng::zlib-ng>")' in cmake
         else:
-            assert "set_target_properties(openssl::crypto PROPERTIES INTERFACE_LINK_LIBRARIES\n" \
-                   '                      "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake
+            assert "set_property(TARGET openssl::crypto APPEND PROPERTY INTERFACE_LINK_LIBRARIES\n" \
+                   '             "$<$<CONFIG:RELEASE>:ZLIB::ZLIB>")' in cmake