Skip to content

feat: keep target_platform concrete for noarch packages and add build.subdir - #2663

Open
baszalmstra wants to merge 2 commits into
prefix-dev:mainfrom
baszalmstra:decouple-target-platform-noarch
Open

feat: keep target_platform concrete for noarch packages and add build.subdir#2663
baszalmstra wants to merge 2 commits into
prefix-dev:mainfrom
baszalmstra:decouple-target-platform-noarch

Conversation

@baszalmstra

Copy link
Copy Markdown
Contributor

For noarch outputs, target_platform used to be replaced with noarch everywhere: in the variant, the jinja context, and the target_platform environment variable in the build script. This does not match conda-build, where target_platform keeps the requested platform and only the subdir of the resulting package becomes noarch (see the Discord discussion with @isuruf).

With this PR:

build:
  noarch: generic
  script:
    - echo $target_platform  # linux-64 (was: noarch)
    - echo $SUBDIR           # noarch (unchanged)

The package is still stamped as noarch and placed in the noarch folder.

Build strings do not change. The hash input still replaces target_platform with noarch for noarch packages (now inside HashInput::from_variant), so hashes remain platform independent and byte-identical to what rattler-build produces today. test_build_string_py_noarch and test_noarch_variants assert the exact same build strings as before, and a new test asserts that noarch hashes are identical across target platforms.

Implementation

  • BuildConfiguration::target_platform is now always a concrete platform. The subdir of a package is derived instead: Output::subdir() returns build.subdir if set, noarch for noarch packages, and the target platform otherwise.
  • Everything that used target_platform == NoArch as an "is this noarch?" check (relinking, linking checks, the Windows symlink check, run-export selection, --noarch-build-platform, native test skipping) now uses Output::subdir(), so behavior there is unchanged.
  • index.json subdir/arch/platform, the output folder, prefix placeholder detection, file mapping, and package content tests also go through Output::subdir() and behave as before.

Visible behavior changes for noarch builds besides target_platform itself:

  • CONDA_BUILD_CROSS_COMPILATION is now 0 when host and build platform match. It was always 1 for noarch because noarch != build_platform.
  • The platform default environment variables (e.g. MACOSX_DEPLOYMENT_TARGET) are now set in the build script because os_vars receives a concrete platform. This is what made the compiler activation workaround in tzdata-feedstock necessary before.
  • ARCH is unchanged (it was already derived from the host platform).

build.subdir

Conda-build has an output-level target key that only controls the subdir the package is stamped with. This PR ports it as build.subdir (renamed per @isuruf's suggestion):

build:
  subdir: linux-aarch64

It only affects index.json and the output folder. It does not change target_platform or dependency resolution, and it cannot be combined with noarch. The key supports jinja templates and inherits from the top-level build section in multi-output recipes, like noarch.

Open questions

  • SUBDIR in the build script still refers to the package subdir (noarch), while conda-build populates it from host_subdir (a concrete platform). Should we follow conda-build here as well?
  • Test scripts still see target_platform as the package subdir, because at test time the value is derived from the package (index.json / hash_input.json) rather than from the build.

Compatibility with existing conda-forge recipes

Assessment of the impact on conda-forge recipes

Summary

Changing rattler-build so that target_platform remains the requested platform for noarch outputs appears to have a small compatibility risk for conda-forge recipes.

The search did not find a current recipe.yaml that clearly and intentionally relies on target_platform=noarch. The strongest ecosystem evidence points in the opposite direction: tzdata-feedstock explicitly works around rattler-build setting target_platform to noarch.

One recipe, m2w64-sysroot-feedstock, has a build script whose behavior can change depending on the value. It should be included in regression testing, although its normal conda-forge noarch build is unlikely to change.

Behavior in conda-build

Given a noarch recipe built with target_platform=win-64, conda-build keeps the requested target platform visible to the build script:

package:
  name: foo
  version: "1.0.0"

build:
  number: 0
  noarch: generic
  script:
    - echo %target_platform%

Running:

conda-build recipe --variants "{target_platform: win-64}"

prints:

win-64

The resulting package is nevertheless placed in the noarch subdirectory.

Conda-build therefore distinguishes between:

  • the requested target or host platform, exposed as target_platform;
  • whether the package is noarch, stored as recipe metadata;
  • the final package subdirectory.

SUBDIR is populated from config.host_subdir, not from the final package subdirectory (source). When package metadata is created, conda-build explicitly sets the package subdirectory to noarch when metadata.noarch is set (source).

Conda-build also supports an output-level target key:

outputs:
  - name: foo
    target: noarch

This is assigned to output_metadata.config.target_subdir (source) and controls the final package subdirectory (source). It remains separate from target_platform.

Conda-forge search

The search focused on current recipe.yaml files because those are the recipes using the v1 recipe format and rattler-build. It covered:

  • literal comparisons between target_platform and noarch;
  • ${{ target_platform }} interpolation in noarch outputs;
  • build_platform != target_platform selectors in noarch outputs;
  • inline build scripts referencing target_platform;
  • external shell and batch scripts referencing the runtime target_platform environment variable;
  • mixed multi-output recipes where only some outputs are noarch.

No current recipe.yaml was found containing a literal comparison such as:

target_platform == "noarch"
target_platform != "noarch"

GitHub code search indexes default branches and can lag behind recent changes, so this is not a guarantee that no such recipe exists. It does, however, cover the most direct ways a recipe could intentionally depend on the behavior.

Confirmed workaround: tzdata-feedstock

tzdata-feedstock explicitly overrides target_platform:

build:
  number: 1
  noarch: generic
  script:
    file: build.sh
    # Workaround as rattler-build sets target_platform to noarch, such that the compiler-activation script would not be run
    # https://github.com/conda-forge/tzdata-feedstock/pull/34#issuecomment-3593550598
    env: { target_platform: "${{ host_platform }}" }

This is not a dependency on the current behavior. It documents a concrete problem caused by it: compiler activation does not run when target_platform=noarch.

Keeping target_platform concrete would make this workaround unnecessary and align rattler-build with conda-build.

Potentially behavior-sensitive recipe: m2w64-sysroot-feedstock

m2w64-sysroot-feedstock defines a noarch output using copy.sh:

- package:
    name: m2w64-sysroot_win-64
  build:
    noarch: generic
    merge_build_and_host_envs: false
    script: copy.sh

The corresponding copy.sh contains:

if [[ "$target_platform" != "win-64" ]]; then
  mkdir -p ${PREFIX}/${HOST}/sysroot
  ln -sf ${PREFIX}/${HOST}/sysroot/usr ${PREFIX}/${HOST}/sysroot/ucrt64
fi

With the current rattler-build behavior, the condition is true because target_platform=noarch. With a concrete target_platform, it would be false when the requested platform is win-64.

This deserves regression coverage. However, the noarch package is normally produced in a Linux conda-forge job. In that job the concrete target is linux-64, so the condition remains true and the resulting artifact should be unchanged.

References that do not rely on target_platform=noarch

Several noarch outputs use expressions such as:

- if: build_platform != target_platform
  then:
    - python
    - cross-python_${{ target_platform }}

Examples include:

These are not evidence of reliance on target_platform=noarch. Recipe expressions are evaluated using the requested platform before rattler-build assigns the effective noarch output platform. If these expressions saw noarch, some would attempt to depend on a package such as cross-python_noarch, which is clearly not their intent.

Other recipes explicitly require selectors to see the requested platform:

These recipes support preserving the requested target platform rather than replacing it with noarch.

Risk assessment

The direct recipe compatibility risk appears low:

  • no recipe was found that intentionally checks for target_platform=noarch;
  • one recipe explicitly works around the current behavior;
  • one external script is potentially behavior-sensitive but should be unchanged in its normal noarch build job;
  • many noarch recipes already expect recipe expressions to see the requested platform.

The less visible risk is indirect behavior in activation scripts supplied by build dependencies. Those scripts can inspect target_platform without the recipe mentioning it. The tzdata workaround demonstrates that this already causes incompatibilities when the value is noarch.

The intended invariant:

target_platform describes the platform for which the build was requested. Whether the resulting package is noarch is separate output metadata and must not replace the requested target platform.

….subdir

For noarch outputs target_platform was replaced with noarch in the
variant, the jinja context, and the build script environment. Keep it
concrete (matching conda-build) and derive the package subdir separately
via Output::subdir(). The hash input still uses noarch for noarch
packages, so build strings are unchanged.

Also port conda-build's output-level 'target' key as 'build.subdir' to
override the subdir a package is stamped with.
Comment thread crates/rattler_build_core/src/packaging/file_mapper.rs Outdated
Comment thread crates/rattler_build_core/src/package_test/content_test.rs Outdated
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.

1 participant