blob: 41ffe88a64bab2a27b51929a6eeeea72488b0a18 [file] [log] [blame]
# Copyright 2021 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.
"""Python types for Assembly Input Bundles.
Assembly Input Bundles are a set of artifacts that need to be delivered to out-
of-tree (OOT) assembly as a unit, but whose contents should be opaque to the
delivery system itself.
"""
import json
import os
from typing import Dict, List, Set, TextIO, Union
from depfile.depfile import FilePath
from .image_assembly_config import ImageAssemblyConfig
from .common import FileEntry
from .utils import set_named_member_if_present
__all__ = ["AssemblyInputBundle", "ConfigDataEntries"]
PackageName = str
ConfigDataEntries = Dict[PackageName, Set[FileEntry]]
class AssemblyInputBundle(ImageAssemblyConfig):
"""AssemblyInputBundle wraps a set of artifacts together for use by out-of-tree assembly, both
the manifest of the artifacts, and the artifacts themselves.
The archived artifacts are placed into a nominal layout that is for readability, but the
JSON manifest itself is the arbiter of what artifacts are in what categories:
file layout::
./
assembly_config.json
package_manifests/
base/
<package name>
cache/
system/
blobs/
<merkle>
bootfs/
path/to/file/in/bootfs
config_data/
<package name>/
<file name>
kernel/
kernel.zbi
assembly_config.json schema::
{
"base": [ "package1", "package2" ],
"cache": [ "package3", ... ],
"system": [ "packageN", ... ],
"bootfs_files": [
{ "destination": "path/in/bootfs", source: "path/in/layout" },
...
],
"config_data": {
"package1": [
{ "destination": "path/in/data/dir", "source": "path/in/layout" },
{ "destination": "path/in/data/dir", "source": "path/in/layout" },
...
],
...
},
"kernel": {
"path": "kernel/kernel.zbi",
"args": [ "arg1", "arg2", ... ],
"clock_backstop": "01234"
}
"boot_args": [ "arg1", "arg2", ... ],
}
All items are optional. Files for `config_data` should be in the config_data section,
not in a package called `config_data`.
The AssemblyInputBundle is an extension of the ImageAssemblyConfig class, adding new categories
that it supports which aren't in the ImageAssemblyConfig.
"""
def __init__(self) -> None:
super().__init__()
self.config_data: ConfigDataEntries = {}
self.blobs: Union[None, Set[FilePath]] = None
@classmethod
def from_dict(cls, dict: Dict) -> 'AssemblyInputBundle':
result = super().from_dict(dict)
set_named_member_if_present(
result,
"bootfs_files",
dict,
transform=lambda items: set(
[FileEntry.from_dict(item) for item in items]))
if "config_data" in dict:
for (package, entries) in dict["config_data"].items():
result.config_data[package] = set(
[FileEntry.from_dict(entry) for entry in entries])
return result
def to_dict(self) -> Dict:
"""Dump the object out as a dict."""
result = super().to_dict()
config_data = {}
for (package, entries) in sorted(self.config_data.items()):
config_data[package] = [
entry.to_dict() for entry in sorted(entries)
]
if config_data:
result['config_data'] = config_data
return result
def write_to(self, file) -> None:
"""Write to a file (JSON format)"""
json.dump(self.to_dict(), file, indent=2)
def __repr__(self) -> str:
"""Serialize to a JSON string"""
return json.dumps(self.to_dict(), indent=2)
def intersection(
self, other: 'AssemblyInputBundle') -> 'AssemblyInputBundle':
"""Return the intersection of the two 'ImageAssemblyConfiguration's
"""
result = super().intersection(other)
config_data: ConfigDataEntries = {}
for package in self.config_data.keys():
if package in other.config_data:
entries = self.config_data[package]
other_entries = other.config_data[package]
entries = entries.intersection(other_entries)
config_data[package] = entries
if config_data:
result.config_data = config_data
return result
def difference(self, other: 'AssemblyInputBundle') -> 'AssemblyInputBundle':
"""Return the difference of the two 'ImageAssemblyConfiguration's
"""
result = super().difference(other)
for (package, entries) in self.config_data.items():
if package not in other.config_data:
result.config_data[package] = entries
else:
entries = entries.difference(other.config_data[package])
if len(entries) > 0:
result.config_data[package] = entries
return result
def all_file_paths(self) -> List[FilePath]:
"""Return a list of all files that are referenced by this AssemblyInputBundle.
"""
file_paths = []
file_paths.extend(self.base)
file_paths.extend(self.cache)
file_paths.extend(self.system)
file_paths.extend([entry.source for entry in self.bootfs_files])
if self.kernel.path is not None:
file_paths.append(self.kernel.path)
for entries in self.config_data.values():
file_paths.extend([entry.source for entry in entries])
if self.blobs is not None:
file_paths.extend(self.blobs)
return file_paths
def write_fini_manifest(
self,
file: TextIO,
base_dir: FilePath = None,
rebase: FilePath = None) -> None:
"""Write a fini-style manifest of all files in the AssemblyInputBundle
to the given |file|.
fini manifests are in the format::
destination/path=path/to/source/file
As all file paths in the AssemblyInputBundle are relative to the root of
the bundle, `destination/path` is the existing path. However, the path
to the source file cannot be known (absolutely) without additional
information.
Arguments:
- file -- The |TextIO| file to write to.
- base_dir -- The folder to assume that file paths are relative from.
- rebase -- The folder to make the source paths relative to, if `base_dir` is also provided.
By default this is the cwd.
If `base_dir` is given, it's used to construct the path to the source files, if not, the cwd
is assumed to be the path the files are from.
If `rebase` is also given, the path to the source files are then made relative to it.
"""
file_paths = self.all_file_paths()
if base_dir is not None:
file_path_entries = [
FileEntry(
os.path.relpath(os.path.join(base_dir, path), rebase), path)
for path in file_paths
]
file_path_entries += [
FileEntry(
os.path.join(base_dir, "assembly_config.json"),
"assembly_config.json")
]
else:
file_path_entries = [FileEntry(path, path) for path in file_paths]
FileEntry.write_fini_manifest(file_path_entries, file)