Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/scripts/validate-cci-patch-ng.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash

set -euo pipefail

# Get TEST_ALL_RECIPES from environment variable, default to 0 (false)
PYTHON_NG_TEST_ALL_RECIPES=${PYTHON_NG_TEST_ALL_RECIPES:-0}

SAMPLE_RECIPES_NUM=30
RECIPES_BUILD_NUM=10
RECIPES_BUILT_COUNT=0

# Ensure required tools are installed
COMMANDS=("conan" "yq" "jq")
for cmd in "${COMMANDS[@]}"; do
if ! which $cmd &> /dev/null; then
echo "ERROR: $cmd is not installed. Please install $cmd to proceed."
exit 1
fi
done

# Find all conanfile.py files that use apply_conandata_patches
RECIPES=$(find . -type f -name "conanfile.py" -exec grep -l "apply_conandata_patches(self)" {} + | sort | uniq)
# And does not need system requirement
RECIPES=$(grep -L "/system" $RECIPES)
# And does not contain Conan 1 imports
RECIPES=$(grep -L "from conans" $RECIPES)

echo "Found $(echo "$RECIPES" | wc -l) recipes using apply_conandata_patches."

if [ "${PYTHON_NG_TEST_ALL_RECIPES}" -eq "1" ]; then
SAMPLE_RECIPES_NUM=$(echo "$RECIPES" | wc -l)
RECIPES_BUILD_NUM=$SAMPLE_RECIPES_NUM
echo "PYTHON_NG_TEST_ALL_RECIPES is set to 1, testing all $SAMPLE_RECIPES_NUM recipes."
else
RECIPES=$(shuf -e ${RECIPES[@]} -n $SAMPLE_RECIPES_NUM)
echo "Pick $SAMPLE_RECIPES_NUM random recipes to test:"
echo "$RECIPES"
fi

# Run conan create for each sampled recipe
for it in $RECIPES; do

if [ $RECIPES_BUILT_COUNT -ge $RECIPES_BUILD_NUM ]; then
echo "Reached the limit of $RECIPES_BUILD_NUM recipes built, stopping. All done."
break
fi

recipe_dir=$(dirname "${it}")
pushd "$recipe_dir" > /dev/null
echo "Testing recipe in directory: ${recipe_dir}"
# Get a version from conandata.yml that uses a patch
version=$(yq '.patches | keys | .[0]' conandata.yml 2>/dev/null)
if [ -z "$version" ]; then
echo "ERROR: No patches found in conandata.yml for $recipe_dir, skipping."
popd > /dev/null
continue
fi
version=$(echo ${version} | tr -d '"')
# Replace apply_conandata_patches to exit just after applying patches
sed -i -e 's/apply_conandata_patches(self)/apply_conandata_patches(self); import sys; sys.exit(0)/g' conanfile.py

# Allow conan create to fail without stopping the script, we will handle errors manually
set +e

# Create the package with the specified version
output=$(conan create . --version=${version} 2>&1)
# Accept some errors as non-fatal
if [ $? -ne 0 ]; then
echo "WARNING: conan create failed for $recipe_dir"
allowed_errors=(
"ERROR: There are invalid packages"
"ERROR: Version conflict"
"ERROR: Missing binary"
"Failed to establish a new connection"
"ConanException: sha256 signature failed"
"ConanException: Error downloading file"
"ConanException: Cannot find"
"certificate verify failed: certificate has expired"
"NotFoundException: Not found"
)
# check if any allowed error is in the output
if printf '%s\n' "${allowed_errors[@]}" | grep -q -f - <(echo "$output"); then
echo "WARNING: Could not apply patches, skipping build:"
echo "$output" | tail -n 10
echo "-------------------------------------------------------"
else
echo "ERROR: Fatal error during conan create command execution:"
echo "$output"
popd > /dev/null
exit 1
fi
else
echo "INFO: Successfully patched $recipe_dir."
echo "$output" | tail -n 10
echo "-------------------------------------------------------"
RECIPES_BUILT_COUNT=$((RECIPES_BUILT_COUNT + 1))
fi
popd > /dev/null
done
47 changes: 47 additions & 0 deletions .github/workflows/conan-center-index.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Validate Applying Patch in Conan Center Index

on:
push:
paths-ignore:
- 'doc/**'
- '**/*.md'
- 'LICENSE'
- 'example/**'
- '.gitignore'
- 'tests/**'
workflow_dispatch:
pull_request:
paths-ignore:
- 'doc/**'
- '**/*.md'
- 'LICENSE'
- 'example/**'
- '.gitignore'
- 'tests/**'

jobs:
conan-center-index-validate:
name: "Validate Patche-NG in Conan Center Index"
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
path: python-ng

- name: Setup Conan client
uses: conan-io/setup-conan@v1

- name: Setup Python NG
run: pip install -e ./python-ng

- name: Checkout conan-center-index
uses: actions/checkout@v5
with:
repository: conan-io/conan-center-index
path: conan-center-index
ref: 'd8efbb6f3c51b134205f01d3f8d90bdad1a67fe6'

- name: Validate Python-NG patch application
working-directory: conan-center-index/recipes
run: bash "${GITHUB_WORKSPACE}/python-ng/.github/scripts/validate-cci-patch-ng.sh"
Comment on lines +24 to +47

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
48 changes: 48 additions & 0 deletions .github/workflows/conan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Validate Conan client

on:
push:
paths-ignore:
- 'doc/**'
- '**/*.md'
- 'LICENSE'
- 'example/**'
- '.gitignore'
- 'tests/**'
workflow_dispatch:
pull_request:
paths-ignore:
- 'doc/**'
- '**/*.md'
- 'LICENSE'
- 'example/**'
- '.gitignore'
- 'tests/**'

jobs:
conan-client-validate:
name: "Validate Conan client"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5

- name: Setup python
uses: actions/setup-python@v6
with:
python-version: 3.12
architecture: x64

- name: Clone conan client
run: |
git clone --depth 1 https://github.com/conan-io/conan.git
cd conan/
pip install -r conans/requirements_dev.txt
pip install -r conans/requirements.txt

- name: Run Conan client tests involving patch-ng
run: |
pip install -e .
cd conan/
pytest -v test/functional/test_third_party_patch_flow.py
pytest -v test/functional/tools/test_files.py
pytest -v test/unittests/tools/files/test_patches.py
Comment on lines +24 to +48

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 2 months ago

To fix this problem, explicitly define the minimum required permissions using a permissions block. Since this workflow only runs checks, installs dependencies, and executes tests—it does not appear to require write access—contents: read is usually the minimal necessary permission for these CI jobs. The permissions block can be added either at the top (root) level so it applies globally to all jobs, or per job for more granular control. Adding it at the root is sufficient here. To implement, insert the following directly after the name: Validate Conan client line and before the on: block in .github/workflows/conan.yml:

permissions:
  contents: read

No additional packages or methods need to be added.

Suggested changeset 1
.github/workflows/conan.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/conan.yml b/.github/workflows/conan.yml
--- a/.github/workflows/conan.yml
+++ b/.github/workflows/conan.yml
@@ -1,4 +1,6 @@
 name: Validate Conan client
+permissions:
+  contents: read
 
 on:
   push:
EOF
@@ -1,4 +1,6 @@
name: Validate Conan client
permissions:
contents: read

on:
push:
Copilot is powered by AI and may make mistakes. Always verify output.
2 changes: 1 addition & 1 deletion .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Main workflow
name: Python Patch-NG Tests

on:
push:
Expand Down
5 changes: 4 additions & 1 deletion patch_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
SOFTWARE.
"""
__author__ = "Conan.io <[email protected]>"
__version__ = "1.19.0-dev"
__version__ = "1.19.0"
__license__ = "MIT"
__url__ = "https://github.com/conan-io/python-patch"

Expand Down Expand Up @@ -900,6 +900,9 @@ def _normalize_filenames(self):
p.source = xnormpath(p.source)
p.target = xnormpath(p.target)

p.source = p.source.strip(b'"')
p.target = p.target.strip(b'"')

sep = b'/' # sep value can be hardcoded, but it looks nice this way

# references to parent are not allowed
Expand Down
6 changes: 6 additions & 0 deletions tests/filewithspace/0001-quote.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
diff '--color=auto' -ruN "b/Wrapper/FreeImage.NET/cs/Samples/Sample 01 - Loading and saving/Program.cs" "a/Wrapper/FreeImage.NET/cs/Samples/Sample 01 - Loading and saving/Program.cs"
--- "b/Wrapper/FreeImage.NET/cs/Samples/Sample 01 - Loading and saving/Program.cs" 2025-10-08 15:56:02.302486070 +0200
+++ "a/Wrapper/FreeImage.NET/cs/Samples/Sample 01 - Loading and saving/Program.cs" 2025-10-08 15:21:46.283174211 +0200
@@ -1 +1 @@
-feriunt summos, fulmina montes.
+lux oculorum laetificat animam.
34 changes: 34 additions & 0 deletions tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,40 @@ def test_add_move_and_update_file(self):
content = f.read()
self.assertTrue(b'dum tempus habemus, operemur bonum' in content)

class TestPatchFileWithSpaces(unittest.TestCase):

def setUp(self):
self.save_cwd = os.getcwd()
self.tmpdir = mkdtemp(prefix=self.__class__.__name__)
shutil.copytree(join(TESTS, 'filewithspace'), join(self.tmpdir, 'filewithspace'))
patch_folder = join(self.tmpdir, "a", "Wrapper", "FreeImage.NET", "cs", "Samples", "Sample 01 - Loading and saving")
os.makedirs(patch_folder, exist_ok=True)
self.program_cs = join(patch_folder, "Program.cs")
with open(self.program_cs, 'w') as fd:
fd.write("feriunt summos, fulmina montes.")

def tearDown(self):
os.chdir(self.save_cwd)
remove_tree_force(self.tmpdir)

def test_patch_with_white_space(self):
"""When a patch file is generated using `diff -ruN b/ a/` command, and
contains white spaces in the file path, the patch should be applied correctly.

Reported by https://github.com/conan-io/conan/issues/16727
"""

os.chdir(self.tmpdir)
print("TMPDIR:", self.tmpdir)
pto = patch_ng.fromfile(join(self.tmpdir, 'filewithspace', '0001-quote.diff'))
self.assertEqual(len(pto), 1)
self.assertEqual(pto.items[0].type, patch_ng.PLAIN)
self.assertTrue(pto.apply())
with open(self.program_cs, 'rb') as f:
content = f.read()
self.assertTrue(b'lux oculorum laetificat animam.' in content)


class TestHelpers(unittest.TestCase):
# unittest setting
longMessage = True
Expand Down