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

Feature/patches following layout #9361

Merged
merged 5 commits into from Aug 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 4 additions & 7 deletions 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):
Expand Down Expand Up @@ -85,7 +82,7 @@ def configure(self, source_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:
Expand All @@ -105,7 +102,7 @@ def configure(self, source_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):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion 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
11 changes: 11 additions & 0 deletions conan/tools/files/files.py
Expand Up @@ -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
Expand Down Expand Up @@ -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)
21 changes: 11 additions & 10 deletions conan/tools/files/patches.py
@@ -1,4 +1,5 @@
import logging
import os

import patch_ng

Expand All @@ -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.
Expand All @@ -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 ''
Expand All @@ -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)


Expand All @@ -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).
Expand All @@ -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)
54 changes: 49 additions & 5 deletions conans/test/functional/tools/test_files.py
Expand Up @@ -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)


Expand All @@ -119,29 +120,72 @@ 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":
- 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()
client.save({'conanfile.py': conanfile,
'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)
11 changes: 7 additions & 4 deletions conans/test/unittests/tools/files/test_patches.py
Expand Up @@ -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


Expand Down