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
4 changes: 4 additions & 0 deletions docs/changelog/1140.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Pass ``--dependency-constraints-txt`` files through to the installer unmodified instead of re-parsing them into a
deduplicated set of lines, fixing a case where a hashed requirement (e.g. from ``pip-compile --generate-hashes``) could
have its ``--hash`` continuation line separated from its requirement line and silently dropped, depending on the
interpreter's hash seed - by :user:`manfred-kaiser`
9 changes: 8 additions & 1 deletion src/build/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,14 @@ def _bootstrap_build_env(
install = env.install
if dependency_constraints_txt:
with open(dependency_constraints_txt, encoding='utf-8') as dependency_constraints_file:
install = partial(install, constraints=set(map(str.strip, dependency_constraints_file)))
constraints_text = dependency_constraints_file.read()
Comment thread
gaborbernat marked this conversation as resolved.
if constraints_text.strip():
# Passed through as a single element instead of re-parsed into lines, so a requirement can never
# be split from its `--hash` continuation lines (as produced by `pip-compile --generate-hashes`)
# and silently lose its hash check. env.py's installer backends reconstruct the original file via
# `'\n'.join(constraints)` (see `_PipInstaller`/`_UvInstaller.install_dependencies`), a no-op here
# since there is only one element.
install = partial(install, constraints=(constraints_text,))
Comment thread
gaborbernat marked this conversation as resolved.

# first install the build dependencies
install(builder.build_system_requires, _fresh=True)
Expand Down
39 changes: 38 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,44 @@ def test_build_package_with_constraints(
with pytest.raises(build.BuildBackendException, match=re.escape("Backend 'flit_core.buildapi' is not available.")):
build.__main__.build_package(package_test_flit, tmp_path, ['wheel'], dependency_constraints_txt=constraints_txt_path)

install.assert_any_call({'flit_core >=2,<4'}, constraints={'flit-core==12.34', 'foo==wot'}, _fresh=True)
install.assert_any_call({'flit_core >=2,<4'}, constraints=('flit-core==12.34\nfoo==wot\n',), _fresh=True)


@pytest.mark.isolated
def test_build_package_with_constraints_passes_file_through_unmodified(
mocker: pytest_mock.MockerFixture, tmp_path: pathlib.Path, package_test_flit: str
) -> None:
# As produced by e.g. `pip-compile --generate-hashes`: a requirement and its --hash options wrapped onto
# continuation lines. Regression test for the requirement/hash pair being split apart when re-parsed into
# individual lines - the file content must reach the installer byte-for-byte instead.
install = mocker.patch('build.env.DefaultIsolatedEnv.install')

constraints_text = (
'flit-core==12.34 \\\n --hash=sha256:aaaa \\\n --hash=sha256:bbbb\n # via test\nfoo==wot \\\n'
' --hash=sha256:cccc\n'
)
constraints_txt_path = tmp_path.joinpath('constraints.txt')
constraints_txt_path.write_text(constraints_text, encoding='utf-8')

with pytest.raises(build.BuildBackendException, match=re.escape("Backend 'flit_core.buildapi' is not available.")):
build.__main__.build_package(package_test_flit, tmp_path, ['wheel'], dependency_constraints_txt=constraints_txt_path)

install.assert_any_call({'flit_core >=2,<4'}, constraints=(constraints_text,), _fresh=True)


@pytest.mark.isolated
def test_build_package_with_empty_constraints_txt(
mocker: pytest_mock.MockerFixture, tmp_path: pathlib.Path, package_test_flit: str
) -> None:
install = mocker.patch('build.env.DefaultIsolatedEnv.install')

constraints_txt_path = tmp_path.joinpath('constraints.txt')
constraints_txt_path.write_text(' \n\n', encoding='utf-8')

with pytest.raises(build.BuildBackendException, match=re.escape("Backend 'flit_core.buildapi' is not available.")):
build.__main__.build_package(package_test_flit, tmp_path, ['wheel'], dependency_constraints_txt=constraints_txt_path)

install.assert_any_call({'flit_core >=2,<4'}, _fresh=True)


@pytest.mark.pypy3323bug
Expand Down