From 40aa78958264d99d37209fd1dee94ab3396287ba Mon Sep 17 00:00:00 2001 From: James Date: Mon, 1 Mar 2021 18:20:10 +0100 Subject: [PATCH 1/5] fix lock bundle build (#8579) * fix lock bundle build * Update conans/model/lock_bundle.py Co-authored-by: Jerry Wiltse Co-authored-by: Jerry Wiltse --- conans/model/lock_bundle.py | 53 +++++++++++-------- .../graph_lock/test_lock_bundle.py | 44 ++++++++++++--- 2 files changed, 69 insertions(+), 28 deletions(-) diff --git a/conans/model/lock_bundle.py b/conans/model/lock_bundle.py index 54bbe0497a0..fa0815e4d15 100644 --- a/conans/model/lock_bundle.py +++ b/conans/model/lock_bundle.py @@ -14,11 +14,12 @@ class LockBundle(object): products. The format is a json with: For every reference, for each "package_id" for that reference, list the lockfiles that contain such reference:package_id and the node indexes inside that lockfile - + lock_bundle": { "app1/0.1@#584778f98ba1d0eb7c80a5ae1fe12fe2": { - "package_id": { - "3bcd6800847f779e0883ee91b411aad9ddd8e83c": { + "packages": [ + { + "package_id": "3bcd6800847f779e0883ee91b411aad9ddd8e83c", "lockfiles": { "app1_windows.lock": [ "1" @@ -27,9 +28,7 @@ class LockBundle(object): "prev": null, "modified": null }, - "60fbb0a22359b4888f7ecad69bcdfcd6e70e2784": { - } - }, + ], "requires": [ "pkgb/0.1@#cd8f22d6f264f65398d8c534046e8e20", "tool/0.1@#f096d7d54098b7ad7012f9435d9c33f3" @@ -38,9 +37,6 @@ class LockBundle(object): """ def __init__(self): - """ The structure is - " - """ self._nodes = {} @staticmethod @@ -64,8 +60,16 @@ def ref_convert(r): ref_str = node.ref.full_str() ref_str = ref_convert(ref_str) ref_node = result._nodes.setdefault(ref_str, {}) - pids_node = ref_node.setdefault("package_id", {}) - pid_node = pids_node.setdefault(node.package_id, {}) + packages_node = ref_node.setdefault("packages", []) + # Find existing package_id in the list of packages + # This is the equivalent of a setdefault over a dict, but on a list + for pkg in packages_node: + if pkg["package_id"] == node.package_id: + pid_node = pkg + break + else: + pid_node = {"package_id": node.package_id} + packages_node.append(pid_node) ids = pid_node.setdefault("lockfiles", {}) # TODO: Add check that this prev is always the same pid_node["prev"] = node.prev @@ -95,16 +99,21 @@ def build_order(self): opened = list(self._nodes.keys()) while opened: current_level = [] + closed = [] for o in opened: node = self._nodes[o] requires = node.get("requires", []) - if not any(n in opened for n in requires): - current_level.append(o) - - current_level.sort() - levels.append(current_level) + if not any(n in opened for n in requires): # Doesn't have an open requires + # iterate all packages to see if some has prev=null + if any(pkg["prev"] is None for pkg in node["packages"]): + current_level.append(o) + closed.append(o) + + if current_level: + current_level.sort() + levels.append(current_level) # now initialize new level - opened = set(opened).difference(current_level) + opened = set(opened).difference(closed) return levels @@ -118,12 +127,12 @@ def update_bundle(bundle_path, revisions_enabled): bundle.loads(load(bundle_path)) for node in bundle._nodes.values(): # Each node in bundle belongs to a "ref", and contains lockinfo for every package_id - for bundle_package_ids in node["package_id"].values(): + for pkg in node["packages"]: # Each package_id contains information of multiple lockfiles # First, compute the modified PREV from all lockfiles prev = modified = prev_lockfile = None - for lockfile, nodes_ids in bundle_package_ids["lockfiles"].items(): + for lockfile, nodes_ids in pkg["lockfiles"].items(): graph_lock_conf = GraphLockFile.load(lockfile, revisions_enabled) graph_lock = graph_lock_conf.graph_lock @@ -139,11 +148,11 @@ def update_bundle(bundle_path, revisions_enabled): msg = "Lock mismatch for {} prev: {}:{} != {}:{}".format( ref, prev_lockfile, prev, lockfile, lock_prev) raise ConanException(msg) - bundle_package_ids["prev"] = prev - bundle_package_ids["modified"] = modified + pkg["prev"] = prev + pkg["modified"] = modified # Then, update all prev of all config lockfiles - for lockfile, nodes_ids in bundle_package_ids["lockfiles"].items(): + for lockfile, nodes_ids in pkg["lockfiles"].items(): graph_lock_conf = GraphLockFile.load(lockfile, revisions_enabled) graph_lock = graph_lock_conf.graph_lock for node_id in nodes_ids: diff --git a/conans/test/integration/graph_lock/test_lock_bundle.py b/conans/test/integration/graph_lock/test_lock_bundle.py index df40d2e63c3..8f524e4b390 100644 --- a/conans/test/integration/graph_lock/test_lock_bundle.py +++ b/conans/test/integration/graph_lock/test_lock_bundle.py @@ -58,9 +58,9 @@ def test_basic(): for level in order: for ref in level: # Now get the package_id, lockfile - pkg_ids = bundle[ref]["package_id"] - for pkg_id, lockfile_info in pkg_ids.items(): - lockfiles = lockfile_info["lockfiles"] + packages = bundle[ref]["packages"] + for pkg in packages: + lockfiles = pkg["lockfiles"] lockfile = next(iter(sorted(lockfiles))) client.run("install {ref} --build={ref} --lockfile={lockfile} " @@ -166,9 +166,9 @@ def test_build_requires(): for level in order: for ref in level: # Now get the package_id, lockfile - pkg_ids = bundle[ref]["package_id"] - for pkg_id, lockfile_info in pkg_ids.items(): - lockfiles = lockfile_info["lockfiles"] + packages = bundle[ref]["packages"] + for pkg in packages: + lockfiles = pkg["lockfiles"] lockfile = next(iter(sorted(lockfiles))) client.run("install {ref} --build={ref} --lockfile={lockfile} " @@ -222,3 +222,35 @@ def test_build_requires(): assert nodes[n].ref.full_str() == "tool/0.1#f096d7d54098b7ad7012f9435d9c33f3" assert nodes[n].package_id == "cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31" assert nodes[n].prev == "9e99cfd92d0d7df79d687b01512ce844" + + +def test_build_requires_error(): + # https://github.com/conan-io/conan/issues/8577 + client = TestClient() + # TODO: This is hardcoded + client.run("config set general.revisions_enabled=1") + client.save({"tool/conanfile.py": GenConanfile().with_settings("os"), + "pkga/conanfile.py": GenConanfile().with_settings("os"), + "app1/conanfile.py": GenConanfile().with_settings("os").with_requires("pkga/0.1"), + "profile": "[build_requires]\ntool/0.1"}) + client.run("create tool tool/0.1@ -s os=Windows") + client.run("create tool tool/0.1@ -s os=Linux") + client.run("export pkga pkga/0.1@") + client.run("export app1 app1/0.1@") + + client.run("lock create --ref=app1/0.1 -pr=profile -s os=Windows " + "--lockfile-out=app1_windows.lock --build=missing") + assert "tool/0.1:3475bd55b91ae904ac96fde0f106a136ab951a5e - Cache" in client.out + client.run("lock create --ref=app1/0.1 -pr=profile -s os=Linux " + "--lockfile-out=app1_linux.lock --build=missing") + assert "tool/0.1:cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31 - Cache" in client.out + + client.run("lock bundle create app1_windows.lock app1_linux.lock --bundle-out=lock1.bundle") + client.run("lock bundle build-order lock1.bundle --json=bo.json") + order = client.load("bo.json") + print(order) + order = json.loads(order) + assert order == [ + ["pkga/0.1@#f096d7d54098b7ad7012f9435d9c33f3"], + ["app1/0.1@#5af607abc205b47375f485a98abc3b38"] + ] From 4660d803bb12df957c8cd7a55fe2c8d0eac5a4ba Mon Sep 17 00:00:00 2001 From: James Date: Thu, 4 Mar 2021 10:19:31 +0100 Subject: [PATCH 2/5] preparing migration of names to CMakeDeps (#8568) * preparing migration of names to CMakeDeps * moving the workaround to CMakeDeps * review --- conan/tools/cmake/cmakedeps.py | 42 ++++++++++++-- .../unittests/tools/cmake/test_cmakedeps.py | 57 +++++++++++++++++++ 2 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 conans/test/unittests/tools/cmake/test_cmakedeps.py diff --git a/conan/tools/cmake/cmakedeps.py b/conan/tools/cmake/cmakedeps.py index 27608c8b97e..304cf62644b 100644 --- a/conan/tools/cmake/cmakedeps.py +++ b/conan/tools/cmake/cmakedeps.py @@ -5,6 +5,7 @@ from conans.errors import ConanException from conans.model.build_info import CppInfo, merge_dicts +from conans.util.conan_v2_mode import conan_v2_error from conans.util.files import save COMPONENT_SCOPE = "::" @@ -560,12 +561,40 @@ def _check_component_in_requirements(require): for pkg_require in cpp_info.requires: _check_component_in_requirements(pkg_require) + def _get_name(self, cpp_info, pkg_name): + # FIXME: This is a workaround to be able to use existing recipes that declare + # FIXME: cpp_info.names["cmake_find_package_multi"] = "xxxxx" + name = cpp_info.names.get(self.name) + if name is not None: + return name + find_name = cpp_info.names.get("cmake_find_package_multi") + if find_name is not None: + # Not displaying a warning, too noisy as this is called many times + conan_v2_error("'{}' defines information for 'cmake_find_package_multi', " + "but not 'CMakeDeps'".format(pkg_name)) + return find_name + return cpp_info._name + + def _get_filename(self, cpp_info, pkg_name): + # FIXME: This is a workaround to be able to use existing recipes that declare + # FIXME: cpp_info.filenames["cmake_find_package_multi"] = "xxxxx" + name = cpp_info.filenames.get(self.name) + if name is not None: + return name + find_name = cpp_info.filenames.get("cmake_find_package_multi") + if find_name is not None: + # Not displaying a warning, too noisy as this is called many times + conan_v2_error("'{}' defines information for 'cmake_find_package_multi', " + "but not 'CMakeDeps'".format(pkg_name)) + return find_name + return cpp_info._name + def _get_require_name(self, pkg_name, req): pkg, cmp = req.split(COMPONENT_SCOPE) if COMPONENT_SCOPE in req else (pkg_name, req) pkg_cpp_info = self._conanfile.deps_cpp_info[pkg] - pkg_name = pkg_cpp_info.get_name(self.name) + pkg_name = self._get_name(pkg_cpp_info, pkg_name) if cmp in pkg_cpp_info.components: - cmp_name = pkg_cpp_info.components[cmp].get_name(self.name) + cmp_name = self._get_name(pkg_cpp_info.components[cmp], pkg_name) else: cmp_name = pkg_name return pkg_name, cmp_name @@ -575,7 +604,7 @@ def _get_components(self, pkg_name, cpp_info): sorted_comps = cpp_info._get_sorted_components() for comp_name, comp in sorted_comps.items(): - comp_genname = cpp_info.components[comp_name].get_name(self.name) + comp_genname = self._get_name(cpp_info.components[comp_name], pkg_name) comp_requires_gennames = [] for require in comp.requires: comp_requires_gennames.append(self._get_require_name(pkg_name, require)) @@ -617,8 +646,8 @@ def content(self): for pkg_name, cpp_info in self._conanfile.deps_cpp_info.dependencies: self._validate_components(cpp_info) - pkg_filename = cpp_info.get_filename(self.name) - pkg_findname = cpp_info.get_name(self.name) + pkg_filename = self._get_filename(cpp_info, pkg_name) + pkg_findname = self._get_name(cpp_info, pkg_name) pkg_version = cpp_info.version public_deps = self.get_public_deps(cpp_info) @@ -628,7 +657,8 @@ def content(self): if name not in deps_names: deps_names.append(name) deps_names = ';'.join(deps_names) - pkg_public_deps_filenames = [self._conanfile.deps_cpp_info[it[0]].get_filename(self.name) + pkg_public_deps_filenames = [self._get_filename(self._conanfile.deps_cpp_info[it[0]], + pkg_name) for it in public_deps] config_version = self.config_version_template.format(version=pkg_version) ret[self._config_version_filename(pkg_filename)] = config_version diff --git a/conans/test/unittests/tools/cmake/test_cmakedeps.py b/conans/test/unittests/tools/cmake/test_cmakedeps.py new file mode 100644 index 00000000000..99b96cc1ef9 --- /dev/null +++ b/conans/test/unittests/tools/cmake/test_cmakedeps.py @@ -0,0 +1,57 @@ +import pytest +from mock import Mock + +from conan.tools.cmake import CMakeDeps +from conans import ConanFile, Settings +from conans.client.tools import environment_append +from conans.errors import ConanException +from conans.model.build_info import CppInfo, DepCppInfo +from conans.model.env_info import EnvValues +from conans.util.conan_v2_mode import CONAN_V2_MODE_ENVVAR + + +def test_cpp_info_name_cmakedeps(): + conanfile = ConanFile(Mock(), None) + conanfile.settings = "os", "compiler", "build_type", "arch" + conanfile.initialize(Settings({"os": ["Windows"], + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["x86"]}), EnvValues()) + + cpp_info = CppInfo("mypkg", "dummy_root_folder1") + cpp_info.names["cmake_find_package_multi"] = "MySuperPkg1" + cpp_info.filenames["cmake_find_package_multi"] = "ComplexFileName1" + conanfile.deps_cpp_info.add("mypkg", cpp_info) + + cmakedeps = CMakeDeps(conanfile) + files = cmakedeps.content + assert "TARGET MySuperPkg1::MySuperPkg1" in files["ComplexFileName1Config.cmake"] + + with pytest.raises(ConanException, + match="'mypkg' defines information for 'cmake_find_package_multi'"): + with environment_append({CONAN_V2_MODE_ENVVAR: "1"}): + _ = cmakedeps.content + + +def test_cpp_info_name_cmakedeps_components(): + conanfile = ConanFile(Mock(), None) + conanfile.settings = "os", "compiler", "build_type", "arch" + conanfile.initialize(Settings({"os": ["Windows"], + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["x86"]}), EnvValues()) + + cpp_info = CppInfo("mypkg", "dummy_root_folder1") + cpp_info.names["cmake_find_package_multi"] = "GlobakPkgName1" + cpp_info.components["mycomp"].names["cmake_find_package_multi"] = "MySuperPkg1" + cpp_info.filenames["cmake_find_package_multi"] = "ComplexFileName1" + conanfile.deps_cpp_info.add("mypkg", DepCppInfo(cpp_info)) + + cmakedeps = CMakeDeps(conanfile) + files = cmakedeps.content + assert "TARGET GlobakPkgName1::MySuperPkg1" in files["ComplexFileName1Config.cmake"] + + with pytest.raises(ConanException, + match="'mypkg' defines information for 'cmake_find_package_multi'"): + with environment_append({CONAN_V2_MODE_ENVVAR: "1"}): + _ = cmakedeps.content From e2e437c0dd66193f2cb570618d660209c5a44ae0 Mon Sep 17 00:00:00 2001 From: czoido Date: Tue, 9 Mar 2021 10:30:38 +0100 Subject: [PATCH 3/5] release 1.34.1 --- conans/__init__.py | 2 +- conans/client/migrations_settings.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/conans/__init__.py b/conans/__init__.py index 25e03dab242..6da3bb1c5c0 100644 --- a/conans/__init__.py +++ b/conans/__init__.py @@ -20,4 +20,4 @@ SERVER_CAPABILITIES = [COMPLEX_SEARCH_CAPABILITY, REVISIONS] # Server is always with revisions DEFAULT_REVISION_V1 = "0" -__version__ = '1.34.0' +__version__ = '1.34.1' diff --git a/conans/client/migrations_settings.py b/conans/client/migrations_settings.py index 9a196f144b1..e61272ff6a9 100644 --- a/conans/client/migrations_settings.py +++ b/conans/client/migrations_settings.py @@ -2058,3 +2058,4 @@ """ settings_1_34_0 = settings_1_33_1 +settings_1_34_1 = settings_1_34_0 From 2e57d245b41a01b2ce1563fbe3bab7949d30cce4 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 9 Mar 2021 11:46:46 +0100 Subject: [PATCH 4/5] Backport pytest mark fixes to release 1.34 branch (#8617) * Fix compiler mark autodetection in pytest (#8613) * fix spelling * also fix pkg-config in win * fix tests * fix import * Add marks to some tests to fix develop (#8614) * fix tests * add marks * remove gcc mark --- conans/test/conftest.py | 2 +- conans/test/functional/build_helpers/cmake_flags_test.py | 1 + conans/test/functional/generators/cmake_multi_test.py | 6 +----- .../functional/generators/components/pkg_config_test.py | 3 +++ conans/test/functional/toolchains/cmake/test_cmake.py | 1 + conans/test/functional/util/pkg_config_test.py | 9 +-------- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/conans/test/conftest.py b/conans/test/conftest.py index 95fee9ab785..c6008f7577c 100644 --- a/conans/test/conftest.py +++ b/conans/test/conftest.py @@ -41,7 +41,7 @@ except ConanException: tools_available.remove("visual_studio") -if not any([x for x in ("gcc", "clang", "visual_sudio") if x in tools_available]): +if not any([x for x in ("gcc", "clang", "visual_studio") if x in tools_available]): tools_available.remove("compiler") if not which("xcodebuild"): diff --git a/conans/test/functional/build_helpers/cmake_flags_test.py b/conans/test/functional/build_helpers/cmake_flags_test.py index 0110ee5a771..11200aa5f91 100644 --- a/conans/test/functional/build_helpers/cmake_flags_test.py +++ b/conans/test/functional/build_helpers/cmake_flags_test.py @@ -378,6 +378,7 @@ def build(self): libpath = os.path.join(client.current_folder, "build", "lib", libname) self.assertTrue(os.path.exists(libpath)) + @pytest.mark.tool_mingw64 def test_standard_20_as_cxx_flag(self): # CMake (1-Jun-2018) do not support the 20 flag in CMAKE_CXX_STANDARD var conanfile = """ diff --git a/conans/test/functional/generators/cmake_multi_test.py b/conans/test/functional/generators/cmake_multi_test.py index 2c16815c3e7..a32f4ecaefd 100644 --- a/conans/test/functional/generators/cmake_multi_test.py +++ b/conans/test/functional/generators/cmake_multi_test.py @@ -136,12 +136,8 @@ def package_files(name, deps=None): @pytest.mark.tool_cmake class CMakeMultiTest(unittest.TestCase): - @pytest.mark.skipif(platform.system() != "Windows", reason="Requires mingw32-make") - @pytest.mark.tool_mingw32 - @pytest.mark.tool_gcc + @pytest.mark.tool_mingw64 def test_cmake_multi_find(self): - if platform.system() not in ["Windows", "Linux"]: - return client = TestClient() conanfile = """from conans import ConanFile, CMake class HelloConan(ConanFile): diff --git a/conans/test/functional/generators/components/pkg_config_test.py b/conans/test/functional/generators/components/pkg_config_test.py index e848bd6fc99..33c5c642567 100644 --- a/conans/test/functional/generators/components/pkg_config_test.py +++ b/conans/test/functional/generators/components/pkg_config_test.py @@ -1,6 +1,7 @@ import os import textwrap import unittest +import platform import pytest @@ -11,6 +12,8 @@ @pytest.mark.tool_compiler +@pytest.mark.tool_pkg_config +@pytest.mark.skipif(platform.system() == "Windows", reason="Requires pkg-config") class PkgConfigGeneratorWithComponentsTest(unittest.TestCase): @staticmethod diff --git a/conans/test/functional/toolchains/cmake/test_cmake.py b/conans/test/functional/toolchains/cmake/test_cmake.py index 3d52429a500..2cb1044edec 100644 --- a/conans/test/functional/toolchains/cmake/test_cmake.py +++ b/conans/test/functional/toolchains/cmake/test_cmake.py @@ -261,6 +261,7 @@ def _verify_out(marker=">>"): @parameterized.expand([("Debug", "libstdc++", "4.9", "98", "x86_64", True), ("Release", "libstdc++", "4.9", "11", "x86_64", False)]) + @pytest.mark.tool_mingw64 def test_toolchain_mingw_win(self, build_type, libcxx, version, cppstd, arch, shared): # FIXME: The version and cppstd are wrong, toolchain doesn't enforce it settings = {"compiler": "gcc", diff --git a/conans/test/functional/util/pkg_config_test.py b/conans/test/functional/util/pkg_config_test.py index 7629e167cf6..c8e9c377e10 100644 --- a/conans/test/functional/util/pkg_config_test.py +++ b/conans/test/functional/util/pkg_config_test.py @@ -29,18 +29,15 @@ @pytest.mark.unix +@pytest.mark.skipif(platform.system() == "Windows", reason="Requires pkg-config") class PkgConfigTest(unittest.TestCase): def test_negative(self): - if platform.system() == "Windows": - return pc = PkgConfig('libsomething_that_does_not_exist_in_the_world') with self.assertRaises(ConanException): pc.libs() @pytest.mark.tool_pkg_config def test_pc(self): - if platform.system() == "Windows": - return tmp_dir = temp_folder() filename = os.path.join(tmp_dir, 'libastral.pc') with open(filename, 'w') as f: @@ -64,8 +61,6 @@ def test_pc(self): @pytest.mark.tool_pkg_config def test_define_prefix(self): - if platform.system() == "Windows": - return tmp_dir = temp_folder() filename = os.path.join(tmp_dir, 'libastral.pc') with open(filename, 'w') as f: @@ -91,8 +86,6 @@ def test_define_prefix(self): @pytest.mark.tool_pkg_config def test_rpaths_libs(self): - if platform.system() == "Windows": - return pc_content = """prefix=/my_prefix/path libdir=/my_absoulte_path/fake/mylib/lib libdir3=${prefix}/lib2 From 210df983a0596aa30e45913cccfa78f73a48704e Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 9 Mar 2021 15:06:39 +0100 Subject: [PATCH 5/5] Fix/export case insensitive (#8585) (#8621) * export was case sensitive * add more checks * fixing tests * fixing more tests * removed repeated code * removed repeated code Co-authored-by: James --- conans/client/file_copier.py | 10 +++--- .../functional/command/export/export_test.py | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/conans/client/file_copier.py b/conans/client/file_copier.py index cd7a7fe9dec..ad188850617 100644 --- a/conans/client/file_copier.py +++ b/conans/client/file_copier.py @@ -154,21 +154,19 @@ def _filter_files(src, pattern, links, excludes, ignore_case, excluded_folders): filenames.append(relative_name) if ignore_case: - filenames = {f.lower(): f for f in filenames} pattern = pattern.lower() - files_to_copy = fnmatch.filter(filenames, pattern) + files_to_copy = [n for n in filenames if fnmatch.fnmatch(os.path.normpath(n.lower()), + pattern)] else: files_to_copy = [n for n in filenames if fnmatch.fnmatchcase(os.path.normpath(n), pattern)] + for exclude in excludes: if ignore_case: - files_to_copy = [f for f in files_to_copy if not fnmatch.fnmatch(f, exclude)] + files_to_copy = [f for f in files_to_copy if not fnmatch.fnmatch(f.lower(), exclude)] else: files_to_copy = [f for f in files_to_copy if not fnmatch.fnmatchcase(f, exclude)] - if ignore_case: - files_to_copy = [filenames[f] for f in files_to_copy] - return files_to_copy, linked_folders @staticmethod diff --git a/conans/test/functional/command/export/export_test.py b/conans/test/functional/command/export/export_test.py index e70db56e9bd..7364779aab9 100644 --- a/conans/test/functional/command/export/export_test.py +++ b/conans/test/functional/command/export/export_test.py @@ -1,4 +1,5 @@ import os +import platform import stat import textwrap import unittest @@ -462,3 +463,33 @@ def test_export_conflict_no_user_channel(self): self.assertIn("pkg/0.1: A new conanfile.py version was exported", client.out) client.run('export . Pkg/0.1@', assert_error=True) self.assertIn("ERROR: Cannot export package with same name but different case", client.out) + + +@pytest.mark.skipif(platform.system() != "Linux", reason="Needs case-sensitive filesystem") +def test_export_casing(): + # https://github.com/conan-io/conan/issues/8583 + client = TestClient() + conanfile = textwrap.dedent(""" + from conans import ConanFile + class Pkg(ConanFile): + exports = "file1", "FILE1" + exports_sources = "test", "TEST" + """) + client.save({"conanfile.py": conanfile, + "test": "some lowercase", + "TEST": "some UPPERCASE", + "file1": "file1 lowercase", + "FILE1": "file1 UPPERCASE" + }) + assert client.load("test") == "some lowercase" + assert client.load("TEST") == "some UPPERCASE" + assert client.load("file1") == "file1 lowercase" + assert client.load("FILE1") == "file1 UPPERCASE" + client.run("export . pkg/0.1@") + ref = ConanFileReference.loads("pkg/0.1@") + export_src_folder = client.cache.package_layout(ref).export_sources() + assert load(os.path.join(export_src_folder, "test")) == "some lowercase" + assert load(os.path.join(export_src_folder, "TEST")) == "some UPPERCASE" + exports_folder = client.cache.package_layout(ref).export() + assert load(os.path.join(exports_folder, "file1")) == "file1 lowercase" + assert load(os.path.join(exports_folder, "FILE1")) == "file1 UPPERCASE"