|
45 | 45 | OBJ_DIR = pathlib.Path("obj") |
46 | 46 | # Directory for indexer data. |
47 | 47 | INDEX_DIR = pathlib.Path("idx") |
| 48 | +# The index database filename. |
| 49 | +INDEX_DB = pathlib.Path("db.sqlite") |
48 | 50 | # Library directory, where shared libraries are copied - inside obj. |
49 | 51 | LIB_DIR = OBJ_DIR / "lib" |
50 | 52 | # Manifest location |
|
54 | 56 | # Min archive version we currently support. |
55 | 57 | _MIN_SUPPORTED_ARCHIVE_VERSION = 1 |
56 | 58 | # The current version of the build archive format. |
57 | | -ARCHIVE_VERSION = 4 |
| 59 | +ARCHIVE_VERSION = 5 |
58 | 60 | # OSS-Fuzz $OUT dir. |
59 | 61 | OUT = pathlib.Path(os.getenv("OUT", "/out")) |
60 | 62 | # OSS-Fuzz coverage info. |
@@ -209,6 +211,26 @@ def from_dict(cls, config_dict: Mapping[Any, Any]) -> Self: |
209 | 211 |
|
210 | 212 |
|
211 | 213 |
|
| 214 | +def _get_sqlite_db_user_version(sqlite_db_path: pathlib.Path) -> int: |
| 215 | + """Retrieves `PRAGMA user_version;` value without connecting to the database.""" |
| 216 | + with sqlite_db_path.open("rb") as stream: |
| 217 | + # https://www.sqlite.org/pragma.html#pragma_user_version - a big-endian |
| 218 | + # 32-bit number at offset 60 of the database header. |
| 219 | + too_small_error = ValueError( |
| 220 | + f"The file '{sqlite_db_path}' is too small for an SQLite database." |
| 221 | + ) |
| 222 | + try: |
| 223 | + stream.seek(60) |
| 224 | + except OSError as e: |
| 225 | + raise too_small_error from e |
| 226 | + |
| 227 | + version_bytes = stream.read(4) |
| 228 | + if len(version_bytes) < 4: |
| 229 | + raise too_small_error |
| 230 | + |
| 231 | + return int.from_bytes(version_bytes, byteorder="big") |
| 232 | + |
| 233 | + |
212 | 234 | @dataclasses.dataclass(frozen=True) |
213 | 235 | class Manifest: |
214 | 236 | """Contains general meta-information about the snapshot.""" |
@@ -245,6 +267,9 @@ class Manifest: |
245 | 267 | # Version of the manifest spec. |
246 | 268 | version: int = ARCHIVE_VERSION |
247 | 269 |
|
| 270 | + # Version of the index database schema. |
| 271 | + index_db_version: int | None = None |
| 272 | + |
248 | 273 | @classmethod |
249 | 274 | def from_dict(cls, data: dict[str, Any]) -> Self: |
250 | 275 | """Creates a Manifest object from a deserialized dict.""" |
@@ -282,6 +307,7 @@ def from_dict(cls, data: dict[str, Any]) -> Self: |
282 | 307 | ) |
283 | 308 | return Manifest( |
284 | 309 | version=version, |
| 310 | + index_db_version=data.get("index_db_version"), |
285 | 311 | name=data["name"], |
286 | 312 | uuid=data["uuid"], |
287 | 313 | lib_mount_path=lib_mount_path, |
@@ -365,7 +391,7 @@ def save_build( |
365 | 391 | archive_path: pathlib.PurePath, |
366 | 392 | out_dir: pathlib.PurePath = pathlib.Path("/out"), |
367 | 393 | overwrite: bool = True, |
368 | | - ) -> None: |
| 394 | + ) -> Self: |
369 | 395 | """Saves a build archive with this Manifest.""" |
370 | 396 | if os.path.exists(archive_path) and not overwrite: |
371 | 397 | raise FileExistsError(f"Not overwriting existing archive {archive_path}") |
@@ -416,13 +442,20 @@ def _save_dir( |
416 | 442 | arcname=prefix + str(file.relative_to(path)), |
417 | 443 | ) |
418 | 444 |
|
| 445 | + dumped_self = self |
| 446 | + if self.index_db_version is None: |
| 447 | + index_db_version = _get_sqlite_db_user_version(index_dir / INDEX_DB) |
| 448 | + dumped_self = dataclasses.replace( |
| 449 | + self, index_db_version=index_db_version |
| 450 | + ) |
| 451 | + |
419 | 452 | # Make sure the manifest is the first file in the archive to avoid |
420 | 453 | # seeking when we only need the manifest. |
421 | 454 | _add_string_to_tar( |
422 | 455 | tar, |
423 | 456 | MANIFEST_PATH.as_posix(), |
424 | 457 | json.dumps( |
425 | | - self.to_dict(), |
| 458 | + dumped_self.to_dict(), |
426 | 459 | indent=2, |
427 | 460 | ), |
428 | 461 | ) |
@@ -452,6 +485,8 @@ def _save_dir( |
452 | 485 |
|
453 | 486 | shutil.copyfile(tmp.name, archive_path) |
454 | 487 |
|
| 488 | + return dumped_self |
| 489 | + |
455 | 490 |
|
456 | 491 | def report_missing_source_files( |
457 | 492 | binary_name: str, copied_files: list[str], tar: tarfile.TarFile): |
|
0 commit comments