Skip to content

copy all files as non-executable by default - #2596

Open
h-vetinari wants to merge 1 commit into
conda-forge:mainfrom
h-vetinari:exe
Open

copy all files as non-executable by default#2596
h-vetinari wants to merge 1 commit into
conda-forge:mainfrom
h-vetinari:exe

Conversation

@h-vetinari

Copy link
Copy Markdown
Member

As it turns out, there are circumstances where conda-smithy will wrongly populate a rerendered recipe with executable bits also for non-executable files, e.g.

[main 444ef60] MNT: Re-rendered with conda-smithy 2026.6.14 and conda-forge-pinning 2026.06.14.21.57.4
 12 files changed, 210 insertions(+), 108 deletions(-)
 delete mode 100755 .azure-pipelines/azure-pipelines-linux.yml
 mode change 100644 => 100755 .ci_support/README
 mode change 100644 => 100755 .gitattributes
 create mode 100644 .github/workflows/conda-build.yml
 mode change 100644 => 100755 .gitignore
 mode change 100644 => 100755 .scripts/logging_utils.sh
 mode change 100644 => 100755 LICENSE.txt

which is what we ran into with conda-forge/conda-forge-bot#6237.

In fact, we're definitely generating executable bits on .azure-pipelines/*.yamls, e.g. on the current main branch of arrow-cpp-feedstock (note the 755 at the beginning):

>git ls-files --stage .azure-pipelines
100755 9d232948952fed848626c3534ae48192e88540b8 0       .azure-pipelines/azure-pipelines-osx.yml
100755 2244f1131dc4a489b8366f51c42ce69188b0e478 0       .azure-pipelines/azure-pipelines-win.yml

due to the fact that those files are treated as templates

platform_templates = {
"linux": [
".scripts/run_docker_build.sh",
".scripts/build_steps.sh",
".azure-pipelines/azure-pipelines-linux.yml",
],
"osx": [
".azure-pipelines/azure-pipelines-osx.yml",
".scripts/run_osx_build.sh",
],
"win": [
".azure-pipelines/azure-pipelines-win.yml",
".scripts/run_win_build.bat",
],
}

which is borked, because
def _render_template_exe_files(forge_config, jinja_env, template_files, forge_dir):

just sets the executable bit unconditionally for everything.

So to clean this up, copy everything as non-executable by default (9457bb2), then distinguish executable files from templates, and add the exec bit only for the former. Add a test that validates that all executable files have the exec bit, and that all other files don't.

@h-vetinari
h-vetinari requested a review from a team as a code owner June 15, 2026 06:43
@h-vetinari
h-vetinari force-pushed the exe branch 3 times, most recently from 722622c to 97fd607 Compare June 15, 2026 07:26
Comment on lines +138 to +146
try:
import pygit2

# ensure we add all files as non-exectuable by default
entry = repo.index[index_path]
entry.mode = pygit2.GIT_FILEMODE_BLOB # 100644 (non-executable)
repo.index.add(entry)
except ImportError:
pass

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Note, I've used the try/except approach here to stay compatible with

try:
import pygit2
if search_parent_directories:
path = pygit2.discover_repository(path)
if path is not None:
try:
no_search = pygit2.enums.RepositoryOpenFlag.NO_SEARCH
except AttributeError: # pygit2 < 1.14
no_search = pygit2.GIT_REPOSITORY_OPEN_NO_SEARCH
repo = pygit2.Repository(path, no_search)
except ImportError:
pass
except pygit2.GitError:
pass

which seems to intentionally avoid a hard dependence on pygit2

@h-vetinari

h-vetinari commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

@beckerm @jaimergp, would appreciate reviews on this, as this is currently blocking1 the bot from picking up new pinning files, see conda-forge/conda-forge-bot#6237; it looks like another latent bug that got surfaced by ... something. 🤷

Footnotes

  1. obviously this PR alone is not enough; but I'd be happy to do another quick release after this.

@h-vetinari
h-vetinari force-pushed the exe branch 3 times, most recently from 75e9af9 to bb9f098 Compare June 15, 2026 09:17

@beckermr beckermr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here are the permissions of the files on main:

(base) beckermr@mac conda-smithy % ls -lah conda_smithy/templates 
total 320
drwxr-xr-x  27 beckermr  staff   864B Jun 14 04:27 .
drwxr-xr-x  29 beckermr  staff   928B Jun 14 04:27 ..
-rw-r--r--@  1 beckermr  staff   2.9K Dec 21  2023 appveyor.yml.tmpl
-rw-r--r--@  1 beckermr  staff   3.7K Jun 14 04:27 azure-pipelines-linux.yml.tmpl
-rw-r--r--@  1 beckermr  staff   3.1K Jun 14 04:27 azure-pipelines-osx.yml.tmpl
-rw-r--r--@  1 beckermr  staff   5.2K Jun 14 04:27 azure-pipelines-win.yml.tmpl
-rw-r--r--@  1 beckermr  staff   1.8K Apr 18 17:06 azure-pipelines.yml.tmpl
-rw-r--r--@  1 beckermr  staff   8.5K Jun 14 04:27 build_steps.sh.tmpl
-rw-r--r--@  1 beckermr  staff   2.8K Dec 21  2023 circle.yml.tmpl
-rw-r--r--@  1 beckermr  staff   277B Jul 24  2023 conda-forge.yml.ci-skel.tmpl
-rw-r--r--@  1 beckermr  staff   2.9K Jun 14 04:27 create_conda_build_artifacts.bat.tmpl
-rwxr-xr-x@  1 beckermr  staff   4.2K Jun 14 04:27 create_conda_build_artifacts.sh.tmpl
-rw-r--r--@  1 beckermr  staff   934B Jun 14 04:27 create_pagefile.bat.tmpl
-rwxr-xr-x@  1 beckermr  staff   501B Jun 14 04:27 create_pagefile.sh.tmpl
-rw-r--r--@  1 beckermr  staff   1.6K Jun 14 04:27 drone.yml.tmpl
-rw-r--r--@  1 beckermr  staff    34B Jul 24  2023 fast_finish_ci_pr_build.sh.tmpl
-rw-r--r--@  1 beckermr  staff   1.3K Jun 14 04:27 free_disk_space.sh.tmpl
-rw-r--r--@  1 beckermr  staff    13K Jun 14 04:27 github-actions.yml.tmpl
-rw-r--r--@  1 beckermr  staff   2.3K Jul 24  2023 meta.yaml.ci-skel.tmpl
-rw-r--r--@  1 beckermr  staff   3.6K Nov  9  2025 pixi.toml.tmpl
-rw-r--r--@  1 beckermr  staff    13K May 24 08:13 README.md.tmpl
-rw-r--r--@  1 beckermr  staff   5.1K Jun 14 04:27 run_docker_build.sh.tmpl
-rw-r--r--@  1 beckermr  staff   7.9K Jun 14 04:27 run_osx_build.sh.tmpl
-rw-r--r--@  1 beckermr  staff    10K May 11  2025 run_win_build.bat.tmpl
-rw-r--r--@  1 beckermr  staff   6.9K Jun 14 04:27 SetPageFileSize.ps1.tmpl
-rw-r--r--@  1 beckermr  staff   2.5K Apr 18 17:06 travis.yml.tmpl
-rw-r--r--@  1 beckermr  staff   1.2K Jul 24  2023 woodpecker.yml.tmpl

Instead of maintaining separate lists of which files need to be executable, we should set the correct permissions on the template file in the repo itself and ensure those permissions are preserved after the file is rendered.

@h-vetinari

Copy link
Copy Markdown
Member Author

we should set the correct permissions on the template file in the repo itself and ensure those permissions are preserved after the file is rendered.

why? This is exactly the reason why we find ourselves in this situation. The code in copy_file looks completely benign, it is copying non-executable files, and yet they still end up with the executable bit.

I find it much simpler to just clear the exec bit when copying; we already require all the machinery for setting the executable bit anyway, and we already have a list of all templates in get_common_script (again, mixed between scripts and templates currently). And finally, we need a list of the expected files with the executable bit for doing an exhaustive test that checks whether all rendered files have the bit (or not) as expected. So something like this PR is IMO the simplest & cleanest way forward here.

Explicit is better than implicit. And making sure the executable bit gets copied correctly in all corner-cases sounds like a major maintenance headache. If you wanna give this a shot nevertheless, you can use 6ca5f14 as a short-cut to come up with an alternative PR.

@beckermr

Copy link
Copy Markdown
Member

It's not implicit to preserve the bits on the file you are copying.

@h-vetinari

Copy link
Copy Markdown
Member Author

To flesh out the point about how surprising "just copy" can get: This is the state on main here

>git ls-files --stage conda_smithy/feedstock_content
100644 a47316be6ae90505a64fbe303a9fdf21a056a4a4 0       conda_smithy/feedstock_content/.ci_support/README
100755 9528e4b365334716b321d34ed8f0453525e2273f 0       conda_smithy/feedstock_content/.circleci/checkout_merge_commit.sh
100644 85ccb8f4f327dd4396ef57e0d05c4309c1fe871f 0       conda_smithy/feedstock_content/.gitattributes
100644 b4e6e61b9f4996c9d4ae12126bd695437771843c 0       conda_smithy/feedstock_content/.github/workflows/conda-build.yml
100644 86a9c55b4d97f5f0695455524660218e3a38d111 0       conda_smithy/feedstock_content/.gitignore
100644 aff009f0c53a856bf31f12912c2a371c2302d266 0       conda_smithy/feedstock_content/.scripts/logging_utils.sh
100644 c5aa738f1bd246658c9da9e1bbe2502df6d0e173 0       conda_smithy/feedstock_content/LICENSE.txt
100644 56007e1b1766e097b95e8b4fd0116be2966d06e3 0       conda_smithy/feedstock_content/README
100755 3beb68ccd1f77ad2529ecb2cd184aa2998eaa570 0       conda_smithy/feedstock_content/build-locally.py

Yet what we see in the bot repo (and I was able to reproduce locally) is

 mode change 100644 => 100755 .ci_support/README
 mode change 100644 => 100755 .gitattributes
 mode change 100644 => 100755 .gitignore
 mode change 100644 => 100755 LICENSE.txt

IOW,

shutil.copymode(src, dst)
repo = get_repo(dst)
if repo:
index_path = Path(dst).resolve().relative_to(repo.workdir).as_posix()
repo.index.add(index_path)
repo.index.write()

silently ended up adding an executable bit, despite using the very appropriate-looking shutil.copymode.

@beckermr

Copy link
Copy Markdown
Member

Indeed. The doc string of copymode says it should copy the permissions bits. So that indicates a bug somewhere in the code that needs to be fixed. This PR is not fixing that bug, but is instead just papering over it with more code. Let's fix the bug.

@h-vetinari

Copy link
Copy Markdown
Member Author

Let's fix the bug.

I'm awaiting your alternative implementation with interest. :)

@beckermr

Copy link
Copy Markdown
Member

I think the bot has a different bug. It handles its own file permissions due to how containers are used.

Smithy is doing the correct thing locally on my machine, and the block of code with copymode you identified above (conda-smithy/conda_smithy/feedstock_io.py) is not adding permissions bits (at least on Unix-like platforms). Maybe this is a Windows-only issue?

I made a PR #2599 that puts in a smaller change just to Azure (where yaml files were incorrectly passed to the exe rendering function), and leaves the rest of the code for setting executable bits localized to the per-provider functions as it is now.

@h-vetinari

h-vetinari commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

Maybe this is a Windows-only issue?

The failure on the bot repo was on linux, and I reproduced it locally in WSL.

Smithy is doing the correct thing locally on my machine

Are you looking at the file system (ls -l etc.) or what actually gets committed in git git ls-files --stage ...?

I made a PR #2599 that puts in a smaller change just to Azure (where yaml files were incorrectly passed to the exe rendering function), and leaves the rest of the code for setting executable bits localized to the per-provider functions as it is now.

I strongly doubt that this will fix anything, not least because the failures were not for azure-specific files, but for the feedstock_content ones. But rather than take my or your word for it, you should reuse the test I wrote while researching/debugging this (6ca5f14), and get it passing in your PR in similar form.

@beckermr

Copy link
Copy Markdown
Member

The git staged thing shows the correct permissions on my machine.

@h-vetinari

Copy link
Copy Markdown
Member Author

The git staged thing shows the correct permissions on my machine.

That's nice, but there's more machines involved. I can reproduce the error locally, so... Anyway, here's a branch with a self-contained commit (58aeb07) that you can pick anywhere and see if it works. On my machine, this fails with

$ pytest tests/test_configure_feedstock.py::test_exec_bits -v
[...]
=============================================== short test summary info ================================================
FAILED tests/test_configure_feedstock.py::test_exec_bits[conda-build-azure] - AssertionError: assert True == ('LICENSE.txt' in ['.circleci/checkout_merge_commit.sh', '.scripts/SetPageFileSize.p...

@h-vetinari

Copy link
Copy Markdown
Member Author

I think the bot has a different bug. It handles its own file permissions due to how containers are used.

The failure didn't involve containers by the way; it was in the local_rerend_exec result

@beckermr

Copy link
Copy Markdown
Member

OK @h-vetinari. I made progress after dropping a kid at camp.

So IDK what is going on, but at least within smithy's own test suite, things are working fine.

I am going to continue debugging the code in the feedstock OPs PR, since it appears to be the most direct way to trigger it.

@beckermr

Copy link
Copy Markdown
Member

Ooooof. I found the actual bug, and it is not pretty. When conda-smithy 2026.6.14 was packaged in the feedstock, the permissions came out wrong on the files. 😱

Here is 2026.6.14 from the package on anaconda.org:

(base) beckermr@finnegan conda-smithy-2026.6.14-unix_pyh9ac5cc3_0 % ls -lah site-packages/conda_smithy/feedstock_content/.ci_support/README 
-rwxr-xr-x@ 1 beckermr  staff   363B Jun 14 18:44 site-packages/conda_smithy/feedstock_content/.ci_support/README

and here is 2026.5.29 from the package on anaconda.org:

(base) beckermr@finnegan conda-smithy-2026.5.29-unix_pyh9ac5cc3_0 % ls -lah site-packages/conda_smithy/feedstock_content/.ci_support/README 
-rw-r--r--@ 1 beckermr  staff   357B May 29 15:24 site-packages/conda_smithy/feedstock_content/.ci_support/README

I confirmed by testing conda-forge-feedstock-ops using a local install of smithy from source instead of the anaconda.org package in this CI run. Now the local rerender permissions come out right, but the ones in the container (which uses smithy from anaconda.org) come out wrong.

So this PR is indeed papering over a more fundamental problem in the tooling somewhere. The next place to look is in the CI jobs for smithy 2026.6.14.

@beckermr

Copy link
Copy Markdown
Member

Yup, there it is:

  ?rwxrwxr-x 0/0        363 2026-06-14 23:44:43 site-packages/conda_smithy/feedstock_content/.ci_support/README 

from this CI job.

Oh boy today is fun!

@beckermr

beckermr commented Jun 15, 2026

Copy link
Copy Markdown
Member

My hunch is that this is something in setuptools 82 vs 81. That is the only difference that stands out in the CI jobs. Checking now.

Nope. Must be conda-build or something...

@beckermr

Copy link
Copy Markdown
Member

debugging moved to here: conda-forge/conda-smithy-feedstock#394

@beckermr

Copy link
Copy Markdown
Member

Oh wow this is weird:

conda-package-handling 2.5.0 pypi_0 pypi

@beckermr

Copy link
Copy Markdown
Member

Finally found it.

@h-vetinari, when you uploaded the source for smithy 2026.6.14 as a tar.gz to the release, the files had the wrong permissions:

base) beckermr@finnegan ~ % cd Downloads/conda_smithy-2026.6.14                   
(base) beckermr@finnegan conda_smithy-2026.6.14 % ls -lah conda_smithy/feedstock_content/*
-rwxrwxrwx@ 1 beckermr  staff   4.1K Jun 13 20:07 conda_smithy/feedstock_content/build-locally.py
-rwxrwxrwx@ 1 beckermr  staff   1.5K Jun 13 20:07 conda_smithy/feedstock_content/LICENSE.txt
-rwxrwxrwx@ 1 beckermr  staff   219B Jun 13 20:07 conda_smithy/feedstock_content/README

IDK how that happened on your end, but that is the cause of the permissions issues. Smithy itself appears to work fine. Any ideas on how that happened on your end?

In any case, I am going to mark version 2026.6.14 as broken and try to upload a new version myself later today.

This PR is not needed, as it is not fixing the actual bug.

@h-vetinari

h-vetinari commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

So this PR is indeed papering over a more fundamental problem in the tooling somewhere

That's a question of point-of-view... I consider it as insulating us from the shenanigans that might be happening elsewhere, and putting the executable bits under our full control.

Given all the bits and pieces involved where this could go wrong, this seems like the much simpler approach to me.

@h-vetinari

h-vetinari commented Jun 15, 2026

Copy link
Copy Markdown
Member Author

IDK how that happened on your end, but that is the cause of the permissions issues. Smithy itself appears to work fine. Any ideas on how that happened on your end?

Running rever in WSL, apparently 😭 (though this did work fine in the past)

It highlights the main issue to me though - even if someone manages to change the permissions in smithy's contents (by accident or maliciously), we should copy defensively without the exec bits. It shuts off a whole class of attacks by design.

@beckermr

Copy link
Copy Markdown
Member

Honestly dude, I'm done. I spent a ton of time debugging a CI issue that you incorrectly diagnosed, and yet you are still arguing with me that this PR should be merged. This pr tries to a fix a bug that doesn't exist. I literally added the test to another pr and it passed.

If you want to debug something, go fix whatever it is in your machine that caused the bad release.

From my perspective, it is nearly impossible to reason with you on a host of items. If you feel so strongly, then you can take ownership of it all, bot included.

@h-vetinari

Copy link
Copy Markdown
Member Author

Let's take this discussion somewhere where we can unjinx this. I wrote you on Zulip.

@h-vetinari

Copy link
Copy Markdown
Member Author

you are still arguing with me that this PR should be merged.

To illustrate what I meant with the comment you're apparently reacting to, I've reverted out the one thing you consider pointless/wrong (which we can disagree on, fine); the rest is still a clear improvement though - not sure why it's surprising that I'm arguing we should keep this?

Beyond the cold facts, apologies for exasperating you, and thanks for the time you invested. I also spent a lot of time on this yesterday; obviously I didn't choose for the release workflow to break in creative new ways. Let's address the rest of your message in a private discussion.

@beckermr

Copy link
Copy Markdown
Member

No thank you. I appreciate the revert, but some things just can't be undone that easily.

@h-vetinari

Copy link
Copy Markdown
Member Author

No thank you. I appreciate the revert, but some things just can't be undone that easily.

What in the world are you talking about? I'd be willing to hash this out, but I don't even know what "things that can't be undone that easily" I've apparently committed in your eyes - unwittingly, because I have no reason or interest to offend you. That's why communication would be important; to my mind, we've worked well together for years - so it's hard for me to fathom why we couldn't solve some perturbations in our interaction, or indeed give some benefit of the doubt to the other.

@beckermr

Copy link
Copy Markdown
Member

I should clarify that my comment was about my decision regarding my own boundaries, not about anything very specific to this PR.

The reality for me is that I cannot constantly feel like I am going uphill when working on PRs, debugging, etc., with you. It's been a battle at times for me to keep my cool for a long time, and now it's spilling over. That's on me for sure, but the fact remains that it's just far too difficult for us to find agreement on basic facts (e.g., "there isn't a bug since the test that is supposed to fail if the bug is there is passing" or "one piece of code cannot possibly be responsible for correctly parsing arbitrary output from another"). It's causing us to spend effort on tiny details instead of the big picture (e.g., "smithy needs a new release process" or "we can't roll updates to codes that have no API promises into our infrastructure on-the-fly without testing"). I don't want to engage in that anymore because it is frustrating, time-consuming, and not rewarding. I do respect your time and effort, but whatever is happening here isn't working.

@h-vetinari

Copy link
Copy Markdown
Member Author

The reality for me is that I cannot constantly feel like I am going uphill when working on PRs, debugging, etc., with you

This is the first time I'm hearing of this from you. I'm sorry about that, and I'm sure it's something that can be fixed, if we make an effort to talk about it.

"there isn't a bug since the test that is supposed to fail if the bug is there is passing" or "one piece of code cannot possibly be responsible for correctly parsing arbitrary output from another"

Both of these are not things I said; though I see how you might have inferred them, it's hard to take responsibility for things I didn't say. I fully agree on the overall direction (fixing things, keeping things running smoothly, updating/improving the release process, etc.), so... I really don't see why any disagreement couldn't be resolved, and easily at that.

In this case my impulse would be to completely rule out a class of errors, rather than chase whatever piece of the rube goldberg machine may be responsible. That's not out of laziness to fix things, but because I consider it the more stable & controlled long-term solution. That doesn't mean though that it's the only way to fix it; you didn't like my solution, and once you found the culprit, I was like "ok fine, but let's keep the other improvements at least".

At no point was this about anything personal, and honestly, I take offence at "impossible to reason with". If you (or anyone else for that matter) makes an argument, I'm willing to take it on its merits, which may certainly trump my arguments. If you feel differently, please show me an example where that wasn't the case.

@beckermr

Copy link
Copy Markdown
Member

My apologies @h-vetinari for my comments on this thread. I unloaded a fair bit of pent up frustration over our interactions that should have been expressed in a more collegial tone. The frustration I have is real to me and is that I do want to feel that things which I think matter are taken seriously by you. I don't always feel that way.

@h-vetinari

Copy link
Copy Markdown
Member Author

Thank you, apology accepted.

The frustration I have is real to me and is that I do want to feel that things which I think matter are taken seriously by you. I don't always feel that way.

I'm sorry to hear that. On my end, you did not voice this to me, much less give me an opportunity to respond or change something. I'm hard-pressed to remember anything infrastructure-related I didn't take seriously1, so this sounds like a complete misunderstanding to me? I do feel quite strongly that after many years of productive/pleasant collaboration, I would have deserved a more charitable interpretation of my actions/statements, and a calm opportunity (through whatever channel) to address what you perceived I wasn't taking seriously. This reminds me of a dictum that might be useful here, if only for finding a path back towards levity: when you ASSUME, you make an ASS out of U and ME. 😉

Footnotes

  1. in fact, I mostly remember comments at various times - not from you - that I take things too seriously, i.e. make a fuss about something other people consider non-critical

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants