From ebf619dcf2967b12cd96c949246e6d44b72fd288 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Tue, 25 Feb 2025 14:35:38 +0100 Subject: [PATCH 1/4] Validate deploy_runtime with subfolder Signed-off-by: Uilian Ries --- .../functional/command/test_install_deploy.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/functional/command/test_install_deploy.py b/test/functional/command/test_install_deploy.py index faaeb98cf7b..97e384aeb6b 100644 --- a/test/functional/command/test_install_deploy.py +++ b/test/functional/command/test_install_deploy.py @@ -553,6 +553,31 @@ def package(self): assert not os.path.islink(lib) +def test_runtime_deploy_subfolder(): + """ The deployer runtime_deploy should preserve subfolder structure when deploying shared libraries + """ + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.files import copy, chdir + import os + class Pkg(ConanFile): + package_type = "shared-library" + def package(self): + copy(self, "*.so*", src=self.build_folder, dst=self.package_folder) + """) + c.save({"foo/conanfile.py": conanfile, + "foo/lib/libfoo.so": "", + "foo/lib/subfolder/libbar.so": "", + "foo/lib/subfolder/subsubfolder/libqux.so": "",}) + c.run("export-pkg foo/ --name=foo --version=0.1.0") + c.run(f"install --requires=foo/0.1.0 --deployer=runtime_deploy --deployer-folder=output") + + assert os.listdir(os.path.join(c.current_folder, "output")) == ["libfoo.so"] + assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libbar.so"] + assert os.listdir(os.path.join(c.current_folder, "output", "subfolder", "subsubfolder")) == ["libqux.so"] + + def test_deployer_errors(): c = TestClient() c.save({"conanfile.txt": "", From 737511a20c4de5525faaea0fae99e78b4cccd0b1 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Tue, 25 Feb 2025 14:47:58 +0100 Subject: [PATCH 2/4] Validate deploy_runtime with subfolder and symlink Signed-off-by: Uilian Ries --- .../functional/command/test_install_deploy.py | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/test/functional/command/test_install_deploy.py b/test/functional/command/test_install_deploy.py index 97e384aeb6b..e517676a973 100644 --- a/test/functional/command/test_install_deploy.py +++ b/test/functional/command/test_install_deploy.py @@ -559,7 +559,7 @@ def test_runtime_deploy_subfolder(): c = TestClient() conanfile = textwrap.dedent(""" from conan import ConanFile - from conan.tools.files import copy, chdir + from conan.tools.files import copy import os class Pkg(ConanFile): package_type = "shared-library" @@ -573,11 +573,38 @@ def package(self): c.run("export-pkg foo/ --name=foo --version=0.1.0") c.run(f"install --requires=foo/0.1.0 --deployer=runtime_deploy --deployer-folder=output") - assert os.listdir(os.path.join(c.current_folder, "output")) == ["libfoo.so"] - assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libbar.so"] + assert os.listdir(os.path.join(c.current_folder, "output")) == ["libfoo.so", "subfolder"] + assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libbar.so", "subsubfolder"] assert os.listdir(os.path.join(c.current_folder, "output", "subfolder", "subsubfolder")) == ["libqux.so"] +def test_runtime_deploy_subfolder_symlink(): + """ The deployer runtime_deploy should preserve subfolder structure when deploying shared + libraries with symlinks + """ + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.files import copy, chdir, mkdir + import os + class Pkg(ConanFile): + package_type = "shared-library" + def package(self): + copy(self, "*.so*", src=self.build_folder, dst=self.package_folder) + with chdir(self, os.path.join(self.package_folder, "lib")): + os.symlink(src="subfolder/libfoo.so.1.0", dst="libfoo.so.1") + os.symlink(src="libfoo.so.1", dst="libfoo.so") + """) + c.save({"foo/conanfile.py": conanfile, + "foo/lib/subfolder/libfoo.so.1.0": "",}) + c.run("export-pkg foo/ --name=foo --version=0.1.0") + c.run(f"install --requires=foo/0.1.0 --deployer=runtime_deploy --deployer-folder=output") + + assert sorted(os.listdir(os.path.join(c.current_folder, "output"))) == ["libfoo.so", "libfoo.so.1" , "subfolder"] + assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libfoo.so.1.0"] + assert os.path.islink(os.path.join(c.current_folder, "output", "libfoo.so.1")) + assert os.path.islink(os.path.join(c.current_folder, "output", "libfoo.so")) + def test_deployer_errors(): c = TestClient() c.save({"conanfile.txt": "", From 9e74b3cd5823a1fe25361f049d9557af5a7b3564 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Tue, 25 Feb 2025 15:49:12 +0100 Subject: [PATCH 3/4] Preserve subfolders when using runtime_deploy Signed-off-by: Uilian Ries --- conan/internal/deploy.py | 9 +++++-- .../functional/command/test_install_deploy.py | 26 +++++++++++++------ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/conan/internal/deploy.py b/conan/internal/deploy.py index 1c28895469b..f7fc534bc29 100644 --- a/conan/internal/deploy.py +++ b/conan/internal/deploy.py @@ -102,6 +102,7 @@ def runtime_deploy(graph, output_folder): Deploy all the shared libraries and the executables of the dependencies in a flat directory. It preserves symlinks in case the configuration tools.deployer:symlinks is True. + It preserves the directory structure when having subfolders """ conanfile = graph.root.conanfile output = ConanOutput(scope="runtime_deploy") @@ -136,7 +137,7 @@ def runtime_deploy(graph, output_folder): def _flatten_directory(dep, src_dir, output_dir, symlinks, extension_filter=None): """ - Copy all the files from the source directory in a flat output directory. + Copy all the files from the source directory in a flat output directory, respecting subfolders. An optional string, named extension_filter, can be set to copy only the files with the listed extensions. """ @@ -144,16 +145,20 @@ def _flatten_directory(dep, src_dir, output_dir, symlinks, extension_filter=None output = ConanOutput(scope="runtime_deploy") for src_dirpath, _, src_filenames in os.walk(src_dir, followlinks=symlinks): + rel_path = os.path.relpath(src_dirpath, src_dir) for src_filename in src_filenames: if extension_filter and not any(fnmatch.fnmatch(src_filename, f'*{ext}') for ext in extension_filter): continue src_filepath = os.path.join(src_dirpath, src_filename) - dest_filepath = os.path.join(output_dir, src_filename) + dest_filepath = os.path.join(output_dir, rel_path, src_filename) if not symlinks and os.path.islink(src_filepath): continue + if not os.path.exists(os.path.dirname(dest_filepath)): + os.makedirs(os.path.dirname(dest_filepath)) + if os.path.exists(dest_filepath): if filecmp.cmp(src_filepath, dest_filepath): # Be efficient, do not copy output.verbose(f"{dest_filepath} exists with same contents, skipping copy") diff --git a/test/functional/command/test_install_deploy.py b/test/functional/command/test_install_deploy.py index e517676a973..cb5be07f3a3 100644 --- a/test/functional/command/test_install_deploy.py +++ b/test/functional/command/test_install_deploy.py @@ -564,7 +564,7 @@ def test_runtime_deploy_subfolder(): class Pkg(ConanFile): package_type = "shared-library" def package(self): - copy(self, "*.so*", src=self.build_folder, dst=self.package_folder) + copy(self, "*.so*", src=self.build_folder, dst=self.package_folder, keep_path=True) """) c.save({"foo/conanfile.py": conanfile, "foo/lib/libfoo.so": "", @@ -573,9 +573,9 @@ def package(self): c.run("export-pkg foo/ --name=foo --version=0.1.0") c.run(f"install --requires=foo/0.1.0 --deployer=runtime_deploy --deployer-folder=output") - assert os.listdir(os.path.join(c.current_folder, "output")) == ["libfoo.so", "subfolder"] - assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libbar.so", "subsubfolder"] - assert os.listdir(os.path.join(c.current_folder, "output", "subfolder", "subsubfolder")) == ["libqux.so"] + assert sorted(os.listdir(os.path.join(c.current_folder, "output"))) == ["libfoo.so", "subfolder"] + assert sorted(os.listdir(os.path.join(c.current_folder, "output", "subfolder"))) == ["libbar.so", "subsubfolder"] + assert sorted(os.listdir(os.path.join(c.current_folder, "output", "subfolder", "subsubfolder"))) == ["libqux.so"] def test_runtime_deploy_subfolder_symlink(): @@ -598,12 +598,22 @@ def package(self): c.save({"foo/conanfile.py": conanfile, "foo/lib/subfolder/libfoo.so.1.0": "",}) c.run("export-pkg foo/ --name=foo --version=0.1.0") - c.run(f"install --requires=foo/0.1.0 --deployer=runtime_deploy --deployer-folder=output") + c.run(f"install --requires=foo/0.1.0 --deployer=runtime_deploy --deployer-folder=output -c:a tools.deployer:symlinks=True") - assert sorted(os.listdir(os.path.join(c.current_folder, "output"))) == ["libfoo.so", "libfoo.so.1" , "subfolder"] assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libfoo.so.1.0"] - assert os.path.islink(os.path.join(c.current_folder, "output", "libfoo.so.1")) - assert os.path.islink(os.path.join(c.current_folder, "output", "libfoo.so")) + # INFO: This test requires in Windows to have symlinks enabled, otherwise it will fail + if platform.system() != "Windows": + assert sorted(os.listdir(os.path.join(c.current_folder, "output"))) == ["libfoo.so", "libfoo.so.1" , "subfolder"] + link_so_0 = os.path.join(c.current_folder, "output", "libfoo.so.1") + link_so = os.path.join(c.current_folder, "output", "libfoo.so") + lib = os.path.join(c.current_folder, "output", "subfolder", "libfoo.so.1.0") + assert os.path.islink(link_so_0) + assert os.path.islink(link_so) + assert not os.path.isabs(os.readlink(link_so_0)) + assert not os.path.isabs(os.readlink(os.path.join(link_so))) + assert os.path.realpath(link_so) == os.path.realpath(link_so_0) + assert os.path.realpath(link_so_0) == os.path.realpath(lib) + def test_deployer_errors(): c = TestClient() From c4ea5dcee878c35f80b977eabb9389acd37fb4f6 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Tue, 25 Feb 2025 15:52:41 +0100 Subject: [PATCH 4/4] Respect subfolders for components test Signed-off-by: Uilian Ries --- test/functional/command/test_install_deploy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functional/command/test_install_deploy.py b/test/functional/command/test_install_deploy.py index cb5be07f3a3..87ce8c84171 100644 --- a/test/functional/command/test_install_deploy.py +++ b/test/functional/command/test_install_deploy.py @@ -508,8 +508,9 @@ def package_info(self): c.run("install --requires=pkga/1.0 --requires=pkgb/1.0 --deployer=runtime_deploy " "--deployer-folder=myruntime -vvv") - expected = sorted(["pkga.so", "pkgb.so", "pkga.dll"]) - assert sorted(os.listdir(os.path.join(c.current_folder, "myruntime"))) == expected + assert sorted(os.listdir(os.path.join(c.current_folder, "myruntime"))) == sorted(["bin", "lib"]) + assert sorted(os.listdir(os.path.join(c.current_folder, "myruntime", "lib"))) == sorted(['pkga.so', 'pkgb.so']) + assert sorted(os.listdir(os.path.join(c.current_folder, "myruntime", "bin"))) == sorted(['pkga.dll']) @pytest.mark.parametrize("symlink, expected", [(True, ["libfoo.so.0.1.0", "libfoo.so.0", "libfoo.so"]),