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

extra patches path #17520

Draft
wants to merge 20 commits into
base: develop2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
81 changes: 58 additions & 23 deletions conan/tools/files/patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import shutil

import patch_ng
import yaml

from conan.errors import ConanException
from conans.util.files import mkdir
from conan.internal.paths import DATA_YML
from conans.util.files import mkdir, load, save


class PatchLogHandler(logging.Handler):
Expand Down Expand Up @@ -122,27 +124,60 @@ def export_conandata_patches(conanfile):
if conanfile.conan_data is None:
raise ConanException("conandata.yml not defined")

patches = conanfile.conan_data.get('patches')
if patches is None:
conanfile.output.info("export_conandata_patches(): No patches defined in conandata")
return
conanfile_patches = conanfile.conan_data.get('patches')

if isinstance(patches, dict):
assert conanfile.version, "Can only be exported if conanfile.version is already defined"
entries = patches.get(conanfile.version, [])
if entries is None:
conanfile.output.warning(f"export_conandata_patches(): No patches defined for version {conanfile.version} in conandata.yml")
def _handle_patches(patches, patches_folder):
if patches is None:
conanfile.output.info("export_conandata_patches(): No patches defined in conandata")
return
elif isinstance(patches, list):
entries = patches
else:
raise ConanException("conandata.yml 'patches' should be a list or a dict {version: list}")
for it in entries:
patch_file = it.get("patch_file")
if patch_file:
src = os.path.join(conanfile.recipe_folder, patch_file)
dst = os.path.join(conanfile.export_sources_folder, patch_file)
if not os.path.exists(src):
raise ConanException(f"Patch file does not exist: '{src}'")
mkdir(os.path.dirname(dst))
shutil.copy2(src, dst)

if isinstance(patches, dict):
assert conanfile.version, "Can only be exported if conanfile.version is already defined"
entries = patches.get(conanfile.version, [])
if entries is None:
conanfile.output.warning("export_conandata_patches(): No patches defined for "
f"version {conanfile.version} in conandata.yml")
return
elif isinstance(patches, list):
entries = patches
else:
raise ConanException("conandata.yml 'patches' should be a list or a dict "
"{version: list}")
for it in entries:
patch_file = it.get("patch_file")
if patch_file:
src = os.path.join(patches_folder, patch_file)
dst = os.path.join(conanfile.export_sources_folder, patch_file)
if not os.path.exists(src):
raise ConanException(f"Patch file does not exist: '{src}'")
mkdir(os.path.dirname(dst))
shutil.copy2(src, dst)
return entries

_handle_patches(conanfile_patches, conanfile.recipe_folder)

# NOTE: This only works after drim_conandata, it assumes versions are gone now in the cache
memsharded marked this conversation as resolved.
Show resolved Hide resolved
extra_path = conanfile.conf.get("core.sources.patch:extra_path")
if extra_path:
if not os.path.isdir(extra_path):
raise ConanException(f"Patches extra path '{extra_path}' does not exist")
pkg_path = os.path.join(extra_path, conanfile.name)
if not os.path.isdir(pkg_path):
return
data_path = os.path.join(pkg_path, DATA_YML)
try:
data = yaml.safe_load(load(data_path))
except Exception as e:
raise ConanException("Invalid yml format at {}: {}".format(data_path, e))
data = data or {}
conanfile.output.info(f"Applying extra patches 'core.sources.patch:extra_path': {data_path}")
new_patches = _handle_patches(data.get('patches'), pkg_path)

# Update the CONANDATA.YML
conanfile_patches = conanfile_patches or []
conanfile_patches.extend(new_patches)
conanfile.conan_data['patches'] = conanfile_patches
# Saving in the EXPORT folder
conanfile_data_path = os.path.join(conanfile.export_folder, DATA_YML)
new_conandata_yml = yaml.safe_dump(conanfile.conan_data, default_flow_style=False)
save(conanfile_data_path, new_conandata_yml)
1 change: 1 addition & 0 deletions conans/model/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"core.sources:download_urls": "List of URLs to download backup sources from",
"core.sources:upload_url": "Remote URL to upload backup sources to",
"core.sources:exclude_urls": "URLs which will not be backed up",
"core.sources.patch:extra_path": "Extra path to search for patch files for conan create",
# Package ID
"core.package_id:default_unknown_mode": "By default, 'semver_mode'",
"core.package_id:default_non_embed_mode": "By default, 'minor_mode'",
Expand Down
50 changes: 47 additions & 3 deletions test/functional/tools/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

from conan.test.assets.genconanfile import GenConanfile
from conan.test.utils.file_server import TestFileServer
from conan.test.utils.test_files import temp_folder
from conan.test.utils.tools import TestClient
from conans.util.files import save
from conans.util.files import save, load


class MockPatchset:
Expand Down Expand Up @@ -365,7 +366,7 @@ def build(self):
assert mock_patch_ng.apply_args[1:] == (0, False)


def test_export_conandata_patches(mock_patch_ng):
def test_export_conandata_patches():
conanfile = textwrap.dedent("""
import os
from conan import ConanFile
Expand Down Expand Up @@ -402,7 +403,7 @@ def source(self):
# wrong patches
client.save({"conandata.yml": "patches: 123"})
client.run("create .", assert_error=True)
assert "conandata.yml 'patches' should be a list or a dict" in client.out
assert "conandata.yml 'patches' should be a list or a dict" in client.out

# No patch found
client.save({"conandata.yml": conandata_yml})
Expand Down Expand Up @@ -447,3 +448,46 @@ def build(self):
client.save({"conandata.yml": conandata_yml, "conanfile.py": conanfile})
client.run("create .")
assert "No patches defined for version 1.0 in conandata.yml" in client.out


def test_export_conandata_patches_extra_origin():
conanfile = textwrap.dedent("""
import os
from conan import ConanFile
from conan.tools.files import export_conandata_patches, load, trim_conandata

class Pkg(ConanFile):
name = "mypkg"
version = "1.0"

def export(self):
trim_conandata(self)

def layout(self):
self.folders.source = "source_subfolder"

def export_sources(self):
export_conandata_patches(self)

def source(self):
self.output.info(load(self, os.path.join(self.export_sources_folder,
"patches/mypatch.patch")))
""")
client = TestClient(light=True)
patches_folder = temp_folder()
conandata_yml = textwrap.dedent("""
patches:
"1.0":
- patch_file: "patches/mypatch.patch"
""")
save(os.path.join(patches_folder, "mypkg", "conandata.yml"), conandata_yml)
save(os.path.join(patches_folder, "mypkg", "patches", "mypatch.patch"), "mypatch!!!")

client.save({"conanfile.py": conanfile,
"conandata.yml": "patches:"})
client.run(f'create . -cc core.sources.patch:extra_path="{patches_folder}"')
assert "mypkg/1.0: Applying extra patches" in client.out
assert "mypkg/1.0: mypatch!!!" in client.out

conandata = load(client.exported_layout().conandata())
assert "patch_file: patches/mypatch.patch" in conandata
Loading