blob: dfe855fdb79bac5fa90c0f95042d43080f3dd952 [file] [log] [blame]
# Copyright 2024 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.
import sys
import typing
import os
import json
# kinds of FIDL declaration
KINDS = [
"bits",
"const",
"enum",
"experimental_resource",
"protocol",
"service",
"struct",
"table",
"alias",
"new_type",
"union",
"overlay",
]
def location_object_hook(d):
if "location" in d:
del d["location"]
return d
class ObjectHook:
keep_location: bool
keep_documentation: bool
def __init__(self, keep_location: bool, keep_documentation: bool):
self.keep_location = keep_location
self.keep_documentation = keep_documentation
def __call__(self, d: dict) -> dict:
if not self.keep_location and "location" in d:
del d["location"]
if not self.keep_documentation and "maybe_attributes" in d:
for i in range(len(d["maybe_attributes"])):
if d["maybe_attributes"][i]["name"] == "doc":
del d["maybe_attributes"][i]
if len(d["maybe_attributes"]) == 0:
del d["maybe_attributes"]
break
return d
def find_declaration(name, declarations):
for decl in declarations:
if decl["name"] == name:
return decl
raise Exception(f"{name} not found")
def get_available_attribute(declaration):
for attr in declaration.get("maybe_attributes", []):
if attr["name"] == "available":
return attr
return None
def merge_irs(
inputs: typing.List[typing.TextIO],
output: typing.TextIO,
keep_location: bool = False,
keep_documentation: bool = False,
):
# should we keep location and documentation?
json_object_hook = ObjectHook(keep_location, keep_documentation)
if keep_location and keep_documentation:
json_object_hook = None
# parse the inputs
experiments = None
available = None
dependencies = set()
declarations = {}
for f in inputs:
ir = json.load(f, object_hook=json_object_hook)
# make sure the experiments match
if experiments != ir.get("experiments"):
if experiments is None:
experiments = ir["experiments"]
else:
raise Exception("Experiments mismatch")
# make sure available versions match
if available != ir.get("available"):
if available is None:
available = ir["available"]
else:
raise Exception("Available mismatch")
# grab @available from the library.
library_available = get_available_attribute(ir)
assert library_available is not None
# and grab the added= attribute argument
(library_added,) = [
a for a in library_available["arguments"] if a["name"] == "added"
]
# track all of the external dependencies declared in this library
for lib in ir["library_dependencies"]:
dependencies.update(n for n in lib["declarations"].keys())
# track the declarations in this library
for name, kind in ir["declarations"].items():
assert kind in KINDS
decl = find_declaration(name, ir[f"{kind}_declarations"])
# if the decl doesn't have an @available attribute, use the one from its library
avail = get_available_attribute(decl)
if avail is None:
if "maybe_attributes" not in decl:
decl["maybe_attributes"] = []
decl["maybe_attributes"].append(library_available)
else:
# if the decl's @available is missing the added argument, use the one from its library
if "added" not in [a["name"] for a in avail["arguments"]]:
avail["arguments"].append(library_added)
if name in declarations:
if declarations[name] != (kind, decl):
raise Exception(f"conflicting definitions for {name}")
else:
declarations[name] = (kind, decl)
# make sure the dependencies are satisfied
declaration_names = frozenset(declarations.keys())
missing_dependencies = dependencies - declaration_names
if missing_dependencies:
raise Exception(
"Missing declarations: " + (", ".join(sorted(missing_dependencies)))
)
# build merged IR
merged: dict[str, typing.Any] = {
f"{kind}_declarations": [] for kind in KINDS
}
merged["declarations"] = {}
for name, (kind, info) in declarations.items():
merged[f"{kind}_declarations"].append(info)
merged["declarations"][name] = kind
# sort declarations within kind arrays
for kind in KINDS:
merged[f"{kind}_declarations"].sort(key=lambda d: d["name"])
# put back available and experiments if they were seen
if available is not None:
merged["available"] = available
if experiments is not None:
merged["experiments"] = experiments
json.dump(merged, output, indent=2)