Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 10 additions & 21 deletions scripts/fota_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,28 +193,17 @@ def create_update_item(
)

dependencies = None
runtime_deps = comp_conf.get("dependencies", [])
if runtime_deps:
dependencies = []
for dep in runtime_deps:
if isinstance(dep, rouge.YamlValue):
dep_codename = dep["codename"].as_str
dep_type = dep.get("type", "component").as_str
dep_versions = dep["versions"].as_str
else:
dep_codename = dep["codename"]
dep_type = dep.get("type", "component")
dep_versions = dep["versions"]

dependencies.append(
AosDependency(
identity=AosDependencyIdentity(
codename=dep_codename,
type=dep_type,
),
versions=dep_versions,
)
dep_conf = comp_conf.get("dependency", None)
if dep_conf:
dependencies = [
AosDependency(
identity=AosDependencyIdentity(
codename=dep_conf["item"].as_str,
type="component",
),
versions=dep_conf["versions"].as_str,
)
]

title = None
description = None
Expand Down
79 changes: 77 additions & 2 deletions scripts/layer_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,82 @@
import os
import shutil
import sys
from typing import Dict, List, Optional

import yaml
from bitbake import call_bitbake
from metadata_builder import BundleBuilder
from moulin import rouge


def resolve_parent_target(
name: str,
item_conf: rouge.YamlValue,
items: rouge.YamlValue,
) -> Optional[str]:
"""Return parent layer bitbake target based on item's dependency, or None."""

dep_conf = item_conf.get("dependency", None)
if not dep_conf:
return None

parent_name = dep_conf["item"].as_str

if parent_name not in items.keys():
raise ValueError(
f"Layer '{name}' depends on unknown item '{parent_name}'"
)

return items[parent_name]["target"].as_str


def order_items(items: rouge.YamlValue) -> List[str]:
"""Topologically sort items so every parent is built before its children."""

graph: Dict[str, Optional[str]] = {}
for name in items.keys():
item_conf = items[name]
dep_conf = item_conf.get("dependency", None)
parent = dep_conf["item"].as_str if dep_conf else None

if parent is not None and parent not in items.keys():
raise ValueError(
f"Layer '{name}' depends on unknown item '{parent}'"
)

graph[name] = parent

ordered: List[str] = []
visiting: set = set()
visited: set = set()

def visit(node: str) -> None:
if node in visited:
return
if node in visiting:
raise ValueError(
f"Cyclic layer dependency detected involving '{node}'"
)

visiting.add(node)
parent = graph[node]
if parent is not None:
visit(parent)
visiting.remove(node)
visited.add(node)
ordered.append(node)

for name in graph:
visit(name)

return ordered


def build_layer(
layers_conf: rouge.YamlValue,
layer_conf: rouge.YamlValue,
layer_dir: str,
parent_target: Optional[str] = None,
) -> int:
"""Build a single layer using bitbake into its own subdirectory."""

Expand All @@ -28,6 +93,13 @@ def build_layer(
("AOS_LAYER_DEPLOY_DIR", layer_dir),
]

if parent_target:
# Scope to this recipe only; otherwise the parent layer (also
# parsed in the same bitbake invocation) would inherit the same
# AOS_PARENT_LAYER and create a self-dependency loop.
target = layer_conf["target"].as_str
bbake_conf.append((f"AOS_PARENT_LAYER:pn-{target}", parent_target))

version = layer_conf.get("version", None)
if version:
bbake_conf.append(("AOS_LAYER_VERSION", version.as_str))
Expand Down Expand Up @@ -60,17 +132,20 @@ def main():

ret = 0
items = layers_conf["items"]
build_order = order_items(items)

for name in items.keys():
for name in build_order:
item_conf = items[name]

if not item_conf.get("enabled", True).as_bool:
continue

parent_target = resolve_parent_target(name, item_conf, items)

print(f"Building layer: {name}")

layer_dir = os.path.join(builder._output_dir, name)
result = build_layer(layers_conf, item_conf, layer_dir)
result = build_layer(layers_conf, item_conf, layer_dir, parent_target)

if result != 0:
print(f"Failed to build layer: {name}")
Expand Down
35 changes: 20 additions & 15 deletions scripts/metadata_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,25 +240,30 @@ def _create_image(self, path: str, architecture: str, os_name: str) -> AosImage:
def _create_dependencies(
self, item_conf: rouge.YamlValue
) -> Optional[List[AosDependency]]:
"""Create dependencies list from config."""
"""Create dependencies list from a singular `dependency` entry.

deps_conf = item_conf.get("dependencies", None)
if not deps_conf:
Expected YAML form:
dependency:
item: <codename>
versions: <semver constraint>

`item` becomes the codename and the dependency type is the
builder's own item_type (layer or component).
"""

dep_conf = item_conf.get("dependency", None)
if not dep_conf:
return None

dependencies = []
for dep in deps_conf:
dependencies.append(
AosDependency(
identity=AosDependencyIdentity(
codename=dep["codename"].as_str,
type=dep.get("type", "component").as_str,
),
versions=dep["versions"].as_str,
)
return [
AosDependency(
identity=AosDependencyIdentity(
codename=dep_conf["item"].as_str,
type=self._item_type,
),
versions=dep_conf["versions"].as_str,
)

return dependencies if dependencies else None
]

def build(self) -> None:
"""Write config.yaml to output_dir/."""
Expand Down