diff --git a/conan/tools/files/__init__.py b/conan/tools/files/__init__.py index 2517002f97d..f47cd2beffd 100644 --- a/conan/tools/files/__init__.py +++ b/conan/tools/files/__init__.py @@ -1,5 +1,6 @@ from conan.tools.files.files import load, save, mkdir, rmdir, rm, ftp_download, download, get, \ - rename, chdir, unzip, replace_in_file, collect_libs, check_md5, check_sha1, check_sha256 + rename, chdir, unzip, replace_in_file, collect_libs, check_md5, check_sha1, check_sha256, \ + move_folder_contents from conan.tools.files.patches import patch, apply_conandata_patches, export_conandata_patches from conan.tools.files.cpp_package import CppPackage diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py index e46e02b2f2f..36453bb30d4 100644 --- a/conan/tools/files/files.py +++ b/conan/tools/files/files.py @@ -544,18 +544,39 @@ def collect_libs(conanfile, folder=None): # TODO: Do NOT document this yet. It is unclear the interface, maybe should be split -def swap_child_folder(parent_folder, child_folder): +def move_folder_contents(src_folder, dst_folder): """ replaces the current folder contents with the contents of one child folder. This is used in the SCM monorepo flow, when it is necessary to use one subproject subfolder to replace the whole cloned git repo + /base-folder /base-folder + /pkg (src folder) /other/ + /other/ /pkg/ + /pkg/ + + /siblings + """ - for f in os.listdir(parent_folder): - if f != child_folder: - path = os.path.join(parent_folder, f) - if os.path.isfile(path): - os.remove(path) + # Remove potential "siblings" folders not wanted + src_folder_name = os.path.basename(src_folder) + for f in os.listdir(dst_folder): + if f != src_folder_name: # FIXME: Only works for 1st level subfolder + dst = os.path.join(dst_folder, f) + if os.path.isfile(dst): + os.remove(dst) else: - _internal_rmdir(path) - child = os.path.join(parent_folder, child_folder) - for f in os.listdir(child): - shutil.move(os.path.join(child, f), os.path.join(parent_folder, f)) + _internal_rmdir(dst) + + # Move all the contents + for f in os.listdir(src_folder): + src = os.path.join(src_folder, f) + dst = os.path.join(dst_folder, f) + if not os.path.exists(dst): + shutil.move(src, dst_folder) + else: + for sub_src in os.listdir(src): + shutil.move(os.path.join(src, sub_src), dst) + _internal_rmdir(src) + try: + os.rmdir(src_folder) + except OSError: + pass diff --git a/conans/test/functional/tools/scm/test_git.py b/conans/test/functional/tools/scm/test_git.py index 0b3d822a33f..a4e873b86a1 100644 --- a/conans/test/functional/tools/scm/test_git.py +++ b/conans/test/functional/tools/scm/test_git.py @@ -6,6 +6,8 @@ import pytest import six +from conans.test.assets.cmake import gen_cmakelists +from conans.test.assets.sources import gen_function_cpp from conans.test.utils.scm import create_local_git_repo, git_add_changes_commit, git_create_bare_repo from conans.test.utils.test_files import temp_folder from conans.test.utils.tools import TestClient @@ -484,8 +486,7 @@ class TestGitMonorepoSCMFlow: import os, shutil from conan import ConanFile from conan.tools.scm import Git - from conan.tools.files import load, update_conandata - from conan.tools.files.files import swap_child_folder + from conan.tools.files import load, update_conandata, move_folder_contents class Pkg(ConanFile): name = "{pkg}" @@ -509,7 +510,8 @@ def source(self): sources = self.conan_data["sources"] git.clone(url=sources["url"], target=".") git.checkout(commit=sources["commit"]) - swap_child_folder(self.source_folder, sources["folder"]) + move_folder_contents(os.path.join(self.source_folder, sources["folder"]), + self.source_folder) def build(self): cmake = os.path.join(self.source_folder, "CMakeLists.txt") @@ -558,6 +560,80 @@ def test_full_scm(self): assert "pkg2/0.1: MYCMAKE-BUILD: mycmake2!" in c2.out assert "pkg2/0.1: MYFILE-BUILD: my2header!" in c2.out + @pytest.mark.tool_cmake + def test_exports_sources_common_code_layout(self): + """ This is a copy of test_exports_sources_common_code_layout in test_in_subfolder.py + but instead of using "exports", trying to implement it with Git features + """ + c = TestClient() + conanfile = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.cmake import cmake_layout, CMake + from conan.tools.files import load, copy, save, update_conandata, move_folder_contents + from conan.tools.scm import Git + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeToolchain" + + def export(self): + git = Git(self) + scm_url, scm_commit = git.get_url_and_commit() + update_conandata(self, {"sources": {"commit": scm_commit, "url": scm_url}}) + + def layout(self): + self.folders.root = ".." + self.folders.subproject = "pkg" + cmake_layout(self) + + def source(self): + git = Git(self) + sources = self.conan_data["sources"] + git.clone(url=sources["url"], target=".") + git.checkout(commit=sources["commit"]) + # Layout is pkg/pkg/ and pkg/common/ + # Final we want is pkg/ and common/ + # NOTE: This abs_path is IMPORTANT to avoid the trailing "." + src_folder = os.path.abspath(self.source_folder) + move_folder_contents(src_folder, os.path.dirname(src_folder)) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + self.run(os.path.join(self.cpp.build.bindirs[0], "myapp")) + """) + cmake_include = "include(${CMAKE_CURRENT_LIST_DIR}/../common/myutils.cmake)" + c.save({"pkg/conanfile.py": conanfile, + "pkg/app.cpp": gen_function_cpp(name="main", includes=["../common/myheader"], + preprocessor=["MYDEFINE"]), + "pkg/CMakeLists.txt": gen_cmakelists(appsources=["app.cpp"], + custom_content=cmake_include), + "common/myutils.cmake": 'message(STATUS "MYUTILS.CMAKE!")', + "common/myheader.h": '#define MYDEFINE "MYDEFINEVALUE"'}) + c.init_git_repo() + + c.run("create pkg") + assert "MYUTILS.CMAKE!" in c.out + assert "main: Release!" in c.out + assert "MYDEFINE: MYDEFINEVALUE" in c.out + + # Local flow + c.run("install pkg") + c.run("build pkg") + assert "MYUTILS.CMAKE!" in c.out + assert "main: Release!" in c.out + assert "MYDEFINE: MYDEFINEVALUE" in c.out + + c.run("install pkg -s build_type=Debug") + c.run("build pkg") + assert "MYUTILS.CMAKE!" in c.out + assert "main: Debug!" in c.out + assert "MYDEFINE: MYDEFINEVALUE" in c.out + class TestConanFileSubfolder: """verify that we can have a conanfile in a subfolder