Repository navigation
Expand file tree
/
Copy pathbuild_backend.py
More file actions
140 lines (120 loc) · 6.07 KB
/
Copy pathbuild_backend.py
File metadata and controls
140 lines (120 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# SPDX-FileCopyrightText: 2026 Vane contributors
# SPDX-License-Identifier: Apache-2.0
"""PEP 517 backend that adds DuckDB's generated identities to source archives."""
from __future__ import annotations
import gzip
import io
import os
import struct
import tarfile
import tempfile
from pathlib import Path, PurePosixPath
from scikit_build_core import build as _backend
from scripts.resolve_duckdb_fork_version import source_revision, validate_revision
from scripts.sync_duckdb_source_id import REPOSITORY_ROOT, source_tree_id, validate_source_id
SOURCE_ID_PATH = "DUCKDB_SOURCE_ID"
SOURCE_ID_FILE = REPOSITORY_ROOT / SOURCE_ID_PATH
FORK_REVISION_PATH = "DUCKDB_FORK_REVISION"
FORK_REVISION_FILE = REPOSITORY_ROOT / FORK_REVISION_PATH
build_editable = _backend.build_editable
build_wheel = _backend.build_wheel
get_requires_for_build_editable = _backend.get_requires_for_build_editable
get_requires_for_build_sdist = _backend.get_requires_for_build_sdist
get_requires_for_build_wheel = _backend.get_requires_for_build_wheel
prepare_metadata_for_build_editable = _backend.prepare_metadata_for_build_editable
prepare_metadata_for_build_wheel = _backend.prepare_metadata_for_build_wheel
def _source_id_for_sdist() -> str:
if (REPOSITORY_ROOT / ".git").exists():
return source_tree_id()
if SOURCE_ID_FILE.is_file():
return validate_source_id(SOURCE_ID_FILE.read_text(encoding="ascii").strip())
return source_tree_id()
def _fork_revision_for_sdist() -> str:
if (REPOSITORY_ROOT / ".git").exists():
return source_revision()
if FORK_REVISION_FILE.is_file():
return validate_revision(FORK_REVISION_FILE.read_text(encoding="ascii").strip())
return source_revision()
def _gzip_timestamp(path: Path) -> int:
with path.open("rb") as archive_file:
header = archive_file.read(10)
if len(header) != 10 or header[:2] != b"\x1f\x8b":
raise RuntimeError(f"build backend produced an invalid gzip archive: {path}")
return struct.unpack("<I", header[4:8])[0]
def _inject_duckdb_manifests(archive_path: Path, source_id: str, fork_revision: str) -> None:
"""Atomically add or replace DuckDB build manifests in a generated sdist."""
expected_files = {
SOURCE_ID_PATH: (validate_source_id(source_id) + "\n").encode("ascii"),
FORK_REVISION_PATH: (validate_revision(fork_revision) + "\n").encode("ascii"),
}
timestamp = _gzip_timestamp(archive_path)
with tarfile.open(archive_path, mode="r:gz") as source_archive:
members = source_archive.getmembers()
roots = {PurePosixPath(member.name).parts[0] for member in members if PurePosixPath(member.name).parts}
if len(roots) != 1:
raise RuntimeError(f"sdist must contain exactly one root directory, found {sorted(roots)}")
root = roots.pop()
targets = {str(PurePosixPath(root) / path): contents for path, contents in expected_files.items()}
existing_files_match = True
for target_name, expected in targets.items():
existing = [member for member in members if member.name == target_name]
if len(existing) != 1 or not existing[0].isfile():
existing_files_match = False
break
source_file = source_archive.extractfile(existing[0])
if source_file is None or source_file.read() != expected:
existing_files_match = False
break
if existing_files_match:
return
descriptor, temporary_name = tempfile.mkstemp(prefix=f".{archive_path.name}.", dir=archive_path.parent)
os.close(descriptor)
temporary_path = Path(temporary_name)
try:
entries = [(member.name, member, None) for member in members if member.name not in targets]
for target_name, expected in targets.items():
target = tarfile.TarInfo(target_name)
target.size = len(expected)
target.mode = 0o644
target.mtime = timestamp
target.uid = 0
target.gid = 0
target.uname = ""
target.gname = ""
entries.append((target_name, target, expected))
with temporary_path.open("wb") as raw_archive:
gzip_name = archive_path.name.removesuffix(".gz")
with gzip.GzipFile(
filename=gzip_name,
mode="wb",
compresslevel=9,
fileobj=raw_archive,
mtime=timestamp,
) as gzip_archive:
with tarfile.open(fileobj=gzip_archive, mode="w", format=tarfile.PAX_FORMAT) as output_archive:
for _, member, data in sorted(entries, key=lambda entry: entry[0]):
if data is not None:
output_archive.addfile(member, fileobj=io.BytesIO(data))
elif member.isfile():
source_file = source_archive.extractfile(member)
if source_file is None:
raise RuntimeError(f"unable to read {member.name!r} from {archive_path}")
output_archive.addfile(member, fileobj=source_file)
else:
output_archive.addfile(member)
# Windows cannot replace the archive while its read handle is open.
source_archive.close()
temporary_path.chmod(archive_path.stat().st_mode & 0o777)
os.replace(temporary_path, archive_path)
finally:
temporary_path.unlink(missing_ok=True)
def build_sdist(
sdist_directory: str,
config_settings: dict[str, list[str] | str] | None = None,
) -> str:
"""Build an sdist containing the exact DuckDB identities for this checkout."""
source_id = _source_id_for_sdist()
fork_revision = _fork_revision_for_sdist()
filename = _backend.build_sdist(sdist_directory, config_settings)
_inject_duckdb_manifests(Path(sdist_directory) / filename, source_id, fork_revision)
return filename