blob: 8a5356c43cf2184811d5f341149eaf0b6b46eb8c [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2023 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.
"""Holders for license-related GN metadata dictionaries."""
import json
import dataclasses
import logging
import os
from typing import Any, Dict, List, Tuple, TypeAlias
from gn_label import GnLabel
AnyDict: TypeAlias = Dict[Any, Any]
AnyList: TypeAlias = List[Any]
OptionalPath: TypeAlias = str | None
@dataclasses.dataclass(frozen=True)
class GnLicenseMetadata:
"""Metadata produced by //build/licenses/license.gni template"""
target_label: GnLabel
public_package_name: str
license_files: Tuple[GnLabel]
@staticmethod
def is_license_metadata_dict(dict: AnyDict) -> bool:
return "license_files" in dict
@staticmethod
def from_json_dict(
dict: AnyDict, absolute_fuchsia_source_path: OptionalPath = None
) -> "GnLicenseMetadata":
target_label = GnLabel.from_str(dict["target_label"])
logging.debug("Loading GnLicenseMetadata for %s", target_label)
assert (
target_label.toolchain
), f"Label must have a toolchain part: {target_label}"
assert (
target_label.is_local_name
), f"Label must have a `:name` part: {target_label}"
public_package_name = dict["public_package_name"]
license_files = [
target_label.create_child_from_str(
_rebase_absolute_path(s, absolute_fuchsia_source_path)
)
for s in dict["license_files"]
]
return GnLicenseMetadata(
target_label=target_label,
public_package_name=public_package_name,
license_files=tuple(license_files),
)
@dataclasses.dataclass
class GnApplicableLicensesMetadata:
"""Metadata produced by the GN `applicable_licenses` template parameter"""
target_label: GnLabel
target_type: str
license_labels: Tuple[GnLabel]
third_party_resources: Tuple[GnLabel]
@staticmethod
def is_applicable_licenses_metadata_dict(dict: AnyDict) -> bool:
return "license_labels" in dict
def is_group(self) -> bool:
return self.target_type == "group"
@staticmethod
def from_json_dict(
data: AnyDict, absolute_fuchsia_source_path: OptionalPath = None
) -> "GnApplicableLicensesMetadata":
assert isinstance(data, dict)
target_label = GnLabel.from_str(data["target_label"])
logging.debug(
"Loading GnApplicableLicensesMetadata for %s", target_label
)
target_type = data["target_type"] if "target_type" in data else None
assert (
target_label.toolchain
), f"Label must have a toolchain part: {target_label}"
assert (
target_label.is_local_name
), f"Label must have a `:name` part: {target_label}"
license_labels = [GnLabel.from_str(s) for s in data["license_labels"]]
third_party_resources = [
target_label.create_child_from_str(
_rebase_absolute_path(s, absolute_fuchsia_source_path)
)
for s in data.get("third_party_resources", [])
]
return GnApplicableLicensesMetadata(
target_label=target_label,
target_type=target_type,
license_labels=tuple(license_labels),
third_party_resources=tuple(third_party_resources),
)
@dataclasses.dataclass
class GnLicenseMetadataDB:
"""An in-memory DB of licensing GN metadata"""
"""GnLicenseMetadata by the license's GN label"""
licenses_by_label: Dict[GnLabel, GnLicenseMetadata] = dataclasses.field(
default_factory=dict
)
"""GnApplicableLicensesMetadata by the target label they apply to"""
applicable_licenses_by_target: Dict[
GnLabel, GnApplicableLicensesMetadata
] = dataclasses.field(default_factory=dict)
@staticmethod
def from_file(
file_path: str, fuchsia_source_path: str
) -> "GnLicenseMetadataDB":
"""Loads from a json file generated by the build/licenses/license_collection.gni template"""
logging.debug("Loading metadata from %s", file_path)
# To resolve absolute paths, turn the source path into an absolute one
absolute_fuchsia_source_path = os.path.abspath(
os.path.normpath(fuchsia_source_path)
)
with open(file_path, "r") as f:
output = GnLicenseMetadataDB.from_json_list(
json.load(f), fuchsia_source_path=absolute_fuchsia_source_path
)
logging.debug(
f"Loaded {len(output.licenses_by_label)} licenses and {len(output.applicable_licenses_by_target)} from {file_path}"
)
return output
@staticmethod
def from_json_list(
json_list: AnyList, fuchsia_source_path: OptionalPath = None
) -> "GnLicenseMetadataDB":
"""Loads from a json list generated by the build/licenses/license_collection.gni template"""
db = GnLicenseMetadataDB()
assert type(json_list) is list
for d in json_list:
if GnLicenseMetadata.is_license_metadata_dict(d):
db.add_license_metadata(
GnLicenseMetadata.from_json_dict(d, fuchsia_source_path)
)
elif GnApplicableLicensesMetadata.is_applicable_licenses_metadata_dict(
d
):
db.add_applicable_licenses_metadata(
GnApplicableLicensesMetadata.from_json_dict(
d, fuchsia_source_path
)
)
else:
raise RuntimeError(f"Unexpected json element: {d}")
# Remove applicable_licenses for targets that are license targets.
# Those are meaningless.
for label in db.licenses_by_label.keys():
db.applicable_licenses_by_target.pop(label, None)
return db
def add_license_metadata(self, license_metadata: GnLicenseMetadata) -> None:
current = self.licenses_by_label.setdefault(
license_metadata.target_label, license_metadata
)
assert current == license_metadata
def add_applicable_licenses_metadata(
self, application: GnApplicableLicensesMetadata
) -> None:
current = self.applicable_licenses_by_target.setdefault(
application.target_label, application
)
assert (
current == application
), f"Multiple applicable_licenses metadata entries for {application.target_label} ({application.target_type}), probably due to https://fxbug.dev/42083609)."
def _rebase_absolute_path(
path_str: str, absolute_fuchsia_source_path: OptionalPath
) -> str:
if absolute_fuchsia_source_path and os.path.isabs(path_str):
rel_path = os.path.relpath(path_str, absolute_fuchsia_source_path)
if not rel_path.startswith("../"):
return "//" + rel_path
return path_str