blob: ac4073ea534189200b4509b63d5844280b247c03 [file] [log] [blame]
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from serialization import serialize_fields_as
__all__ = ["PackageManifest", "PackageMetaData", "BlobEntry", "SubpackageEntry"]
from .common import FilePath
@dataclass(init=False)
@serialize_fields_as(version=str)
class PackageMetaData:
"""The metadata that describes a package.
This is the package manifest's metadata section, which includes both
intrinsic data like abi_revision, and extrinsic data like the name that it's
published under and repo that it's for publishing to, etc.
"""
name: str
version: int = 0
def __init__(self, name: str, version: Optional[int] = None) -> None:
self.name = name
if version is not None:
self.version = version
else:
self.version = 0
@dataclass
class BlobEntry:
"""A blob that's in the package.
path - The path that the blob has within the package's namespace.
merkle - The merkle of the blob
size - The (uncompressed) size of the blob, in bytes.
source_path - The path to where the blob was found when the package was
being created.
"""
path: FilePath
merkle: str
size: Optional[int] = None
source_path: Optional[FilePath] = None
def compare_with(
self, other: "BlobEntry", allow_source_path_differences=False
) -> List[str]:
"""Compare this BlobEntry with the other, reporting any differences.
If 'allow_source_path_differences' is True, then the source_paths of
blobs are not compared, just the path, size, and merkle.
"""
errors = []
if self.size != other.size:
errors.append(
f"blob has a different size ({self.size} vs {other.size}) for: {self.path}"
)
elif self.merkle != other.merkle:
errors.append(
f"blob has a different merkle ({self.merkle} vs {other.merkle}) for: {self.path}"
)
elif not allow_source_path_differences:
if self.source_path != other.source_path:
errors.append(
f"blob has a different source ({self.source_path} vs {other.source_path}) for: {self.path}"
)
return errors
@dataclass
class SubpackageEntry:
"""A subpackage dependency that is directly referenced by the package.
name - The parent-package-scoped subpackage name
merkle - The subpackage's package hash
manifest_path - The filesystem path to the subpackage PackageManifest
"""
name: str
merkle: str
manifest_path: FilePath
@dataclass
class PackageManifest:
"""The output manifest for a Fuchsia package."""
package: PackageMetaData
blobs: List[BlobEntry]
version: str = "1"
# TODO(https://fxbug.dev/42066050): Change this to `paths_relative`, because it
# applies to both blob source and subpackage manifest.
blob_sources_relative: Optional[str] = None
subpackages: List[SubpackageEntry] = field(default_factory=list)
repository: Optional[str] = None
def set_paths_relative(self, relative_to_file: bool):
self.blob_sources_relative = (
"file" if relative_to_file else "working_dir"
)
def blobs_by_path(self) -> Dict[FilePath, BlobEntry]:
return {blob.path: blob for blob in self.blobs}
def compare_with(
self, other: "PackageManifest", allow_source_path_differences=False
) -> List[str]:
"""Compare this package manifest with the other, reporting any
differences.
If 'allow_source_path_differences' is True, then the source_paths of
blobs are not compared, just the path, size, and merkle.
"""
errors = []
if self.package.name != other.package.name:
errors.append(
f"package publishing names differ: {self.package.name} vs. {other.package.name}"
)
if self.repository != other.repository:
errors.append(
f"package publishing repositories differ: {self.repository} vs. {other.repository}"
)
self_blobs_by_path = self.blobs_by_path()
other_blobs_by_path = other.blobs_by_path()
missing_paths = set(self_blobs_by_path.keys()).difference(
other_blobs_by_path.keys()
)
extra_paths = set(other_blobs_by_path.keys()).difference(
self_blobs_by_path.keys()
)
common_paths = set(self_blobs_by_path.keys()).intersection(
other_blobs_by_path.keys()
)
for missing_path in missing_paths:
errors.append(f"missing a blob for path: {missing_path}")
for extra_path in extra_paths:
errors.append(f"extra blob found at path: {extra_path}")
for common_path in sorted([str(path) for path in common_paths]):
self_blob = self_blobs_by_path[common_path]
other_blob = other_blobs_by_path[common_path]
errors.extend(
self_blob.compare_with(
other_blob, allow_source_path_differences
)
)
return errors