Repository: conan-io/conan
Pull request #17868 (2025-02-27 23:49:51)

Fix cmake target not generated with CMake2 generator when only system…
…_libs are defined


When trying to use the recipes in conan-center-index which refer to system version   such as xorg/system   or opengl/system, the generated target cmake file do not contain the required target when using the CMakeDep2 generator as it only defined system_libs in cpp_info.  This PR attempt to fix this



CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/conan-io/conan?pullRequest=17868) <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=17868) 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=17868) it.</sub>

Patch:
diff --git a/conan/api/conan_api.py b/conan/api/conan_api.py
index 8a518b21d..719c81fd9 100644
--- a/conan/api/conan_api.py
+++ b/conan/api/conan_api.py
@@ -32,6 +32,10 @@ class ConanAPI:
     not be created directly.
     """
     def __init__(self, cache_folder=None):
+        """
+        :param cache_folder: Conan cache/home folder. It will have less priority than the
+                             "home_folder" defined in a Workspace.
+        """
 
         version = sys.version_info
         if version.major == 2 or version.minor < 6:
diff --git a/conan/api/subapi/config.py b/conan/api/subapi/config.py
index 5f4abcb65..f3645013d 100644
--- a/conan/api/subapi/config.py
+++ b/conan/api/subapi/config.py
@@ -20,6 +20,7 @@ from conan.internal.model.conf import ConfDefinition, BUILT_IN_CONFS, CORE_CONF_
 from conan.internal.model.pkg_type import PackageType
 from conan.api.model import RecipeReference
 from conan.internal.model.settings import Settings
+from conans.client.hook_manager import HookManager
 from conans.util.files import load, save, rmdir, remove
 
 
@@ -29,6 +30,7 @@ class ConfigAPI:
         self.conan_api = conan_api
         self._new_config = None
         self._cli_core_confs = None
+        self.hook_manager = HookManager(HomePaths(conan_api.home_folder).hooks_path)
 
     def home(self):
         return self.conan_api.cache_folder
@@ -234,3 +236,4 @@ class ConfigAPI:
         if self._new_config is not None:
             self._new_config.clear()
             self._populate_global_conf()
+        self.hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
diff --git a/conan/api/subapi/export.py b/conan/api/subapi/export.py
index 5d1d16878..ce08a8b0f 100644
--- a/conan/api/subapi/export.py
+++ b/conan/api/subapi/export.py
@@ -1,11 +1,9 @@
 from conan.api.output import ConanOutput
 from conan.internal.cache.cache import PkgCache
-from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanApp
 from conan.internal.api.export import cmd_export
 from conan.internal.methods import run_package_method
 from conans.client.graph.graph import BINARY_BUILD, RECIPE_INCACHE
-from conans.client.hook_manager import HookManager
 from conan.api.model import PkgReference
 from conans.util.files import mkdir
 
@@ -18,13 +16,13 @@ class ExportAPI:
     def export(self, path, name, version, user, channel, lockfile=None, remotes=None):
         ConanOutput().title("Exporting recipe to the cache")
         app = ConanApp(self.conan_api)
-        hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
+        hook_manager = self.conan_api.config.hook_manager
         return cmd_export(app, hook_manager, self.conan_api.config.global_conf, path, name, version,
                           user, channel, graph_lock=lockfile, remotes=remotes)
 
     def export_pkg(self, deps_graph, source_folder, output_folder):
         cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
-        hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
+        hook_manager = self.conan_api.config.hook_manager
 
         # The graph has to be loaded with build_mode=[ref.name], so that node is not tried
         # to be downloaded from remotes
diff --git a/conan/api/subapi/install.py b/conan/api/subapi/install.py
index d3190f07d..873a9db17 100644
--- a/conan/api/subapi/install.py
+++ b/conan/api/subapi/install.py
@@ -1,12 +1,10 @@
 import os
 
 from conan.internal.api.install.generators import write_generators
-from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanBasicApp
 from conan.internal.deploy import do_deploys
 
 from conans.client.graph.install_graph import InstallGraph
-from conans.client.hook_manager import HookManager
 from conans.client.installer import BinaryInstaller
 from conan.errors import ConanInvalidConfiguration
 
@@ -22,7 +20,8 @@ class InstallAPI:
         :param remotes:
         """
         app = ConanBasicApp(self.conan_api)
-        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages)
+        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages,
+                                    self.conan_api.config.hook_manager)
         install_graph = InstallGraph(deps_graph)
         install_graph.raise_errors()
         install_order = install_graph.install_order()
@@ -35,7 +34,8 @@ class InstallAPI:
         :param graph: Dependency graph to intall packages for
         """
         app = ConanBasicApp(self.conan_api)
-        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages)
+        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages,
+                                    self.conan_api.config.hook_manager)
         installer.install_system_requires(graph, only_info)
 
     def install_sources(self, graph, remotes):
@@ -45,7 +45,8 @@ class InstallAPI:
         :param graph: Dependency graph to install packages for
         """
         app = ConanBasicApp(self.conan_api)
-        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages)
+        installer = BinaryInstaller(app, self.conan_api.config.global_conf, app.editable_packages,
+                                    self.conan_api.config.hook_manager)
         installer.install_sources(graph, remotes)
 
     # TODO: Look for a better name
@@ -86,7 +87,7 @@ class InstallAPI:
             if gen not in final_generators:
                 final_generators.append(gen)
         conanfile.generators = final_generators
-        hook_manager = HookManager(HomePaths(self.conan_api.home_folder).hooks_path)
+        hook_manager = self.conan_api.config.hook_manager
         write_generators(conanfile, hook_manager, self.conan_api.home_folder,
                          envs_generation=envs_generation)
 
diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py
index 71dc48677..ea839fdbd 100644
--- a/conan/api/subapi/local.py
+++ b/conan/api/subapi/local.py
@@ -1,7 +1,6 @@
 import os
 
 from conan.cli import make_abs_path
-from conan.internal.cache.home_paths import HomePaths
 from conan.internal.conan_app import ConanApp
 from conan.internal.api.local.editable import EditablePackages
 from conan.internal.methods import run_build_method, run_source_method
@@ -9,7 +8,6 @@ from conans.client.graph.graph import CONTEXT_HOST
 from conans.client.graph.profile_node_definer import initialize_conanfile_profile
 from conan.internal.errors import conanfile_exception_formatter
 from conan.errors import ConanException
-from conans.client.hook_manager import HookManager
 from conan.api.model import RecipeReference
 from conans.util.files import chdir
 
@@ -93,13 +91,13 @@ class LocalAPI:
         conanfile.folders.set_base_build(None)
         conanfile.folders.set_base_package(None)
 
-        hook_manager = HookManager(HomePaths(self._conan_api.home_folder).hooks_path)
+        hook_manager = self._conan_api.config.hook_manager
         run_source_method(conanfile, hook_manager)
 
     def build(self, conanfile):
         """ calls the 'build()' method of the current (user folder) conanfile.py
         """
-        hook_manager = HookManager(HomePaths(self._conan_api.home_folder).hooks_path)
+        hook_manager = self._conan_api.config.hook_manager
         conanfile.folders.set_base_package(conanfile.folders.base_build)
         conanfile.folders.set_base_pkg_metadata(os.path.join(conanfile.build_folder, "metadata"))
         run_build_method(conanfile, hook_manager)
diff --git a/conan/internal/conan_app.py b/conan/internal/conan_app.py
index 72196af08..e61dcb687 100644
--- a/conan/internal/conan_app.py
+++ b/conan/internal/conan_app.py
@@ -1,7 +1,9 @@
 import os
 
+from conan.internal.api.local.editable import EditablePackages
 from conan.internal.cache.cache import PkgCache
 from conan.internal.cache.home_paths import HomePaths
+from conan.internal.model.conf import ConfDefinition
 from conans.client.graph.proxy import ConanProxy
 from conans.client.graph.python_requires import PyRequireLoader
 from conans.client.graph.range_resolver import RangeResolver
@@ -69,3 +71,22 @@ class ConanApp(ConanBasicApp):
         conanfile_helpers = ConanFileHelpers(conan_api.remotes.requester, cmd_wrap, self.global_conf,
                                              self.cache, self.cache_folder)
         self.loader = ConanFileLoader(self.pyreq_loader, conanfile_helpers)
+
+
+class LocalRecipesIndexApp:
+    """
+    Simplified one, without full API, for the LocalRecipesIndex. Only publicly used fields are:
+    - cache
+    - loader (for the export phase of local-recipes-index)
+    The others are internally use by other collaborators
+    """
+    def __init__(self, cache_folder):
+        self.global_conf = ConfDefinition()
+        self.cache = PkgCache(cache_folder, self.global_conf)
+        self.remote_manager = RemoteManager(self.cache, auth_manager=None, home_folder=cache_folder)
+        editable_packages = EditablePackages()
+        self.proxy = ConanProxy(self, editable_packages)
+        self.range_resolver = RangeResolver(self, self.global_conf, editable_packages)
+        pyreq_loader = PyRequireLoader(self, self.global_conf)
+        helpers = ConanFileHelpers(None, CmdWrapper(""), self.global_conf, self.cache, cache_folder)
+        self.loader = ConanFileLoader(pyreq_loader, helpers)
diff --git a/conan/tools/cmake/cmakedeps2/target_configuration.py b/conan/tools/cmake/cmakedeps2/target_configuration.py
index dd3b7670a..9af4ea614 100644
--- a/conan/tools/cmake/cmakedeps2/target_configuration.py
+++ b/conan/tools/cmake/cmakedeps2/target_configuration.py
@@ -154,7 +154,7 @@ class TargetConfigurationTemplate2:
         return libs
 
     def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):
-        if info.exe or not (info.includedirs or info.libs):
+        if info.exe or not (info.includedirs or info.libs or info.system_libs):
             return
 
         includedirs = ";".join(self._path(i, pkg_folder, pkg_folder_var)
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
index b5ef89ff5..8659141c5 100644
--- a/conan/tools/env/environment.py
+++ b/conan/tools/env/environment.py
@@ -89,7 +89,7 @@ class _EnvValue:
             result.append("{}=!".format(self._name))
         elif _EnvVarPlaceHolder in self._values:
             index = self._values.index(_EnvVarPlaceHolder)
-            for v in self._values[:index]:
+            for v in reversed(self._values[:index]):  # Reverse to prepend
                 result.append("{}=+{}{}{}".format(self._name, path, sep, v))
             for v in self._values[index+1:]:
                 result.append("{}+={}{}{}".format(self._name, path, sep, v))
diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py
index 51ac37afa..af016a0e8 100644
--- a/conans/client/graph/graph_builder.py
+++ b/conans/client/graph/graph_builder.py
@@ -137,7 +137,9 @@ class DepsGraphBuilder(object):
         version_range = require.version_range
         prev_version_range = prev_require.version_range if prev_node is None else None
         if version_range:
-            # TODO: Check user/channel conflicts first
+            if require.ref.user != prev_require.ref.user or \
+                    require.ref.channel != prev_require.ref.channel:
+                raise GraphConflictError(node, require, prev_node, prev_require, base_previous)
             if prev_version_range is not None:
                 # It it is not conflicting, but range can be incompatible, restrict range
                 restricted_version_range = version_range.intersection(prev_version_range)
@@ -149,9 +151,10 @@ class DepsGraphBuilder(object):
                     require.ref = prev_ref
                 else:
                     raise GraphConflictError(node, require, prev_node, prev_require, base_previous)
-
         elif prev_version_range is not None:
-            # TODO: Check user/channel conflicts first
+            if require.ref.user != prev_require.ref.user or \
+                    require.ref.channel != prev_require.ref.channel:
+                raise GraphConflictError(node, require, prev_node, prev_require, base_previous)
             if not prev_version_range.contains(require.ref.version, resolve_prereleases):
                 raise GraphConflictError(node, require, prev_node, prev_require, base_previous)
         else:
diff --git a/conans/client/installer.py b/conans/client/installer.py
index 2c70382fd..5ffd1faf5 100644
--- a/conans/client/installer.py
+++ b/conans/client/installer.py
@@ -3,13 +3,11 @@ import shutil
 from multiprocessing.pool import ThreadPool
 
 from conan.api.output import ConanOutput
-from conan.internal.cache.home_paths import HomePaths
 from conan.internal.methods import run_build_method, run_package_method
 from conan.internal.api.install.generators import write_generators
 from conans.client.graph.graph import BINARY_BUILD, BINARY_CACHE, BINARY_DOWNLOAD, BINARY_EDITABLE, \
     BINARY_UPDATE, BINARY_EDITABLE_BUILD, BINARY_SKIP
 from conans.client.graph.install_graph import InstallGraph
-from conans.client.hook_manager import HookManager
 from conans.client.source import retrieve_exports_sources, config_source
 from conan.internal.errors import conanfile_remove_attr, conanfile_exception_formatter
 from conan.errors import ConanException
@@ -170,12 +168,12 @@ class BinaryInstaller:
     locally in case they are not found in remotes
     """
 
-    def __init__(self, app, global_conf, editable_packages):
+    def __init__(self, app, global_conf, editable_packages, hook_manager):
         self._app = app
         self._editable_packages = editable_packages
         self._cache = app.cache
         self._remote_manager = app.remote_manager
-        self._hook_manager = HookManager(HomePaths(app.cache_folder).hooks_path)
+        self._hook_manager = hook_manager
         self._global_conf = global_conf
         self._home_folder = app.cache_folder
 
diff --git a/conans/client/rest_client_local_recipe_index.py b/conans/client/rest_client_local_recipe_index.py
index 7ab87848a..3a4e65d1d 100644
--- a/conans/client/rest_client_local_recipe_index.py
+++ b/conans/client/rest_client_local_recipe_index.py
@@ -61,15 +61,12 @@ class RestApiClientLocalRecipesIndex:
     def __init__(self, remote, home_folder):
         self._remote = remote
         local_recipes_index_path = HomePaths(home_folder).local_recipes_index_path
-        local_recipes_index_path = os.path.join(local_recipes_index_path, remote.name)
-        local_recipes_index_path = os.path.join(local_recipes_index_path, ".conan")
+        local_recipes_index_path = os.path.join(local_recipes_index_path, remote.name, ".conan")
         repo_folder = self._remote.url
 
-        from conan.internal.conan_app import ConanApp
-        from conan.api.conan_api import ConanAPI
-        conan_api = ConanAPI(local_recipes_index_path)
-        self._app = ConanApp(conan_api)
-        self._hook_manager = HookManager(HomePaths(conan_api.home_folder).hooks_path)
+        from conan.internal.conan_app import LocalRecipesIndexApp
+        self._app = LocalRecipesIndexApp(local_recipes_index_path)
+        self._hook_manager = HookManager(HomePaths(local_recipes_index_path).hooks_path)
         self._layout = _LocalRecipesIndexLayout(repo_folder)
 
     def call_method(self, method_name, *args, **kwargs):


Test patch:
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py b/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py
index f8263b5a2..f968f0b99 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain_win_clang.py
@@ -150,14 +150,14 @@ class TestVSClangCL:
         """ This is using the embedded ClangCL compiler, not the external one"""
         generator = "Visual Studio 17"
         client.run("create . --name=pkg --version=0.1 -pr=clang -s compiler.runtime=dynamic "
-                   "-s compiler.cppstd=17 -s compiler.runtime_version=v143 "
+                   "-s compiler.cppstd=17 -s compiler.runtime_version=v144 "
                    '-c tools.cmake.cmaketoolchain:generator="{}"'.format(generator))
         assert 'cmake -G "{}"'.format(generator) in client.out
         assert "MSVC-like command-line" in client.out
-        # My local is 17, but CI ClangCL still 16
-        assert "main __clang_major__18" in client.out
+        assert "main __clang_major__19" in client.out
         # Check this! Clang compiler in Windows is reporting MSC_VER and MSVC_LANG!
-        assert "main _MSC_VER1938" in client.out
+        # CI forced the installation of 19.38, seems to prevail there
+        assert "main _MSC_VER193" in client.out
         assert "main _MSVC_LANG201703" in client.out
         assert "main _M_X64 defined" in client.out
         assert "main __x86_64__ defined" in client.out
diff --git a/test/integration/graph/conflict_diamond_test.py b/test/integration/graph/conflict_diamond_test.py
index f0383157c..6f24d54e9 100644
--- a/test/integration/graph/conflict_diamond_test.py
+++ b/test/integration/graph/conflict_diamond_test.py
@@ -1,3 +1,5 @@
+import pytest
+
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient
 
@@ -68,3 +70,33 @@ class TestConflictDiamondTest:
         c.run("install --requires=engine/1.0  --requires=ai/1.0", assert_error=True)
         assert "Conflict between math/1.0.1 and math/1.0 in the graph"
         assert "Conflict originates from ai/1.0"
+
+
+@pytest.mark.parametrize("version_range", [True, False])
+def test_conflict_user(version_range):
+    # https://github.com/conan-io/conan/issues/17875
+    v = "[^1.0]" if version_range else "1.0"
+    c = TestClient(light=True)
+    c.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+            "pkg/conanfile.py": GenConanfile("pkg", "1.0").with_requires(f"dep/{v}@user1"),
+            "app/conanfile.py": GenConanfile("app", "1.0").with_requires(f"pkg/{v}@user1",
+                                                                         f"dep/{v}@user2")})
+    c.run("create dep --user=user1")
+    c.run("create dep --user=user2")
+    c.run("create pkg --user=user1")
+    c.run("install app", assert_error=True)
+    assert f"Version conflict: Conflict between dep/{v}@user1 and dep/{v}@user2" in c.out
+
+
+def test_conflict_user_order():
+    # https://github.com/conan-io/conan/issues/17875
+    c = TestClient(light=True)
+    c.save({"dep/conanfile.py": GenConanfile("dep", "1.0"),
+            "pkg/conanfile.py": GenConanfile("pkg", "1.0").with_requires("dep/1.0@user1"),
+            "app/conanfile.py": GenConanfile("app", "1.0").with_requires("pkg/1.0@user1",
+                                                                         "dep/[>=1.0]@user2")})
+    c.run("create dep --user=user1")
+    c.run("create dep --user=user2")
+    c.run("create pkg --user=user1")
+    c.run("install app", assert_error=True)
+    assert "ERROR: Version conflict: Conflict between dep/1.0@user1 and dep/[>=1.0]@user2" in c.out
diff --git a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
index 83420d6d6..76f0aee4b 100644
--- a/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
+++ b/test/integration/toolchains/cmake/cmakedeps2/test_cmakedeps.py
@@ -152,3 +152,29 @@ def test_cmakeconfigdeps_recipe():
     assert "WARN: Using the new CMakeConfigDeps generator" in c.out
     c.run("install app -c tools.cmake.cmakedeps:new=recipe_will_break")
     assert "WARN: Using the new CMakeConfigDeps generator" in c.out
+
+
+def test_system_wrappers():
+    c = TestClient()
+    conanfile = textwrap.dedent("""
+        import os
+        from conan.tools.files import copy
+        from conan import ConanFile
+        class TestConan(ConanFile):
+            name = "lib"
+            version = "system"
+            package_type = "shared-library"
+
+            def package_info(self):
+                self.cpp_info.includedirs = []
+                self.cpp_info.libdirs = []
+                self.cpp_info.system_libs = ["my_system_cool_lib"]
+    """)
+    c.save({"conanfile.py": conanfile})
+    c.run("create .")
+
+    c.run(f"install --requires=lib/system -g CMakeConfigDeps "
+          f"-c tools.cmake.cmakedeps:new={new_value}")
+    cmake = c.load("lib-Targets-release.cmake")
+    assert "add_library(lib::lib INTERFACE IMPORTED)" in cmake
+    assert "target_link_libraries(lib::lib INTERFACE my_system_cool_lib)" in cmake
diff --git a/test/integration/workspace/test_workspace.py b/test/integration/workspace/test_workspace.py
index 92eb9d1f7..2fabaf9a3 100644
--- a/test/integration/workspace/test_workspace.py
+++ b/test/integration/workspace/test_workspace.py
@@ -9,7 +9,7 @@ from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.scm import create_local_git_repo
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient
-from conans.util.files import save
+from conans.util.files import save, save_files
 
 WorkspaceAPI.TEST_ENABLED = "will_break_next"
 
@@ -499,7 +499,6 @@ class TestMeta:
                 "conanws.py": conanfilews})
         c.run("workspace add dep")
         c.run("workspace install -of=build")
-        print(c.out)
         files = os.listdir(os.path.join(c.current_folder, "build"))
         assert "conandeps.props" in files
 
@@ -524,3 +523,25 @@ class TestMeta:
         c.run("workspace add dep")
         c.run("workspace install", assert_error=True)
         assert "ERROR: Conanfile in conanws.py shouldn't have 'requires'" in c.out
+
+
+def test_workspace_with_local_recipes_index():
+    c3i_folder = temp_folder()
+    recipes_folder = os.path.join(c3i_folder, "recipes")
+    zlib_config = textwrap.dedent("""
+       versions:
+         "1.2.11":
+           folder: all
+       """)
+    save_files(recipes_folder, {"zlib/config.yml": zlib_config,
+                                "zlib/all/conanfile.py": str(GenConanfile("zlib")),
+                                "zlib/all/conandata.yml": ""})
+
+    c = TestClient(light=True)
+    c.save({"conanws.yml": 'home_folder: "deps"'})
+    c.run(f'remote add local "{c3i_folder}"')
+
+    c.run("list zlib/1.2.11#* -r=local")
+    assert "zlib/1.2.11" in c.out  # It doesn't crash
+    c.run("list zlib/1.2.11#*")
+    assert "zlib/1.2.11" not in c.out
diff --git a/test/unittests/tools/env/test_env.py b/test/unittests/tools/env/test_env.py
index e673c3bad..eb916ae94 100644
--- a/test/unittests/tools/env/test_env.py
+++ b/test/unittests/tools/env/test_env.py
@@ -368,6 +368,24 @@ class TestProfileEnvRoundTrip:
             MyPath1+=(path)/my/path1
             """)
 
+    def test_append_multiple(self):
+        myprofile = textwrap.dedent("""
+            # define
+            MyVar1+=MyValue1
+            MyVar1+=MyValue2
+            MyPath1 +=(path)/my/path1
+            MyPath1 +=(path)/my/path2
+            """)
+
+        env = ProfileEnvironment.loads(myprofile)
+        text = env.dumps()
+        assert text == textwrap.dedent("""\
+            MyVar1+=MyValue1
+            MyVar1+=MyValue2
+            MyPath1+=(path)/my/path1
+            MyPath1+=(path)/my/path2
+            """)
+
     def test_prepend(self):
         myprofile = textwrap.dedent("""
             # define
@@ -382,6 +400,24 @@ class TestProfileEnvRoundTrip:
             MyPath1=+(path)/my/path1
             """)
 
+    def test_prepend_multiple(self):
+        myprofile = textwrap.dedent("""
+           # define
+           MyVar1=+MyValue1
+           MyVar1=+MyValue2
+           MyPath1=+(path)/my/path1
+           MyPath1=+(path)/my/path2
+           """)
+
+        env = ProfileEnvironment.loads(myprofile)
+        text = env.dumps()
+        assert text == textwrap.dedent("""\
+           MyVar1=+MyValue1
+           MyVar1=+MyValue2
+           MyPath1=+(path)/my/path1
+           MyPath1=+(path)/my/path2
+           """)
+
     def test_combined(self):
         myprofile = textwrap.dedent("""
             MyVar1=+MyValue11
@@ -399,12 +435,59 @@ class TestProfileEnvRoundTrip:
             MyPath1+=(path)/my/path12
             """)
 
-    def test_combined2(self):
+    def test_combined_multiple(self):
+        myprofile = textwrap.dedent("""
+            MyVar1=+MyValue11
+            MyVar1=+MyValue12
+            MyVar1+=MyValue13
+            MyVar1+=MyValue14
+            MyPath1=+(path)/my/path11
+            MyPath1=+(path)/my/path12
+            MyPath1+=(path)/my/path13
+            MyPath1+=(path)/my/path12
+            """)
+
+        env = ProfileEnvironment.loads(myprofile)
+        text = env.dumps()
+        assert text == textwrap.dedent("""\
+            MyVar1=+MyValue11
+            MyVar1=+MyValue12
+            MyVar1+=MyValue13
+            MyVar1+=MyValue14
+            MyPath1=+(path)/my/path11
+            MyPath1=+(path)/my/path12
+            MyPath1+=(path)/my/path13
+            MyPath1+=(path)/my/path12
+            """)
+
+    def test_combined_prepend_first(self):
+        myprofile = textwrap.dedent("""
+            MyVar1+=MyValue11
+            MyVar1=+MyValue12
+            MyPath1+=(path)/my/path11
+            MyPath1=+(path)/my/path12
+            """)
+
+        env = ProfileEnvironment.loads(myprofile)
+        text = env.dumps()
+        # NOTE: This is reversed order compared to origin, prepend always first
+        assert text == textwrap.dedent("""\
+            MyVar1=+MyValue12
+            MyVar1+=MyValue11
+            MyPath1=+(path)/my/path12
+            MyPath1+=(path)/my/path11
+            """)
+
+    def test_combined_prepend_first_multiple(self):
         myprofile = textwrap.dedent("""
             MyVar1+=MyValue11
             MyVar1=+MyValue12
+            MyVar1+=MyValue13
+            MyVar1=+MyValue14
             MyPath1+=(path)/my/path11
             MyPath1=+(path)/my/path12
+            MyPath1+=(path)/my/path13
+            MyPath1=+(path)/my/path14
             """)
 
         env = ProfileEnvironment.loads(myprofile)
@@ -412,9 +495,13 @@ class TestProfileEnvRoundTrip:
         # NOTE: This is reversed order compared to origin, prepend always first
         assert text == textwrap.dedent("""\
             MyVar1=+MyValue12
+            MyVar1=+MyValue14
             MyVar1+=MyValue11
+            MyVar1+=MyValue13
             MyPath1=+(path)/my/path12
+            MyPath1=+(path)/my/path14
             MyPath1+=(path)/my/path11
+            MyPath1+=(path)/my/path13
             """)