From 34d4d1e5bdecbb1a0e290e68005c6241ff8ae47b Mon Sep 17 00:00:00 2001 From: "Bernhard M. Wiedemann" Date: Thu, 21 Jun 2018 22:05:37 +0200 Subject: [PATCH] Sort list of RECORD entries Without sorting, the 'installed' hash had entries in random order that caused output to differ for every run. See https://reproducible-builds.org/ for why this matters. Sorting all entries to make testing easier. --- news/5525.feature | 1 + src/pip/_internal/wheel.py | 9 ++++++--- tests/functional/test_install_wheel.py | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 news/5525.feature diff --git a/news/5525.feature b/news/5525.feature new file mode 100644 index 00000000000..1af8be6f997 --- /dev/null +++ b/news/5525.feature @@ -0,0 +1 @@ +pip now ensures that the RECORD file is sorted when installing from a wheel file. diff --git a/src/pip/_internal/wheel.py b/src/pip/_internal/wheel.py index 6563ae62c7e..9a5cce33eef 100644 --- a/src/pip/_internal/wheel.py +++ b/src/pip/_internal/wheel.py @@ -500,16 +500,19 @@ def _get_script_text(entry): with open_for_csv(temp_record, 'w+') as record_out: reader = csv.reader(record_in) writer = csv.writer(record_out) + outrows = [] for row in reader: row[0] = installed.pop(row[0], row[0]) if row[0] in changed: row[1], row[2] = rehash(row[0]) - writer.writerow(row) + outrows.append(tuple(row)) for f in generated: digest, length = rehash(f) - writer.writerow((normpath(f, lib_dir), digest, length)) + outrows.append((normpath(f, lib_dir), digest, length)) for f in installed: - writer.writerow((installed[f], '', '')) + outrows.append((installed[f], '', '')) + for row in sorted(outrows): + writer.writerow(row) shutil.move(temp_record, record) diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py index ddb7744af42..58b9d2bc15e 100644 --- a/tests/functional/test_install_wheel.py +++ b/tests/functional/test_install_wheel.py @@ -217,6 +217,23 @@ def test_install_from_wheel_no_deps(script, data): assert pkg_folder not in result.files_created +def test_wheel_record_lines_in_deterministic_order(script, data): + to_install = data.packages.join("simplewheel-1.0-py2.py3-none-any.whl") + result = script.pip('install', to_install) + + dist_info_folder = script.site_packages / 'simplewheel-1.0.dist-info' + record_path = dist_info_folder / 'RECORD' + + assert dist_info_folder in result.files_created, str(result) + assert record_path in result.files_created, str(result) + + record_path = result.files_created[record_path].full + record_lines = [ + p for p in Path(record_path).read_text().split('\n') if p + ] + assert record_lines == sorted(record_lines) + + @pytest.mark.network def test_install_user_wheel(script, virtualenv, data, common_wheels): """