Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[runtime_deploy] Respect subfolders in order to preserve symlinks #17848

Open
wants to merge 4 commits into
base: develop2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions conan/internal/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -136,24 +137,28 @@ 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.
"""
file_count = 0

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")
Expand Down
67 changes: 65 additions & 2 deletions test/functional/command/test_install_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]),
Expand Down Expand Up @@ -553,6 +554,68 @@ 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
import os
class Pkg(ConanFile):
package_type = "shared-library"
def package(self):
copy(self, "*.so*", src=self.build_folder, dst=self.package_folder, keep_path=True)
""")
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 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():
""" 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 -c:a tools.deployer:symlinks=True")

assert os.listdir(os.path.join(c.current_folder, "output", "subfolder")) == ["libfoo.so.1.0"]
# 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()
c.save({"conanfile.txt": "",
Expand Down
Loading