From 50045d8df5c74a864e070392dc5bde150771e79c Mon Sep 17 00:00:00 2001 From: Luis Martinez de Bartolome Izquierdo Date: Tue, 24 Aug 2021 00:38:43 +0200 Subject: [PATCH] Feature/patches following layout (#9361) * apply patches following the layout * Testing * breaking new interface * test local methods * fix test --- conan/tools/cmake/cmake.py | 11 ++-- conan/tools/files/__init__.py | 2 +- conan/tools/files/files.py | 11 ++++ conan/tools/files/patches.py | 21 ++++---- conans/test/functional/tools/test_files.py | 54 +++++++++++++++++-- .../unittests/tools/files/test_patches.py | 11 ++-- 6 files changed, 83 insertions(+), 27 deletions(-) diff --git a/conan/tools/cmake/cmake.py b/conan/tools/cmake/cmake.py index 7aa8b27e5f5..40ceba2e3fe 100644 --- a/conan/tools/cmake/cmake.py +++ b/conan/tools/cmake/cmake.py @@ -1,18 +1,15 @@ import os -import os import platform from conan.tools.cmake.utils import is_multi_configuration -from conan.tools.files import load_toolchain_args +from conan.tools.files import load_toolchain_args, chdir, mkdir from conan.tools.gnu.make import make_jobs_cmd_line_arg from conan.tools.meson.meson import ninja_jobs_cmd_line_arg from conan.tools.microsoft.msbuild import msbuild_verbosity_cmd_line_arg, \ msbuild_max_cpu_count_cmd_line_arg from conans.client import tools -from conans.client.tools.files import chdir from conans.client.tools.oss import cpu_count, args_to_string from conans.errors import ConanException -from conans.util.files import mkdir def _validate_recipe(conanfile): @@ -85,7 +82,7 @@ def configure(self, build_script_folder=None): build_folder = self._conanfile.build_folder generator_folder = self._conanfile.generators_folder - mkdir(build_folder) + mkdir(self._conanfile, build_folder) arg_list = [self._cmake_program] if self._generator: @@ -105,7 +102,7 @@ def configure(self, build_script_folder=None): command = " ".join(arg_list) self._conanfile.output.info("CMake command: %s" % command) - with chdir(build_folder): + with chdir(self, build_folder): self._conanfile.run(command) def _build(self, build_type=None, target=None): @@ -142,7 +139,7 @@ def build(self, build_type=None, target=None): def install(self, build_type=None): if not self._conanfile.should_install: return - mkdir(self._conanfile.package_folder) + mkdir(self._conanfile, self._conanfile.package_folder) self._build(build_type=build_type, target="install") def test(self, build_type=None, target=None, output_on_failure=False): diff --git a/conan/tools/files/__init__.py b/conan/tools/files/__init__.py index 048030fa6c8..d49555582b7 100644 --- a/conan/tools/files/__init__.py +++ b/conan/tools/files/__init__.py @@ -1,4 +1,4 @@ from conan.tools.files.files import load, save, mkdir, ftp_download, download, get, rename, \ - load_toolchain_args, save_toolchain_args + load_toolchain_args, save_toolchain_args, chdir from conan.tools.files.patches import patch, apply_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 3be63b57bd8..3435a414878 100644 --- a/conan/tools/files/files.py +++ b/conan/tools/files/files.py @@ -3,6 +3,7 @@ import os import platform import subprocess +from contextlib import contextmanager from conan.tools import CONAN_TOOLCHAIN_ARGS_FILE, CONAN_TOOLCHAIN_ARGS_SECTION from conans.client.downloaders.download import run_downloader @@ -222,3 +223,13 @@ def save_toolchain_args(content, generators_folder=None): toolchain_config[CONAN_TOOLCHAIN_ARGS_SECTION] = content_ with open(args_file, "w") as f: toolchain_config.write(f) + + +@contextmanager +def chdir(conanfile, newdir): + old_path = os.getcwd() + os.chdir(newdir) + try: + yield + finally: + os.chdir(old_path) diff --git a/conan/tools/files/patches.py b/conan/tools/files/patches.py index bc5f17a2c00..5c4db5352ee 100644 --- a/conan/tools/files/patches.py +++ b/conan/tools/files/patches.py @@ -1,4 +1,5 @@ import logging +import os import patch_ng @@ -24,11 +25,9 @@ def emit(self, record): self._output.info("%s: %s" % (self.patchname, logstr)) -def patch(conanfile, base_path=None, patch_file=None, patch_string=None, - strip=0, fuzz=False, **kwargs): +def patch(conanfile, patch_file=None, patch_string=None, strip=0, fuzz=False, **kwargs): """ Applies a diff from file (patch_file) or string (patch_string) in base_path directory or current dir if None - :param base_path: Base path where the patch should be applied. :param patch_file: Patch file that should be applied. :param patch_string: Patch string that should be applied. :param strip: Number of folders to be stripped from the path. @@ -39,6 +38,7 @@ def patch(conanfile, base_path=None, patch_file=None, patch_string=None, patch_type = kwargs.get('patch_type') patch_description = kwargs.get('patch_description') + if patch_type or patch_description: patch_type_str = ' ({})'.format(patch_type) if patch_type else '' patch_description_str = ': {}'.format(patch_description) if patch_description else '' @@ -56,7 +56,7 @@ def patch(conanfile, base_path=None, patch_file=None, patch_string=None, if not patchset: raise ConanException("Failed to parse patch: %s" % (patch_file if patch_file else "string")) - if not patchset.apply(root=base_path, strip=strip, fuzz=fuzz): + if not patchset.apply(root=conanfile.source_folder, strip=strip, fuzz=fuzz): raise ConanException("Failed to apply patch: %s" % patch_file) @@ -71,9 +71,7 @@ def apply_conandata_patches(conanfile): ``` patches: - patch_file: "patches/0001-buildflatbuffers-cmake.patch" - base_path: "source_subfolder" - patch_file: "patches/0002-implicit-copy-constructor.patch" - base_path: "source_subfolder" patch_type: backport patch_source: https://github.com/google/flatbuffers/pull/5650 patch_description: Needed to build with modern clang compilers (adapted to 1.11.0 tagged sources). @@ -84,24 +82,27 @@ def apply_conandata_patches(conanfile): patches: "1.11.0": - patch_file: "patches/0001-buildflatbuffers-cmake.patch" - base_path: "source_subfolder" - patch_file: "patches/0002-implicit-copy-constructor.patch" - base_path: "source_subfolder" patch_type: backport patch_source: https://github.com/google/flatbuffers/pull/5650 patch_description: Needed to build with modern clang compilers (adapted to 1.11.0 tagged sources). "1.12.0": - patch_file: "patches/0001-buildflatbuffers-cmake.patch" - base_path: "source_subfolder" ``` """ patches = conanfile.conan_data.get('patches') + if isinstance(patches, dict): assert conanfile.version, "Can only be applied if conanfile.version is already defined" entries = patches.get(conanfile.version, []) for it in entries: - patch(conanfile, **it) + if not "patch_file" in it: + raise ConanException("The 'conandata.yml' file needs a 'patch_file' entry for every" + " patch to be applied") + # The patch files are located in the root src + patch_file = os.path.join(conanfile.folders.base_source, it.pop("patch_file")) + patch(conanfile, patch_file=patch_file, **it) elif isinstance(patches, Iterable): for it in patches: patch(conanfile, **it) diff --git a/conans/test/functional/tools/test_files.py b/conans/test/functional/tools/test_files.py index b8b16044398..2e58ed96361 100644 --- a/conans/test/functional/tools/test_files.py +++ b/conans/test/functional/tools/test_files.py @@ -106,7 +106,8 @@ def build(self): client.save({"conanfile.py": conanfile}) client.run('create .') - assert mock_patch_ng.apply_args == (None, 0, False) + assert os.path.exists(mock_patch_ng.apply_args[0]) + assert mock_patch_ng.apply_args[1:] == (0, False) assert 'mypkg/1.0: Apply patch (security)' in str(client.out) @@ -119,6 +120,9 @@ class Pkg(ConanFile): name = "mypkg" version = "1.11.0" + def layout(self): + self.folders.source = "source_subfolder" + def build(self): apply_conandata_patches(self) """) @@ -126,15 +130,12 @@ def build(self): patches: "1.11.0": - patch_file: "patches/0001-buildflatbuffers-cmake.patch" - base_path: "source_subfolder" - patch_file: "patches/0002-implicit-copy-constructor.patch" - base_path: "source_subfolder" patch_type: backport patch_source: https://github.com/google/flatbuffers/pull/5650 patch_description: Needed to build with modern clang compilers. "1.12.0": - patch_file: "patches/0001-buildflatbuffers-cmake.patch" - base_path: "source_subfolder" """) client = TestClient() @@ -142,6 +143,49 @@ def build(self): 'conandata.yml': conandata_yml}) client.run('create .') - assert mock_patch_ng.apply_args == ('source_subfolder', 0, False) + + assert mock_patch_ng.apply_args[0].endswith('source_subfolder') + assert mock_patch_ng.apply_args[1:] == (0, False) + assert 'mypkg/1.11.0: Apply patch (backport): Needed to build with modern' \ ' clang compilers.' in str(client.out) + + # Test local methods + client.run("install . -if=install") + client.run("build . -if=install") + + assert 'conanfile.py (mypkg/1.11.0): Apply patch (backport): Needed to build with modern' \ + ' clang compilers.' in str(client.out) + + + +def test_no_patch_file_entry(): + conanfile = textwrap.dedent(""" + from conans import ConanFile + from conan.tools.files import apply_conandata_patches + + class Pkg(ConanFile): + name = "mypkg" + version = "1.11.0" + + def layout(self): + self.folders.source = "source_subfolder" + + def build(self): + apply_conandata_patches(self) + """) + conandata_yml = textwrap.dedent(""" + patches: + "1.11.0": + - wrong_entry: "patches/0001-buildflatbuffers-cmake.patch" + "1.12.0": + - patch_file: "patches/0001-buildflatbuffers-cmake.patch" + """) + + client = TestClient() + client.save({'conanfile.py': conanfile, + 'conandata.yml': conandata_yml}) + client.run('create .', assert_error=True) + + assert "The 'conandata.yml' file needs a 'patch_file' entry for every patch to be applied" \ + in str(client.out) diff --git a/conans/test/unittests/tools/files/test_patches.py b/conans/test/unittests/tools/files/test_patches.py index 8c055de80bc..3f403788cc0 100644 --- a/conans/test/unittests/tools/files/test_patches.py +++ b/conans/test/unittests/tools/files/test_patches.py @@ -35,30 +35,33 @@ def mock_fromstring(string): def test_single_patch_file(mock_patch_ng): conanfile = ConanFileMock() + conanfile.folders.set_base_source("my_source") conanfile.display_name = 'mocked/ref' patch(conanfile, patch_file='patch-file') assert mock_patch_ng.filename == 'patch-file' assert mock_patch_ng.string is None - assert mock_patch_ng.apply_args == (None, 0, False) + assert mock_patch_ng.apply_args == ("my_source", 0, False) assert len(str(conanfile.output)) == 0 def test_single_patch_string(mock_patch_ng): conanfile = ConanFileMock() + conanfile.folders.set_base_source("my_folder") conanfile.display_name = 'mocked/ref' patch(conanfile, patch_string='patch_string') assert mock_patch_ng.string == b'patch_string' assert mock_patch_ng.filename is None - assert mock_patch_ng.apply_args == (None, 0, False) + assert mock_patch_ng.apply_args == ("my_folder", 0, False) assert len(str(conanfile.output)) == 0 def test_single_patch_arguments(mock_patch_ng): conanfile = ConanFileMock() conanfile.display_name = 'mocked/ref' - patch(conanfile, patch_file='patch-file', base_path='root', strip=23, fuzz=True) + conanfile.folders.set_base_source("/path/to/sources") + patch(conanfile, patch_file='patch-file', strip=23, fuzz=True) assert mock_patch_ng.filename == 'patch-file' - assert mock_patch_ng.apply_args == ('root', 23, True) + assert mock_patch_ng.apply_args == ("/path/to/sources", 23, True) assert len(str(conanfile.output)) == 0