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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added support for `PIP_BUILD_CONSTRAINT` environment variable for pip 26.2+ compatibility.
The build system now prioritizes `PIP_BUILD_CONSTRAINT` over `PIP_CONSTRAINT` when determining
build-time constraints, while maintaining backward compatibility.

## [0.31.2] - 2026/01/26

### Added
Expand Down
10 changes: 7 additions & 3 deletions pyodide_build/build_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,17 +328,21 @@ def local_versions() -> dict[str, str]:


def _create_constraints_file() -> str:
# PIP_BUILD_CONSTRAINT takes precedence; fall back to PIP_CONSTRAINT for backward compatibility
try:
constraints = get_build_flag("PIP_CONSTRAINT")
constraints = get_build_flag("PIP_BUILD_CONSTRAINT")
except ValueError:
return ""
try:
constraints = get_build_flag("PIP_CONSTRAINT")
except ValueError:
return ""

if not constraints:
return ""

if len(constraints.split(maxsplit=1)) > 1:
raise ValueError(
"PIP_CONSTRAINT contains spaces so pip will misinterpret it. Make sure the path to pyodide has no spaces.\n"
"PIP_BUILD_CONSTRAINT/PIP_CONSTRAINT contains spaces so pip will misinterpret it. Make sure the path to pyodide has no spaces.\n"
"See https://github.com/pypa/pip/issues/13283"
)

Expand Down
3 changes: 3 additions & 0 deletions pyodide_build/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ def _parse_makefile_envs(
"pyodide_package_index": "PYODIDE_PACKAGE_INDEX",
"platform_triplet": "PLATFORM_TRIPLET",
"pip_constraint": "PIP_CONSTRAINT",
"pip_build_constraint": "PIP_BUILD_CONSTRAINT",
"pymajor": "PYMAJOR",
"pymicro": "PYMICRO",
"pyminor": "PYMINOR",
Expand Down Expand Up @@ -320,6 +321,8 @@ def _parse_makefile_envs(
"pyodide_interpreter": "$(PYODIDE_ROOT)/dist/python",
"pyodide_package_index": "$(PYODIDE_ROOT)/package_index",
"dist_dir": "$(PYODIDE_ROOT)/dist",
# Pip constraints - defaults to PIP_CONSTRAINT if not set
"pip_build_constraint": "$(PIP_CONSTRAINT)",
}


Expand Down
4 changes: 3 additions & 1 deletion pyodide_build/recipe/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,9 @@ def _compile(
)
build_env = runner.env

build_env["PIP_CONSTRAINT"] = str(self._create_constraints_file())
constraints_file = str(self._create_constraints_file())
build_env["PIP_CONSTRAINT"] = constraints_file
build_env["PIP_BUILD_CONSTRAINT"] = constraints_file

wheel_path = pypabuild.build(
self.src_extract_dir, self.src_dist_dir, build_env, config_settings
Expand Down
62 changes: 62 additions & 0 deletions pyodide_build/tests/test_build_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,65 @@ def test_wheel_paths(dummy_xbuildenv):
f"{current_version}-none-any",
]
)


def test_create_constraints_file_with_pip_build_constraint(
tmp_path, dummy_xbuildenv, monkeypatch, reset_env_vars, reset_cache
):
"""Test that _create_constraints_file prioritizes PIP_BUILD_CONSTRAINT."""
# Set up a test constraints file
constraints_file = tmp_path / "build_constraints.txt"
constraints_file.write_text("numpy<2.0\n")

# Set PIP_BUILD_CONSTRAINT via environment - this will override the computed default
monkeypatch.setenv("PIP_BUILD_CONSTRAINT", str(constraints_file))

# Clear caches to pick up the new environment variable
build_env.get_build_environment_vars.cache_clear()
build_env.get_pyodide_root.cache_clear()

result = build_env._create_constraints_file()
# Should use PIP_BUILD_CONSTRAINT when explicitly set
assert result == str(constraints_file)


def test_create_constraints_file_uses_default_from_xbuildenv(
dummy_xbuildenv, reset_env_vars, reset_cache
):
"""Test that _create_constraints_file uses values from xbuildenv when no override is provided."""
# Don't set any environment overrides - use what comes from xbuildenv
# The xbuildenv has PIP_CONSTRAINT set, and pip_build_constraint defaults to it

# Clear caches
build_env.get_build_environment_vars.cache_clear()
build_env.get_pyodide_root.cache_clear()

result = build_env._create_constraints_file()
# Should get the default constraint file path from xbuildenv
assert "constraints.txt" in result
assert result # Should not be empty


def test_create_constraints_file_pip_build_constraint_takes_precedence(
tmp_path, dummy_xbuildenv, monkeypatch, reset_env_vars, reset_cache
):
"""Test that PIP_BUILD_CONSTRAINT takes precedence over PIP_CONSTRAINT."""
# Set up two different constraints files
build_constraints_file = tmp_path / "build_constraints.txt"
build_constraints_file.write_text("numpy<2.0\n")

regular_constraints_file = tmp_path / "constraints.txt"
regular_constraints_file.write_text("scipy<2.0\n")

# Set both environment variables
monkeypatch.setenv("PIP_BUILD_CONSTRAINT", str(build_constraints_file))
monkeypatch.setenv("PIP_CONSTRAINT", str(regular_constraints_file))

# Clear caches to pick up the new environment variables
build_env.get_build_environment_vars.cache_clear()
build_env.get_pyodide_root.cache_clear()

# PIP_BUILD_CONSTRAINT should take precedence
result = build_env._create_constraints_file()
assert result == str(build_constraints_file)
assert result != str(regular_constraints_file)