Skip to content

CEP XXXX: Document status quo for prefix replacement - #180

Open
chrisburr wants to merge 3 commits into
conda:mainfrom
chrisburr:prefix-replacement
Open

CEP XXXX: Document status quo for prefix replacement#180
chrisburr wants to merge 3 commits into
conda:mainfrom
chrisburr:prefix-replacement

Conversation

@chrisburr

@chrisburr chrisburr commented Jul 15, 2026

Copy link
Copy Markdown

In #179 (comment) I found myself thinking there is a need for an informative CEP to document the status quo for what prefix replacement means.

🤖 the document was written by AI based on my human researched notes 🤖

Checklist for submitter

  • I am submitting a new CEP: Put your title here.
    • I am using the CEP template by creating a copy cep-0000.md named cep-XXXX.md in the root level.
  • I am submitting modifications to CEP XX.
    • The PR title reflects the CEP I'm modifying: CEP XX: Amend XYZ.
    • I updated the "Updated" date.
    • I added the link of this PR to the Discussions row.
    • I added or extended the ## Changelog section right above the final "Copyright" section with an item that uses syntax YYYY-MM-DD: Brief explanation of changes.
  • Something else: (add your description here).

Checklist for CEP approvals

  • The vote period has ended and the vote has passed the necessary quorum and approval thresholds.
  • A new CEP number has been minted. Usually, this is ${greatest-number-in-main} + 1.
  • The cep-XXXX.md file has been renamed accordingly.
  • The # CEP XXXX - header has been edited accordingly.
  • The CEP status in the table has been changed to approved.
  • The last modification date in the table has been updated accordingly.
  • The table in the README has been updated with the new CEP entry.
  • The pre-commit checks are passing.

@chrisburr
chrisburr marked this pull request as ready for review July 15, 2026 12:04
@chrisburr chrisburr changed the title Document status quo for prefix replacement CFEP XXXX: Document status quo for prefix replacement Jul 15, 2026
chrisburr added a commit to chrisburr/ceps that referenced this pull request Jul 15, 2026
Record occurrences per encoding ([{"encoding": ..., "ranges": ...}]) so a
single package serves installers with different search semantics. Producers
must now cover all five encodings replaced by existing installers; the
previous rule omitting offsets for wide-encoding files is gone. Reference
the prefix replacement CEP (conda#180) for replacement semantics and
defer padding strategies and mixed text/binary substitution to a future
versioned paths format.
@chrisburr chrisburr changed the title CFEP XXXX: Document status quo for prefix replacement CEP XXXX: Document status quo for prefix replacement Jul 15, 2026
Comment thread cep-XXXX.md Outdated
<tr><td> Author(s) </td><td> Chris Burr &lt;christopher.burr@cern.ch&gt; </td></tr>
<tr><td> Created </td><td> Jul 15, 2026 </td></tr>
<tr><td> Updated </td><td> Jul 15, 2026 </td></tr>
<tr><td> Discussion </td><td> NA </td></tr>

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.

Suggested change
<tr><td> Discussion </td><td> NA </td></tr>
<tr><td> Discussion </td><td> https://github.com/conda/ceps/pull/180 </td></tr>

Comment thread cep-XXXX.md Outdated
<tr><td> Created </td><td> Jul 15, 2026 </td></tr>
<tr><td> Updated </td><td> Jul 15, 2026 </td></tr>
<tr><td> Discussion </td><td> NA </td></tr>
<tr><td> Implementation </td><td> NA (documents existing behavior) </td></tr>

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.

You could link here some permalinks to the relevant modules in each package manager.

@chrisburr

Copy link
Copy Markdown
Author

@jaimergp Any further thoughts here? I think this and the other CEP are done from my perspective.

@jaimergp jaimergp 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.

I have some comments about what we should define as a standard, a divergence and a bug. In practice, conda has been the leading de facto standard, so unless the behavior is clearly a bug, their design decision should be recorded as a standard (on which we can iterate in further work). Under that definition, we can standardize the UTF encoding and pyzzer handling divergences as standard. Deprecating that should be done explicitly afterwards, or non-compliant tools should catch up and fix the bug.

If further work like #179 is already in the pipeline addressing some of the limitations (e.g. the ambiguity of UTF handling), even more so.

Comment thread cep-XXXX.md
Comment on lines +62 to +64
Only files whose path entry carries a `prefix_placeholder` are processed. All
other files are linked or copied unmodified. Which files receive an entry is
decided at build time and is out of scope here.

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.

Suggested change
Only files whose path entry carries a `prefix_placeholder` are processed. All
other files are linked or copied unmodified. Which files receive an entry is
decided at build time and is out of scope here.
All files whose path entry (in `paths.json` or equivalent) carries a `prefix_placeholder` MUST be processed. All
other files MUST be linked or copied unmodified. Which files receive an entry is
decided at build time and is out of scope here.

Comment thread cep-XXXX.md
### Text files

Installers MUST replace every occurrence of the placeholder with the target
prefix. The file length may change.

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.

Suggested change
prefix. The file length may change.
prefix. The file size may change.

Talking about length is ambiguous here, I think.

Comment thread cep-XXXX.md
Comment on lines +71 to +74
When the target prefix contains backslashes (Windows), installers MUST
convert them to forward slashes before replacement. This matches the
separator convention CEP 34 requires of the placeholder itself and avoids
producing invalid escape sequences in patched source files.

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.

This is not clear to me. Will the final contents of the file use forward or back slashes? I'd venture it must use whatever was using originally, but replacement must take into account the slash normalization, correct? As in:

  1. Read file and find occurrences of the placeholder prefix with both types of slashes. Record found slash pattern.
  2. Replace placeholder with target prefix, matching the required slash pattern.

Is that what happens?

Comment thread cep-XXXX.md

### Binary files

Binary replacement MUST preserve the file length. Installers replace each

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.

Suggested change
Binary replacement MUST preserve the file length. Installers replace each
Binary replacement MUST preserve the file size. Installers MUST replace each

Comment thread cep-XXXX.md
Comment on lines +79 to +80
occurrence, shift the remainder of the surrounding NUL-terminated string
towards the start, and fill the gap before the original NUL terminator with

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.

Isn't that "shifting" implied by left-aligned replacement (the most common type)?

Comment thread cep-XXXX.md
Comment on lines +115 to +164
### Encodings

conda replaces occurrences of the placeholder encoded as UTF-8, UTF-16-LE/BE
and UTF-32-LE/BE, in both text and binary mode. This was added by
[conda/conda#9946][conda-9946] for packages that store paths as wide strings
([conda/conda#4298][conda-4298], wxWidgets). libmamba and rattler replace
UTF-8 only; [mamba-org/mamba#2215][mamba-2215] tracks this but was never
implemented. Neither conda-build nor rattler-build detects wide-encoding
occurrences at build time, so affected files only receive a path entry
through conda-build's `binary_has_prefix_files` or by also containing a UTF-8
occurrence.

The multi-encoding replacement is a sequence of byte-level search passes,
one per encoding in the order listed above, and byte-level search cannot
always tell the two endiannesses of a wide encoding apart. An ASCII
character encodes to its ASCII byte plus zero bytes, on opposite sides
depending on the endianness, so the big-endian encoding of an ASCII string
contains the little-endian byte pattern of the same string at a shift of
one byte (three bytes for UTF-32), with the final zero byte supplied by
whatever follows the last character: the high bytes of the next character
or the NUL terminator. For example, a placeholder `/pfx` stored as a
NUL-terminated UTF-16-BE string, followed by one more zero byte (padding,
or the high byte of the next big-endian character), admits two complete
readings, each leaving a single unclaimed zero byte at the opposite end:

```text
bytes: 00 2f 00 70 00 66 00 78 00 00 00
read as BE: └'/'┘ └'p'┘ └'f'┘ └'x'┘ └NUL┘
read as LE: └'/'┘ └'p'┘ └'f'┘ └'x'┘ └NUL┘
```

A big-endian wide string containing the placeholder therefore also matches
the little-endian pass, which runs first and splices the little-endian
target at the shifted position. Whether the wrong guess matters depends on
the characters involved. Below U+0100 every byte the wide encoding adds is
zero, and the zeros are interchangeable between the two readings: the
spliced bytes read back correctly at the big-endian alignment, so the
wrong guess produces exactly the bytes the right one would. At U+0100 and
above the added bytes are no longer zero, the shift pairs bytes across
character boundaries, and the string is corrupted instead of replaced (a
little-endian splice of `ā`, U+0101, reads back big-endian as the pair
U+0001, U+0170). The ambiguity is symmetric, since a little-endian string
preceded by a zero byte contains a phantom big-endian match one byte
earlier, so no pass order resolves it; it merely goes unnoticed while the
target prefix and the remainder of the affected string stay below U+0100,
which paths in practice almost always do. A target prefix containing
characters at or above U+0100, installed over a wide string of the
opposite endianness, is the combination that corrupts. Metadata that
records the encoding of each occurrence, as proposed in
[conda/ceps#179][ceps-179], is not subject to this ambiguity.

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.

Since #179 will address this unambiguously:

  • I'd trim this to the first paragraph and mention that this behavior is currently unspecified, and should be addressed by further work.
  • I'd take the other two paragraphs and use it in the Rationale or Motivation sections for 179.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The ambiguity is still there for generating the paths.json. The only thing that can truly solve it is metadata in the recipe but that's outside of the scope of #179.

Comment thread cep-XXXX.md
rattler rewrites Python interpreters to a `/bin/sh` trampoline instead of
the `#!/usr/bin/env` form.

### Format strictness

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.

I'd advocate for standardizing around the strict behavior of conda and submitting bug reports to the other projects.

Comment thread cep-XXXX.md
records the encoding of each occurrence, as proposed in
[conda/ceps#179][ceps-179], is not subject to this ambiguity.

### pyzzer launchers

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.

This also sounds like a bug in rattler

Comment thread cep-XXXX.md
Comment on lines +92 to +103
### Shebang lines

On Unix target platforms, a text file starting with `#!` gets special
treatment: the first line is rewritten as a unit rather than by plain
substitution. If the patched interpreter path fits within the length limit
and contains no spaces, it is kept. Otherwise the line is rewritten to an
`#!/usr/bin/env <program>` form, on the assumption that the environment will
be activated when the script runs. The limits in use are 127 bytes on Linux
and 512 on macOS.

The rewritten output differs between installers; see below. Only the intent
is shared: the first line must remain a working interpreter directive.

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.

This is the specification, so we need to use RFC language like MUST or MUST NOT.

Comment thread cep-XXXX.md
Comment on lines +84 to +86
When the target platform is Windows, installers MUST NOT apply C-string
replacement. The pyzzer rewrite described under divergences is the one
exception conda and libmamba make.

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.

Suggested change
When the target platform is Windows, installers MUST NOT apply C-string
replacement. The pyzzer rewrite described under divergences is the one
exception conda and libmamba make.
When the target platform is Windows, installers MUST NOT apply C-string
replacement, except for the `pyzzer`-style launchers, which feature a shebang line that MUST be handled as if it was a Unix shebang.

WDYT about standardizing this? Or is it an edge case that doesn't deserve standardization?

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.

Alternatively, we can encode this spec as a behavior to follow in this type of case: "when the file features an occurrence of the prefix placeholder and its functionality depends on its correct replacement with the target prefix, install tools SHOULD strive to apply the transformation when feasible (e.g. easy-to-define generalizations like pyzzer launchers).

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