Repository: conan-io/conan
Pull request #16597 (2024-07-03 11:40:28)

core.scm:local_url=allow|block
Changelog: Feature: Introduce ``core.scm:local_url=allow|block`` to control local folder URLs in conandata ``scm``
Changelog: Fix: Raise an error when trying to upload a package with a local folder URL in ``conandata`` ``scm``. 
Docs: https://github.com/conan-io/docs/pull/XXXX

Close https://github.com/conan-io/conan/issues/16591




jasal82: This will work if everyone stores the url in `scm.url`. But your migration guide already recommends otherwise for replacing the scm attribute

https://docs.conan.io/1/migrating_to_2.0/recipes.html#the-scm-attribute

So you'd have to include `sources.url` as well. And any other possible structure because it is never validated. I think we'd have to create an interface for interacting with conandata to be able to do this reliably.
memsharded: > This will work if everyone stores the url in scm.url. But your migration guide already recommends otherwise for replacing the scm attribute

This has been normalized in Conan 2 with the ``coordinates_to_conandata`` and ``checkout_from_conandata_coordinates`` helpers, that will always store it in ``conandata->scm->url``. The intention is to fully normalize it, and even leverage this information for other functionality.


> https://docs.conan.io/1/migrating_to_2.0/recipes.html#the-scm-attribute

This is a mistake in the docs, and should be updated to reflect the new convention.


jasal82: I see, that's good to know. That means I'll have to create another release of our base conanfile that uses the conventions from those new functions, so that we can replace them with the actual function calls once we're ready to switch to Conan 2.
memsharded: Yes, sorry about that, the "standardization" of the ``conandata->scm->url/commit`` happened in https://github.com/conan-io/conan/pull/15377,  that was 1 year after the Conan 2.0 launch, so the Conan 1 docs were no longer updated with these things.

Patch:
diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py
index dad6ce312..fbff89c20 100644
--- a/conan/tools/cmake/layout.py
+++ b/conan/tools/cmake/layout.py
@@ -94,9 +94,11 @@ def get_build_folder_custom_vars(conanfile):
                     tmp = "{}_{}".format(var, value)
         elif group == "self":
             tmp = getattr(conanfile, var, None)
+        elif group == "const":
+            tmp = var
         else:
             raise ConanException("Invalid 'tools.cmake.cmake_layout:build_folder_vars' value, it has"
-                                 " to start with 'settings.' or 'options.': {}".format(s))
+                                 f" to start with 'settings.', 'options.', 'self.' or 'const.': {s}")
         if tmp:
             ret.append(tmp.lower())
 
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 0d6585718..425e62b2a 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -724,7 +724,7 @@ class ExtraFlagsBlock(Block):
         {% if defines %}
         {% if config %}
         {% for define in defines %}
-        add_compile_definitions($<$<CONFIG:{{config}}>:"{{ define }}">)
+        add_compile_definitions("$<$<CONFIG:{{config}}>:{{ define }}>")
         {% endfor %}
         {% else %}
         add_compile_definitions({% for define in defines %} "{{ define }}"{% endfor %})
diff --git a/conan/tools/gnu/gnutoolchain.py b/conan/tools/gnu/gnutoolchain.py
index 95c32129e..9848e18fc 100644
--- a/conan/tools/gnu/gnutoolchain.py
+++ b/conan/tools/gnu/gnutoolchain.py
@@ -123,15 +123,13 @@ class GnuToolchain:
         if is_msvc(self._conanfile):
             extra_env_vars = {"CC": "cl -nologo",
                               "CXX": "cl -nologo",
-                              "LD": "link -nologo",
-                              "AR": "lib",
                               "NM": "dumpbin -symbols",
                               "OBJDUMP": ":",
                               "RANLIB": ":",
                               "STRIP": ":"}
         # Configuration map
         compilers_mapping = {"c": "CC", "cpp": "CXX", "cuda": "NVCC", "fortran": "FC",
-                             "rc": "RC", "ld": "LD", "ar": "AR", "nm": "NM", "ranlib": "RANLIB",
+                             "rc": "RC", "nm": "NM", "ranlib": "RANLIB",
                              "objdump": "OBJDUMP", "strip": "STRIP"}
         # Compiler definitions by conf
         compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={},
diff --git a/conan/tools/gnu/makedeps.py b/conan/tools/gnu/makedeps.py
index c3d0017b9..d02cc3534 100644
--- a/conan/tools/gnu/makedeps.py
+++ b/conan/tools/gnu/makedeps.py
@@ -30,7 +30,9 @@ import re
 import textwrap
 
 from jinja2 import Template, StrictUndefined
+from typing import Optional
 
+from conan.api.output import ConanOutput
 from conan.internal import check_duplicated_generator
 from conan.tools.files import save
 
@@ -67,6 +69,30 @@ def _makefy(name: str) -> str:
     return re.sub(r'[^0-9A-Z_]', '_', name.upper())
 
 
+def _makefy_properties(properties: Optional[dict]) -> dict:
+    """
+    Convert property dictionary keys to Make-variable-friendly syntax
+    :param properties: The property dictionary to be converted (None is also accepted)
+    :return: Modified property dictionary with keys not including bad characters that are not parsed correctly
+    """
+    return {_makefy(name): value for name, value in properties.items()} if properties else {}
+
+def _check_property_value(name, value, output):
+    if "\n" in value:
+        output.warning(f"Skipping propery '{name}' because it contains newline")
+        return False
+    else:
+        return True
+
+def _filter_properties(properties: Optional[dict], output) -> dict:
+    """
+    Filter out properties whose values contain newlines, because they would break the generated makefile
+    :param properties: A property dictionary (None is also accepted)
+    :return: A property dictionary without the properties containing newlines 
+    """
+    return {name: value for name, value in properties.items() if _check_property_value(name, value, output)} if properties else {}
+
+
 def _conan_prefix_flag(variable: str) -> str:
     """
     Return a global flag to be used as prefix to any value in the makefile
@@ -131,6 +157,12 @@ def _jinja_format_list_values() -> str:
             {%- endif -%}
             {%- endmacro %}
 
+            {%- macro define_multiple_variable_value(var, values) -%}
+            {% for property_name, value in values.items() %}
+            {{ var }}_{{ property_name }} = {{ value }}
+            {% endfor %}
+            {%- endmacro %}
+
             {%- macro define_variable_value(var, values) -%}
             {%- if values is not none -%}
             {%- if values|length > 0 -%}
@@ -332,9 +364,10 @@ class DepComponentContentGenerator:
         {{- define_variable_value_safe("CONAN_FRAMEWORKS_{}_{}".format(dep_name, name), cpp_info_flags, 'frameworks') -}}
         {{- define_variable_value_safe("CONAN_REQUIRES_{}_{}".format(dep_name, name), cpp_info_flags, 'requires') -}}
         {{- define_variable_value_safe("CONAN_SYSTEM_LIBS_{}_{}".format(dep_name, name), cpp_info_flags, 'system_libs') -}}
+        {{- define_multiple_variable_value("CONAN_PROPERTY_{}_{}".format(dep_name, name), properties) -}}
         """)
 
-    def __init__(self, dependency, component_name: str, dirs: dict, flags: dict):
+    def __init__(self, dependency, component_name: str, dirs: dict, flags: dict, output):
         """
         :param dependency: The dependency object that owns the component
         :param component_name: component raw name e.g. poco::poco_json
@@ -345,6 +378,7 @@ class DepComponentContentGenerator:
         self._name = component_name
         self._dirs = dirs or {}
         self._flags = flags or {}
+        self._output = output
 
     def content(self) -> str:
         """
@@ -356,7 +390,8 @@ class DepComponentContentGenerator:
             "dep_name": _makefy(self._dep.ref.name),
             "name": _makefy(self._name),
             "cpp_info_dirs": self._dirs,
-            "cpp_info_flags": self._flags
+            "cpp_info_flags": self._flags,
+            "properties": _makefy_properties(_filter_properties(self._dep.cpp_info.components[self._name]._properties, self._output)),
         }
         template = Template(_jinja_format_list_values() + self.template, trim_blocks=True,
                             lstrip_blocks=True, undefined=StrictUndefined)
@@ -397,15 +432,17 @@ class DepContentGenerator:
         {{- define_variable_value_safe("CONAN_REQUIRES_{}".format(name), cpp_info_flags, 'requires') -}}
         {{- define_variable_value_safe("CONAN_SYSTEM_LIBS_{}".format(name), cpp_info_flags, 'system_libs') -}}
         {{- define_variable_value("CONAN_COMPONENTS_{}".format(name), components) -}}
+        {{- define_multiple_variable_value("CONAN_PROPERTY_{}".format(name), properties) -}}
         """)
 
-    def __init__(self, dependency, require, root: str, sysroot, dirs: dict, flags: dict):
+    def __init__(self, dependency, require, root: str, sysroot, dirs: dict, flags: dict, output):
         self._dep = dependency
         self._req = require
         self._root = root
         self._sysroot = sysroot
         self._dirs = dirs or {}
         self._flags = flags or {}
+        self._output = output
 
     def content(self) -> str:
         """
@@ -420,6 +457,7 @@ class DepContentGenerator:
             "components": list(self._dep.cpp_info.get_sorted_components().keys()),
             "cpp_info_dirs": self._dirs,
             "cpp_info_flags": self._flags,
+            "properties": _makefy_properties(_filter_properties(self._dep.cpp_info._properties, self._output)),
         }
         template = Template(_jinja_format_list_values() + self.template, trim_blocks=True,
                             lstrip_blocks=True, undefined=StrictUndefined)
@@ -431,7 +469,7 @@ class DepComponentGenerator:
     Generates Makefile content for a dependency component
     """
 
-    def __init__(self, dependency, makeinfo: MakeInfo, component_name: str, component, root: str):
+    def __init__(self, dependency, makeinfo: MakeInfo, component_name: str, component, root: str, output):
         """
         :param dependency: The dependency object that owns the component
         :param makeinfo: Makeinfo to store component variables
@@ -444,6 +482,7 @@ class DepComponentGenerator:
         self._comp = component
         self._root = root
         self._makeinfo = makeinfo
+        self._output = output
 
     def _get_component_dirs(self) -> dict:
         """
@@ -500,7 +539,7 @@ class DepComponentGenerator:
         """
         dirs = self._get_component_dirs()
         flags = self._get_component_flags()
-        comp_content_gen = DepComponentContentGenerator(self._dep, self._name, dirs, flags)
+        comp_content_gen = DepComponentContentGenerator(self._dep, self._name, dirs, flags, self._output)
         comp_content = comp_content_gen.content()
         return comp_content
 
@@ -510,10 +549,11 @@ class DepGenerator:
     Process a dependency cpp_info variables and generate its Makefile content
     """
 
-    def __init__(self, dependency, require):
+    def __init__(self, dependency, require, output):
         self._dep = dependency
         self._req = require
         self._info = MakeInfo(self._dep.ref.name, [], [])
+        self._output = output
 
     @property
     def makeinfo(self) -> MakeInfo:
@@ -585,11 +625,11 @@ class DepGenerator:
         sysroot = self._get_sysroot(root)
         dirs = self._get_dependency_dirs(root, self._dep)
         flags = self._get_dependency_flags(self._dep)
-        dep_content_gen = DepContentGenerator(self._dep, self._req, root, sysroot, dirs, flags)
+        dep_content_gen = DepContentGenerator(self._dep, self._req, root, sysroot, dirs, flags, self._output)
         content = dep_content_gen.content()
 
         for comp_name, comp in self._dep.cpp_info.get_sorted_components().items():
-            component_gen = DepComponentGenerator(self._dep, self._info, comp_name, comp, root)
+            component_gen = DepComponentGenerator(self._dep, self._info, comp_name, comp, root, self._output)
             content += component_gen.generate()
 
         return content
@@ -630,8 +670,8 @@ class MakeDeps:
             # Require is not used at the moment, but its information could be used, and will be used in Conan 2.0
             if require.build:
                 continue
-
-            dep_gen = DepGenerator(dep, require)
+            output = ConanOutput(scope=f"{self._conanfile} MakeDeps: {dep}:")
+            dep_gen = DepGenerator(dep, require, output)
             make_infos.append(dep_gen.makeinfo)
             deps_buffer += dep_gen.generate()
 
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index 1c8a12fc0..117a064b8 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -34,6 +34,7 @@ class Git:
                 self._excluded = c.get("core.scm:excluded", check_type=list)
             else:
                 self._excluded = conf_excluded
+        self._local_url = global_conf.get("core.scm:local_url", choices=["allow", "block"])
 
     def run(self, cmd, hidden_output=None):
         """
@@ -176,16 +177,14 @@ class Git:
         in_remote = self.commit_in_remote(commit, remote=remote)
         if in_remote:
             return url, commit
-        # TODO: Once we know how to pass [conf] to export, enable this
-        # conf_name = "tools.scm:local"
-        # allow_local = self._conanfile.conf[conf_name]
-        # if not allow_local:
-        #    raise ConanException("Current commit {} doesn't exist in remote {}\n"
-        #                         "use '-c {}=1' to allow it".format(commit, remote, conf_name))
-
-        self._conanfile.output.warning("Current commit {} doesn't exist in remote {}\n"
-                                       "This revision will not be buildable in other "
-                                       "computer".format(commit, remote))
+        if self._local_url == "block":
+            raise ConanException(f"Current commit {commit} doesn't exist in remote {remote}\n"
+                                 "Failing according to 'core.scm:local_url=block' conf")
+
+        if self._local_url != "allow":
+            self._conanfile.output.warning("Current commit {} doesn't exist in remote {}\n"
+                                           "This revision will not be buildable in other "
+                                           "computer".format(commit, remote))
         return self.get_repo_root(), commit
 
     def get_repo_root(self):
diff --git a/conans/client/cmd/uploader.py b/conans/client/cmd/uploader.py
index cff1385a9..6c56a69be 100644
--- a/conans/client/cmd/uploader.py
+++ b/conans/client/cmd/uploader.py
@@ -7,7 +7,7 @@ from conan.api.output import ConanOutput
 from conans.client.source import retrieve_exports_sources
 from conans.errors import ConanException, NotFoundException
 from conan.internal.paths import (CONAN_MANIFEST, CONANFILE, EXPORT_SOURCES_TGZ_NAME,
-                          EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME, CONANINFO)
+                                  EXPORT_TGZ_NAME, PACKAGE_TGZ_NAME, CONANINFO)
 from conans.util.files import (clean_dirty, is_dirty, gather_files,
                                gzopen_without_timestamps, set_dirty_context_manager, mkdir,
                                human_size)
@@ -81,10 +81,17 @@ class PackagePreparator:
         self._global_conf = global_conf
 
     def prepare(self, upload_bundle, enabled_remotes):
+        local_url = self._global_conf.get("core.scm:local_url", choices=["allow", "block"])
         for ref, bundle in upload_bundle.refs().items():
             layout = self._app.cache.recipe_layout(ref)
             conanfile_path = layout.conanfile()
             conanfile = self._app.loader.load_basic(conanfile_path)
+            url = conanfile.conan_data.get("scm", {}).get("url") if conanfile.conan_data else None
+            if local_url != "allow" and url is not None:
+                if not any(url.startswith(v) for v in ("ssh", "git", "http", "file")):
+                    raise ConanException(f"Package {ref} contains conandata scm url={url}\n"
+                                         "This isn't a remote URL, the build won't be reproducible\n"
+                                         "Failing because conf 'core.scm:local_url!=allow'")
 
             if bundle.get("upload"):
                 self._prepare_recipe(ref, bundle, conanfile, enabled_remotes)
diff --git a/conans/model/conf.py b/conans/model/conf.py
index 1a36d24a5..c04e48d95 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -52,6 +52,7 @@ BUILT_IN_CONFS = {
     "core.gzip:compresslevel": "The Gzip compression level for Conan artifacts (default=9)",
     # Excluded from revision_mode = "scm" dirty and Git().is_dirty() checks
     "core.scm:excluded": "List of excluded patterns for builtin git dirty checks",
+    "core.scm:local_url": "By default allows to store local folders as remote url, but not upload them. Use 'allow' for allowing upload and 'block' to completely forbid it",
     # Tools
     "tools.android:ndk_path": "Argument for the CMAKE_ANDROID_NDK",
     "tools.android:cmake_legacy_toolchain": "Define to explicitly pass ANDROID_USE_LEGACY_TOOLCHAIN_FILE in CMake toolchain",
@@ -117,7 +118,7 @@ BUILT_IN_CONFS = {
     "tools.apple:enable_visibility": "(boolean) Enable/Disable Visibility Apple Clang flags",
     "tools.env.virtualenv:powershell": "If it is set to True it will generate powershell launchers if os=Windows",
     # Compilers/Flags configurations
-    "tools.build:compiler_executables": "Defines a Python dict-like with the compilers path to be used. Allowed keys {'c', 'cpp', 'cuda', 'objc', 'objcxx', 'rc', 'fortran', 'asm', 'hip', 'ispc', 'ld', 'ar'}",
+    "tools.build:compiler_executables": "Defines a Python dict-like with the compilers path to be used. Allowed keys {'c', 'cpp', 'cuda', 'objc', 'objcxx', 'rc', 'fortran', 'asm', 'hip', 'ispc'}",
     "tools.build:cxxflags": "List of extra CXX flags used by different toolchains like CMakeToolchain, AutotoolsToolchain and MesonToolchain",
     "tools.build:cflags": "List of extra C flags used by different toolchains like CMakeToolchain, AutotoolsToolchain and MesonToolchain",
     "tools.build:defines": "List of extra definition flags used by different toolchains like CMakeToolchain, AutotoolsToolchain and MesonToolchain",
diff --git a/conans/server/crypto/jwt/jwt_credentials_manager.py b/conans/server/crypto/jwt/jwt_credentials_manager.py
index f2699f887..609469875 100644
--- a/conans/server/crypto/jwt/jwt_credentials_manager.py
+++ b/conans/server/crypto/jwt/jwt_credentials_manager.py
@@ -1,19 +1,26 @@
-from conans.server.crypto.jwt.jwt_manager import JWTManager
+from datetime import datetime, timezone
+from calendar import timegm
 
+import jwt
 
-class JWTCredentialsManager(JWTManager):
+
+class JWTCredentialsManager:
     """JWT for manage auth credentials"""
 
     def __init__(self, secret, expire_time):
-        super(JWTCredentialsManager, self).__init__(secret, expire_time)
+        """expire_time is a timedelta
+        secret is a string with the secret encoding key"""
+        self.secret = secret
+        self.expire_time = expire_time
 
-    def get_token_for(self, brl_user):
+    def get_token_for(self, user):
         """Generates a token with the brl_user and additional data dict if needed"""
-        return JWTManager.get_token_for(self, {"user": brl_user})
+        profile_fields = {"user": user,
+                          "exp": timegm((datetime.now(timezone.utc) + self.expire_time).timetuple())}
+        return jwt.encode(profile_fields, self.secret, algorithm="HS256")
 
     def get_user(self, token):
         """Gets the user from credentials object. None if no credentials.
         Can raise jwt.ExpiredSignature and jwt.DecodeError"""
-        profile = self.get_profile(token)
-        username = profile.get("user", None)
-        return username
+        profile = jwt.decode(token, self.secret, algorithms=["HS256"])
+        return profile.get("user", None)
diff --git a/conans/server/crypto/jwt/jwt_manager.py b/conans/server/crypto/jwt/jwt_manager.py
deleted file mode 100644
index fadd972f2..000000000
--- a/conans/server/crypto/jwt/jwt_manager.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from datetime import datetime, timezone
-from calendar import timegm
-
-import jwt
-
-
-class JWTManager:
-    """
-        Handles the JWT token generation and encryption.
-    """
-
-    def __init__(self, secret, expire_time):
-        """expire_time is a timedelta
-           secret is a string with the secret encoding key"""
-        self.secret = secret
-        self.expire_time = expire_time
-
-    def get_token_for(self, profile_fields=None):
-        """Generates a token with the provided fields.
-        if exp is True, expiration time will be used"""
-        profile_fields = profile_fields or {}
-
-        if self.expire_time:
-            profile_fields["exp"] = timegm((datetime.now(timezone.utc)
-                                            + self.expire_time).timetuple())
-
-        return jwt.encode(profile_fields, self.secret, algorithm="HS256")
-
-    def get_profile(self, token):
-        """Gets the user from credentials object. None if no credentials.
-        Can raise jwt.ExpiredSignature and jwt.DecodeError"""
-        profile = jwt.decode(token, self.secret, algorithms=["HS256"])
-        return profile


Test patch:
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain.py b/test/functional/toolchains/cmake/test_cmake_toolchain.py
index acd6c5ee1..b64c5c791 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -1913,3 +1913,106 @@ def test_cmake_toolchain_cxxflags_multi_config():
     assert 'DEFINE conan_test_answer=123' in c.out
     assert 'other=' not in c.out
     assert "CPLUSPLUS: __cplusplus19" in c.out
+
+
+@pytest.mark.tool("ninja")
+@pytest.mark.tool("cmake", "3.23")
+def test_cmake_toolchain_ninja_multi_config():
+    c = TestClient()
+    profile_release = textwrap.dedent(r"""
+        include(default)
+        [conf]
+        tools.cmake.cmaketoolchain:generator=Ninja Multi-Config
+        tools.build:defines=["conan_test_answer=42", "conan_test_other=24"]
+        """)
+    profile_debug = textwrap.dedent(r"""
+        include(default)
+        [settings]
+        build_type=Debug
+        [conf]
+        tools.cmake.cmaketoolchain:generator=Ninja Multi-Config
+        tools.build:defines=["conan_test_answer=123"]
+        """)
+    profile_relwithdebinfo = textwrap.dedent(r"""
+        include(default)
+        [settings]
+        build_type=RelWithDebInfo
+        [conf]
+        tools.cmake.cmaketoolchain:generator=Ninja Multi-Config
+        tools.build:defines=["conan_test_answer=456", "conan_test_other=abc", 'conan_test_complex="1 2"']
+        """)
+
+    conanfile = textwrap.dedent(r'''
+        from conan import ConanFile
+        from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
+
+        class Test(ConanFile):
+            exports_sources = "CMakeLists.txt", "src/*"
+            settings = "os", "compiler", "arch", "build_type"
+            generators = "CMakeToolchain"
+
+            def layout(self):
+                cmake_layout(self)
+
+            def build(self):
+                cmake = CMake(self)
+                cmake.configure()
+                cmake.build()
+        ''')
+
+    main = textwrap.dedent(r"""
+        #include <iostream>
+        #include <stdio.h>
+
+        #define STR(x)   #x
+        #define SHOW_DEFINE(x) printf("DEFINE %s=%s!\n", #x, STR(x))
+
+        int main() {
+            SHOW_DEFINE(conan_test_answer);
+            #ifdef conan_test_other
+            SHOW_DEFINE(conan_test_other);
+            #endif
+            #ifdef conan_test_complex
+            SHOW_DEFINE(conan_test_complex);
+            #endif
+        }
+        """)
+
+    cmakelists = textwrap.dedent("""
+        cmake_minimum_required(VERSION 3.15)
+        project(Test CXX)
+        add_executable(example src/main.cpp)
+        """)
+
+    c.save({"conanfile.py": conanfile,
+            "profile_release": profile_release,
+            "profile_debug": profile_debug,
+            "profile_relwithdebinfo": profile_relwithdebinfo,
+            "src/main.cpp": main,
+            "CMakeLists.txt": cmakelists})
+    c.run("install . -pr=./profile_release")
+    c.run("install . -pr=./profile_debug")
+    c.run("install . -pr=./profile_relwithdebinfo")
+
+    with c.chdir("build"):
+        env = r".\generators\conanbuild.bat &&" if platform.system() == "Windows" else ""
+        c.run_command(f"{env} cmake .. -G \"Ninja Multi-Config\" "
+                      "-DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake")
+        c.run_command(f"{env} cmake --build . --config Release")
+        c.run_command(f"{env} cmake --build . --config Debug")
+        c.run_command(f"{env} cmake --build . --config RelWithDebInfo")
+
+    c.run_command(os.sep.join([".", "build", "Release", "example"]))
+    assert 'DEFINE conan_test_answer=42!' in c.out
+    assert 'DEFINE conan_test_other=24!' in c.out
+    assert 'complex=' not in c.out
+
+    c.run_command(os.sep.join([".", "build", "Debug", "example"]))
+    assert 'DEFINE conan_test_answer=123' in c.out
+    assert 'other=' not in c.out
+    assert 'complex=' not in c.out
+
+    c.run_command(os.sep.join([".", "build", "RelWithDebInfo", "example"]))
+    assert 'DEFINE conan_test_answer=456!' in c.out
+    assert 'DEFINE conan_test_other=abc!' in c.out
+    assert 'DEFINE conan_test_complex="1 2"!' in c.out
diff --git a/test/functional/tools/scm/test_git.py b/test/functional/tools/scm/test_git.py
index af7336054..c566270d8 100644
--- a/test/functional/tools/scm/test_git.py
+++ b/test/functional/tools/scm/test_git.py
@@ -617,7 +617,7 @@ class TestGitBasicSCMFlow:
                                                    "CMakeLists.txt": "mycmake"}, folder=folder)
 
         c = TestClient(default_server_user=True)
-        c.run_command('git clone "{}" .'.format(url))
+        c.run_command('git clone "file://{}" .'.format(url))
         c.run("create .")
         assert "pkg/0.1: MYCMAKE: mycmake" in c.out
         assert "pkg/0.1: MYFILE: myheader!" in c.out
@@ -648,7 +648,7 @@ class TestGitBasicSCMFlow:
         conanfile = self.conanfile_scm if conanfile_scm else self.conanfile_full
         url = git_create_bare_repo()
         c = TestClient(default_server_user=True)
-        c.run_command('git clone "{}" .'.format(url))
+        c.run_command('git clone "file://{}" .'.format(url))
         c.save({"conanfile.py": conanfile,
                 "src/myfile.h": "myheader!",
                 "CMakeLists.txt": "mycmake"})
diff --git a/test/integration/command/user_test.py b/test/integration/command/user_test.py
index b5e4857a1..960ba8640 100644
--- a/test/integration/command/user_test.py
+++ b/test/integration/command/user_test.py
@@ -418,7 +418,7 @@ class TestRemoteAuth:
 
     def test_remote_auth_server_expire_token(self):
         server = TestServer(users={"myuser": "password", "myotheruser": "otherpass"})
-        server.test_server.ra.api_v2.credentials_manager.expire_time = timedelta(seconds=1)
+        server.test_server.ra.api_v2.credentials_manager.expire_time = timedelta(seconds=2)
         c = TestClient(servers={"default": server}, inputs=["myuser", "password",
                                                             "myotheruser", "otherpass",
                                                             "user", "pass", "user", "pass",
@@ -429,10 +429,10 @@ class TestRemoteAuth:
         c.run("remote auth *")
         assert "user: myuser" in c.out
         # Token should expire
-        time.sleep(2)
+        time.sleep(2.5)
         c.run("remote auth *")
         assert "user: myotheruser" in c.out
         # Token should expire
-        time.sleep(2)
+        time.sleep(2.5)
         c.run("remote auth *")
         assert "error: Too many failed login attempts, bye!" in c.out
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index dee27284e..38ac9049b 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1278,6 +1278,32 @@ def test_build_folder_vars_self_name_version():
     assert "conan-windows-pkg-0.1-debug" in presets
 
 
+def test_build_folder_vars_constants_user():
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.cmake import cmake_layout
+
+        class Conan(ConanFile):
+            name = "dep"
+            version = "0.1"
+            settings = "os", "build_type"
+            generators = "CMakeToolchain"
+
+            def layout(self):
+                cmake_layout(self)
+        """)
+    c.save({"conanfile.py": conanfile})
+    conf = "tools.cmake.cmake_layout:build_folder_vars='[\"const.myvalue\"]'"
+    settings = " -s os=FreeBSD -s arch=armv8 -s build_type=Debug"
+    c.run("install . -c={} {}".format(conf, settings))
+    assert "cmake --preset conan-myvalue-debug" in c.out
+    assert os.path.exists(os.path.join(c.current_folder, "build", "myvalue", "Debug"))
+    presets = load(os.path.join(c.current_folder,
+                                "build/myvalue/Debug/generators/CMakePresets.json"))
+    assert "conan-myvalue-debug" in presets
+
+
 def test_extra_flags():
     client = TestClient()
     conanfile = textwrap.dedent("""
diff --git a/test/integration/toolchains/gnu/test_gnutoolchain.py b/test/integration/toolchains/gnu/test_gnutoolchain.py
index 653599ae6..d35d45b04 100644
--- a/test/integration/toolchains/gnu/test_gnutoolchain.py
+++ b/test/integration/toolchains/gnu/test_gnutoolchain.py
@@ -381,8 +381,6 @@ def test_msvc_profile_defaults():
             # Default values and conf ones
             assert r'set "CC=clang"' in content  # conf value has precedence
             assert r'set "CXX=clang++"' in content  # conf value has precedence
-            assert 'set "LD=link -nologo"' in content
-            assert r'set "AR=lib"' in content
             assert 'set "NM=dumpbin -symbols"' in content
             assert 'set "OBJDUMP=:"' in content
             assert 'set "RANLIB=:"' in content
@@ -412,8 +410,6 @@ def test_msvc_profile_defaults():
             # Prepending compiler wrappers
             tc.extra_env.prepend("CC", "compile")
             tc.extra_env.prepend("CXX", "compile")
-            tc.extra_env.prepend("AR", "ar-lib")
-            tc.extra_env.append("LD", "-fixed")
             tc.extra_env.define("OBJDUMP", "other-value")
             tc.extra_env.unset("RANLIB")
             tc.generate()
@@ -424,8 +420,6 @@ def test_msvc_profile_defaults():
             # Default values
             assert r'set "CC=compile clang"' in content
             assert r'set "CXX=compile clang++"' in content
-            assert 'set "LD=link -nologo -fixed"' in content  # appended new value
-            assert r'set "AR=ar-lib lib"' in content
             assert 'set "NM=dumpbin -symbols"' in content
             assert 'set "OBJDUMP=other-value"' in content  # redefined
             assert 'set "RANLIB=:"' not in content  # removed
diff --git a/test/integration/toolchains/gnu/test_makedeps.py b/test/integration/toolchains/gnu/test_makedeps.py
index 948174280..31fd5d3a6 100644
--- a/test/integration/toolchains/gnu/test_makedeps.py
+++ b/test/integration/toolchains/gnu/test_makedeps.py
@@ -30,6 +30,8 @@ def test_make_dirs_with_abs_path():
                 lib_dir2 = os.path.join(self.package_folder, "lib2")
                 self.cpp_info.includedirs = [include_dir]
                 self.cpp_info.libdirs = [lib_dir, lib_dir2]
+                self.cpp_info.set_property("my_prop", "my prop value")
+                self.cpp_info.set_property("my_prop_with_newline", "my\\nprop")
         """)
     client = TestClient()
     client.save({"conanfile.py": conanfile})
@@ -44,6 +46,9 @@ def test_make_dirs_with_abs_path():
     assert f'CONAN_LIB_DIRS_MYLIB = \\\n\t$(CONAN_LIB_DIR_FLAG){prefix}/my_absoulte_path/fake/mylib/lib \\\n\t$(CONAN_LIB_DIR_FLAG)$(CONAN_ROOT_MYLIB)/lib2' in makefile_content
     assert f'CONAN_INCLUDE_DIRS_MYLIB = $(CONAN_INCLUDE_DIR_FLAG){prefix}/my_absoulte_path/fake/mylib/include' in makefile_content
     assert 'CONAN_BIN_DIRS_MYLIB = $(CONAN_BIN_DIR_FLAG)$(CONAN_ROOT_MYLIB)/bin' in makefile_content
+    assert 'CONAN_PROPERTY_MYLIB_MY_PROP = my prop value' in makefile_content
+    assert 'CONAN_PROPERTY_MYLIB_MY_PROP_WITH_NEWLINE' not in makefile_content
+    assert "WARN: Skipping propery 'my_prop_with_newline' because it contains newline" in client.stderr
 
     lines = makefile_content.splitlines()
     for line_no, line in enumerate(lines):
@@ -90,6 +95,7 @@ def test_make_empty_dirs():
     assert 'CONAN_BIN_DIRS' not in makefile_content
     assert 'CONAN_LIBS' not in makefile_content
     assert 'CONAN_FRAMEWORK_DIRS' not in makefile_content
+    assert 'CONAN_PROPERTY' not in makefile_content
 
 
 def test_libs_and_system_libs():
@@ -197,6 +203,8 @@ def test_make_with_public_deps_and_component_requires():
             def package_info(self):
                 self.cpp_info.components["mycomponent"].requires.append("lib::cmp1")
                 self.cpp_info.components["myfirstcomp"].requires.append("mycomponent")
+                self.cpp_info.components["myfirstcomp"].set_property("my_prop", "my prop value")
+                self.cpp_info.components["myfirstcomp"].set_property("my_prop_with_newline", "my\\nprop")
 
         """)
     client.save({"conanfile.py": conanfile}, clean_first=True)
@@ -236,6 +244,10 @@ def test_make_with_public_deps_and_component_requires():
     assert 'CONAN_REQUIRES = $(CONAN_REQUIRES_SECOND)\n' in makefile_content
     assert 'CONAN_LIBS = $(CONAN_LIBS_LIB)\n' in makefile_content
 
+    assert 'CONAN_PROPERTY_SECOND_MYFIRSTCOMP_MY_PROP = my prop value\n' in makefile_content
+    assert 'CONAN_PROPERTY_SECOND_MYFIRSTCOMP_MY_PROP_WITH_NEWLINE' not in makefile_content
+    assert "WARN: Skipping propery 'my_prop_with_newline' because it contains newline" in client2.stderr
+
 
 def test_make_with_public_deps_and_component_requires_second():
     """
diff --git a/test/unittests/server/crypto/jwt_test.py b/test/unittests/server/crypto/jwt_test.py
index 9c6b916a4..d1253232b 100644
--- a/test/unittests/server/crypto/jwt_test.py
+++ b/test/unittests/server/crypto/jwt_test.py
@@ -1,40 +1,26 @@
 import time
-import unittest
 from datetime import timedelta
 
 import jwt
+import pytest
 from jwt import DecodeError
 
 from conans.server.crypto.jwt.jwt_credentials_manager import JWTCredentialsManager
-from conans.server.crypto.jwt.jwt_manager import JWTManager
 
 
-class JwtTest(unittest.TestCase):
+def test_jwt_manager():
+    # Instance the manager to generate tokens that expires in 10 ms
+    manager = JWTCredentialsManager(secret="1234asdf", expire_time=timedelta(seconds=1))
 
-    def setUp(self):
-        unittest.TestCase.setUp(self)
-        self.secret = "123123123qweqwe"
-        self.expire_time = timedelta(seconds=1)  # No lower resolution available
+    # Encrypt a profile
+    token = manager.get_token_for("myuser")
 
-    def test_jwt_manager(self):
-        # Instance the manager to generate tokens that expires in 10 ms
+    # Decrypt the profile
+    assert "myuser" == manager.get_user(token)
+    with pytest.raises(DecodeError):
+        manager.get_user("invalid_user")
 
-        manager = JWTManager(self.secret, self.expire_time)
-
-        # Encrypt a profile
-        profile = {"hello": "world"}
-        token = manager.get_token_for(profile)
-
-        # Decrypt the profile
-        decrypted_profile = manager.get_profile(token)
-        self.assertEqual(profile, decrypted_profile)
-
-        # Now wait 2 seconds and check if its valid now
-        time.sleep(2)
-        self.assertRaises(jwt.ExpiredSignatureError, manager.get_profile, token)
-
-    def test_jwt_credentials_manager(self):
-        manager = JWTCredentialsManager(self.secret, self.expire_time)
-        token = manager.get_token_for("lasote")
-        self.assertEqual(manager.get_user(token), "lasote")
-        self.assertRaises(DecodeError, manager.get_user, "invalid_user")
+    # Now wait 2 seconds and check if its valid now
+    time.sleep(2)
+    with pytest.raises(jwt.ExpiredSignatureError):
+        manager.get_user(token)