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

Open
wants to merge 20 commits into
base: develop2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions conan/internal/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
3 changes: 3 additions & 0 deletions conan/tools/files/conandata.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,8 @@ def trim_conandata(conanfile, raise_if_missing=True):
if version_data is not None:
result[k] = {version: version_data}

# Update the internal conanfile data too
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is important @AbrilRBS @jcar87
This was not done before, but it seems it make sense, otherwise self.conan_data in recipes can have outdated/trimmed information. Please check if this could be an issue in any ConanCenter flow.

Copy link
Member

@AbrilRBS AbrilRBS Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not an issue in the current CCI CI, as the current flow is to first export each version separatedly, and upload it to a staging repo from which workers can then download it to build them, so the create/install is done on an already trimmed recipe conandata

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The possible issue would be if some custom command or something was reading conanfile.conandata after the export that trimmed it and somehow reading other versions information too. I doubt this happens ,but just in case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mmm, we don't use trim_conandata() (yet) in any recipe in Conan Center, so I'm unsure as to how this change can be currently relevant?

This makes sense regardless, but seems possibly unrelated to the PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result for the shake of validation should be the same, the important thing is to have the final exported conandata.yml trimmed or not. It doesn't matter if it is trimmed by the ConanCenter hook or by the recipe, what is important is that the export_conandata_patches is resilient to either case, having it trimmed or not, and that is what the new checks validate.

conanfile.conan_data = result

new_conandata_yml = yaml.safe_dump(result, default_flow_style=False)
save(path, new_conandata_yml)
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)

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.setdefault(conanfile.version, []).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)
64 changes: 61 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,60 @@ 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


@pytest.mark.parametrize("trim", [True, False])
def test_export_conandata_patches_extra_origin(trim):
conanfile = textwrap.dedent(f"""
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):
if {trim}:
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!!!")

pkg_conandata = textwrap.dedent("""\
patches:
"1.1":
- patch_file: "patches/mypatch2.patch"
""")
client.save({"conanfile.py": conanfile,
"conandata.yml": pkg_conandata,
"patches/mypatch2.patch": ""})
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 "1.0" in conandata
assert "patch_file: patches/mypatch.patch" in conandata

if trim:
assert "1.1" not in conandata
else:
assert "1.1" in conandata
Loading