Repository: conan-io/conan
Pull request #17849 (2025-02-25 16:07:10)

VCVars: Fix generation if OS is set to WindowsStore
Changelog: Fix: Fixes VCVars vcvarsall.bat generation if OS is set to WindowsStore
- added test for VCVars generator targeting armv8 WindowsStore

- [ ] Refer to the issue that supports this Pull Request.
- [ ] 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.


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/conan-io/conan?pullRequest=17849) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/conan-io/conan?pullRequest=17849) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/conan-io/conan?pullRequest=17849) it.</sub>

Patch:
diff --git a/conan/__init__.py b/conan/__init__.py
index 3b5b2a722..64d0beef5 100644
--- a/conan/__init__.py
+++ b/conan/__init__.py
@@ -2,5 +2,5 @@ from conan.internal.model.conan_file import ConanFile
 from conan.internal.model.workspace import Workspace
 from conan.internal.model.version import Version
 
-__version__ = '2.13.0-dev'
+__version__ = '2.14.0-dev'
 conan_version = Version(__version__)
diff --git a/conan/api/conan_api.py b/conan/api/conan_api.py
index 30fe82cc2..8a518b21d 100644
--- a/conan/api/conan_api.py
+++ b/conan/api/conan_api.py
@@ -1,3 +1,4 @@
+import os
 import sys
 
 from conan.api.output import init_colorama
@@ -35,6 +36,8 @@ class ConanAPI:
         version = sys.version_info
         if version.major == 2 or version.minor < 6:
             raise ConanException("Conan needs Python >= 3.6")
+        if cache_folder is not None and not os.path.isabs(cache_folder):
+            raise ConanException("cache_folder has to be an absolute path")
 
         init_colorama(sys.stderr)
         self.workspace = WorkspaceAPI(self)
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
index c42d9f060..b5ef89ff5 100644
--- a/conan/tools/env/environment.py
+++ b/conan/tools/env/environment.py
@@ -84,18 +84,19 @@ class _EnvValue:
     def dumps(self):
         result = []
         path = "(path)" if self._path else ""
+        sep = f"(sep={self._sep})" if self._sep != " " and not self._path else ""
         if not self._values:  # Empty means unset
             result.append("{}=!".format(self._name))
         elif _EnvVarPlaceHolder in self._values:
             index = self._values.index(_EnvVarPlaceHolder)
             for v in self._values[:index]:
-                result.append("{}=+{}{}".format(self._name, path, v))
+                result.append("{}=+{}{}{}".format(self._name, path, sep, v))
             for v in self._values[index+1:]:
-                result.append("{}+={}{}".format(self._name, path, v))
+                result.append("{}+={}{}{}".format(self._name, path, sep, v))
         else:
             append = ""
             for v in self._values:
-                result.append("{}{}={}{}".format(self._name, append, path, v))
+                result.append("{}{}={}{}{}".format(self._name, append, path, sep, v))
                 append = "+"
         return "\n".join(result)
 
@@ -644,6 +645,14 @@ class ProfileEnvironment:
                 env = Environment()
                 if method == "unset":
                     env.unset(name)
+                elif value.strip().startswith("(sep="):
+                    value = value.strip()
+                    sep = value[5]
+                    value = value[7:]
+                    if value.strip().startswith("(path)"):
+                        msg = f"Cannot use (sep) and (path) qualifiers simultaneously: {line}"
+                        raise ConanException(msg)
+                    getattr(env, method)(name, value, separator=sep)
                 else:
                     if value.strip().startswith("(path)"):
                         value = value.strip()
diff --git a/conan/tools/microsoft/visual.py b/conan/tools/microsoft/visual.py
index 930c3aab8..3a0c9e3b5 100644
--- a/conan/tools/microsoft/visual.py
+++ b/conan/tools/microsoft/visual.py
@@ -117,7 +117,7 @@ class VCVars:
         os_ = conanfile.settings.get_safe("os")
         build_os_ = conanfile.settings_build.get_safe("os")
 
-        if os_ != "Windows" or build_os_ != "Windows":
+        if (os_ != "Windows" and os_ != "WindowsStore") or build_os_ != "Windows":
             return
 
         compiler = conanfile.settings.get_safe("compiler")


Test patch:
diff --git a/test/functional/subsystems_build_test.py b/test/functional/subsystems_build_test.py
index c5f8336f7..0b396663e 100644
--- a/test/functional/subsystems_build_test.py
+++ b/test/functional/subsystems_build_test.py
@@ -83,6 +83,7 @@ class TestSubsystems:
         client.run_command('uname', assert_error=True)
         assert "'uname' is not recognized as an internal or external command" in client.out
 
+
 @pytest.mark.skipif(platform.system() != "Windows", reason="Tests Windows Subsystems")
 class TestSubsystemsBuild:
 
@@ -512,3 +513,49 @@ class TestSubsystemsCMakeBuild:
         self._build(client, generator="Visual Studio 17 2022", toolset="ClangCL")
         check_exe_run(client.out, "main", "clang", None, "Debug", "x86_64", None, subsystem=None)
         check_vs_runtime("Debug/app.exe", client, "15", "Debug", subsystem=None)
+
+
+@pytest.mark.tool("msys2")
+def test_msys2_env_vars_paths():
+    c = TestClient()
+    # A tool-requires injecting PATHs for native, should not use "_path" calls, and use
+    # 'separator=;' explicitly
+    tool = textwrap.dedent("""
+        from conan import ConanFile
+        class HelloConan(ConanFile):
+            name = "tool"
+            version = "0.1"
+            def package_info(self):
+                self.buildenv_info.append("INCLUDE", "C:/mytool/path", separator=";")
+        """)
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class HelloConan(ConanFile):
+            win_bash = True
+            tool_requires = "tool/0.1"
+
+            def build(self):
+                self.run('echo "INCLUDE=$INCLUDE"')
+        """)
+    profile = textwrap.dedent("""
+        [conf]
+        tools.microsoft.bash:subsystem=msys2
+        tools.microsoft.bash:path=bash
+
+        [buildenv]
+        INCLUDE=+(sep=;)C:/prepended/path
+        INCLUDE+=(sep=;)C:/appended/path
+        """)
+    c.save({"tool/conanfile.py": tool,
+            "consumer/conanfile.py": conanfile,
+            "profile": profile})
+    c.run("create tool")
+    with environment_update({"INCLUDE": "C:/my/abs path/folder;C:/other path/subfolder"}):
+        c.run("build consumer -pr=profile")
+
+    # Check the profile is outputed correctly
+    assert "INCLUDE=+(sep=;)C:/prepended/path" in c.out
+    assert "INCLUDE+=(sep=;)C:/appended/path" in c.out
+    # check the composition is correct
+    assert "INCLUDE=C:/prepended/path;C:/my/abs path/folder;C:/other path/subfolder;" \
+           "C:/mytool/path;C:/appended/path" in c.out
diff --git a/test/integration/conan_api/test_profile_api.py b/test/integration/conan_api/test_profile_api.py
new file mode 100644
index 000000000..4f06d9d10
--- /dev/null
+++ b/test/integration/conan_api/test_profile_api.py
@@ -0,0 +1,11 @@
+import pytest
+
+from conan.api.conan_api import ConanAPI
+from conan.errors import ConanException
+
+
+def test_profile_api():
+    # It must be an absolute path
+    with pytest.raises(ConanException) as e:
+        ConanAPI(cache_folder="test")
+    assert "cache_folder has to be an absolute path" in str(e.value)
diff --git a/test/integration/lockfile/test_user_overrides.py b/test/integration/lockfile/test_user_overrides.py
index f41a3a979..69b1cbed1 100644
--- a/test/integration/lockfile/test_user_overrides.py
+++ b/test/integration/lockfile/test_user_overrides.py
@@ -435,3 +435,25 @@ class TestLockUpgrade:
         lock = c.load("conan.lock")
         assert "libd/1.1" in lock
         assert "libd/1.2" not in lock
+
+
+    def test_lock_upgrade_new_requirement(self):
+        c = TestClient(light=True)
+        c.save({"liba/conanfile.py": GenConanfile("liba").with_requires("libb/1.0"),
+                "libb/conanfile.py": GenConanfile("libb").with_requires("libc/1.0"),
+                "libc/conanfile.py": GenConanfile("libc"),
+                "libd/conanfile.py": GenConanfile("libd")})
+        c.run(f"export liba --version=1.0")
+        c.run(f"export libb --version=1.0")
+        c.run(f"export libc --version=1.0")
+        c.run(f"export libd --version=1.0")
+        c.save({f"conanfile.py": GenConanfile().with_requires(f"liba/[>=1.0 <2]")})
+        c.run("lock create .")
+
+        c.save({"libb/conanfile.py": GenConanfile("libb").with_requires("libd/1.0")})
+        c.run(f"export libb --version=2.0")
+        c.run("lock upgrade --requires='libb/[>=2]' --update-requires='libb/*'")
+        lock = c.load("conan.lock")
+        assert "libb/2.0" in lock
+        assert "libd/1.0" in lock
+        assert "libc/1.0" in lock  # TODO: libc should be removed from lockfile? It is not required anymore...
diff --git a/test/integration/toolchains/microsoft/vcvars_test.py b/test/integration/toolchains/microsoft/vcvars_test.py
index 16b1c474c..d61378471 100644
--- a/test/integration/toolchains/microsoft/vcvars_test.py
+++ b/test/integration/toolchains/microsoft/vcvars_test.py
@@ -160,3 +160,21 @@ def test_vcvars_compiler_update():
 
     vcvars = client.load("conanvcvars.bat")
     assert 'vcvarsall.bat"  amd64 -vcvars_ver=14.33' in vcvars
+
+@pytest.mark.skipif(platform.system() != "Windows", reason="Requires Windows")
+def test_vcvars_armv8_windows_store():
+    client = TestClient(path_with_spaces=False)
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            generators = "VCVars"
+            settings = "os", "compiler", "arch", "build_type"
+    """)
+    client.save({"conanfile.py": conanfile})
+    client.run('install . -s:b os=Windows -s compiler="msvc" -s compiler.version=194 '
+               '-s compiler.cppstd=14 -s compiler.runtime=static -s:h arch=armv8 '
+               '-s:h os=WindowsStore -s:h os.version=10.0')
+
+    vcvars = client.load("conanvcvars.bat")
+    assert 'vcvarsall.bat"  amd64_arm64' in vcvars
diff --git a/test/unittests/tools/env/test_env.py b/test/unittests/tools/env/test_env.py
index 156484905..e673c3bad 100644
--- a/test/unittests/tools/env/test_env.py
+++ b/test/unittests/tools/env/test_env.py
@@ -5,6 +5,7 @@ import textwrap
 
 import pytest
 
+from conan.errors import ConanException
 from conan.tools.env import Environment
 from conan.tools.env.environment import ProfileEnvironment
 from conans.client.subsystems import WINDOWS
@@ -426,3 +427,26 @@ def test_custom_placeholder():
            f"$penv{{MyVar}}:MyValue"
     items = {k: v for k, v in env.items(variable_reference="$penv{{{name}}}")}
     assert items == {"MyVar": f"$penv{{MyVar}}:MyValue"}
+
+
+class TestProfileSeparators:
+
+    def test_define(self):
+        myprofile = "MyPath1 = (sep=@)/my/path1"
+        env = ProfileEnvironment.loads(myprofile).get_profile_env(ref="")
+        assert env.dumps() == "MyPath1=(sep=@)/my/path1"
+        env.append("MyPath1", "/other/value")
+        assert env.vars(ConanFileMock()).get("MyPath1") == "/my/path1@/other/value"
+
+    def test_append(self):
+        myprofile = "MyPath1 +=(sep=@)/my/path1"
+        env = ProfileEnvironment.loads(myprofile).get_profile_env(ref="")
+        assert env.dumps() == "MyPath1+=(sep=@)/my/path1"
+        env.append("MyPath1", "/other/value")
+        assert env.vars(ConanFileMock()).get("MyPath1") == "/my/path1@/other/value"
+
+    def test_error(self):
+        myprofile = "MyPath1 = (sep=@) (path)/my/path1"
+        with pytest.raises(ConanException) as e:
+            ProfileEnvironment.loads(myprofile)
+        assert "Cannot use (sep) and (path) qualifiers simultaneously" in str(e.value)