Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include firmware and stack versions in filenames #55

Merged
merged 6 commits into from
May 6, 2024
Merged
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 .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,6 @@ jobs:
steps:
- uses: actions/checkout@v4.1.4

- name: Parse firmware manifest
id: read_manifest_yaml
run: |
yq -r '
to_entries
| .[]
| select(.value | type == "string")
| .key + "=" + .value
' "${{ matrix.manifest }}" >> $GITHUB_OUTPUT

manifest_filename=$(basename "${{ matrix.manifest }}")
manifest_base="${manifest_filename%%.*}"
echo "manifest_base=$manifest_base" >> $GITHUB_OUTPUT

- name: Install SDK extensions
run: |
# XXX: slc-cli does not actually work when the extensions aren't in the SDK!
Expand All @@ -137,6 +123,7 @@ jobs:
done

- name: Build firmware
id: build-firmware
run: |
# Fix `fatal: detected dubious ownership in repository at`
git config --global --add safe.directory "$GITHUB_WORKSPACE"
Expand All @@ -154,18 +141,20 @@ jobs:
done

# Build it
mkdir outputs
filename="${{ steps.read_manifest_yaml.outputs['manifest_base'] }}"

python3 tools/build_project.py \
$sdk_args \
$toolchain_args \
--manifest "${{ matrix.manifest }}" \
--build-dir build \
--build-system makefile \
--output "gbl:outputs/$filename.gbl" \
--output "hex:outputs/$filename.hex" \
--output "out:outputs/$filename.out"
--output-dir outputs \
--output gbl \
--output hex \
--output out

# Get the basename of the GBL in `outputs`
output_basename=$(basename -- $(basename -- $(ls -1 outputs/*.gbl | head -n 1)) .gbl)
echo "output_basename=$output_basename" >> $GITHUB_OUTPUT

- name: Install node within container (act)
if: ${{ env.ACT }}
Expand All @@ -176,7 +165,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4.3.3
with:
name: ${{ steps.read_manifest_yaml.outputs['manifest_base'] }}
name: ${{ steps.build-firmware.outputs.output_basename }}
path: outputs/*
compression-level: 9
if-no-files-found: error
1 change: 1 addition & 0 deletions manifests/skyconnect_bootloader-uart-xmodem.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: SkyConnect Bootloader
device: EFR32MG21A020F512IM32
base_project: src/bootloader-uart-xmodem
filename: "{manifest_name}_{gecko_bootloader_version}"

gbl:
fw_type: gecko-bootloader
Expand Down
1 change: 1 addition & 0 deletions manifests/skyconnect_firmware-eraser.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: SkyConnect Firmware Eraser
device: EFR32MG21A020F512IM32
base_project: misc/firmware-eraser
filename: "{manifest_name}_gsdk_{sdk_version}"

gbl: {}
1 change: 1 addition & 0 deletions manifests/skyconnect_ncp-uart-hw.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: SkyConnect Zigbee
device: EFR32MG21A020F512IM32
base_project: src/ncp-uart-hw
filename: "{manifest_name}_{ezsp_version}"

gbl:
fw_type: ncp-uart-hw
Expand Down
1 change: 1 addition & 0 deletions manifests/skyconnect_ot-rcp.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: SkyConnect OpenThread RCP
device: EFR32MG21A020F512IM32
base_project: src/ot-rcp
filename: "{manifest_name}_{ot_rcp_version.split('/')[-1]}_gsdk_{sdk_version}"

gbl:
fw_type: ot-rcp
Expand Down
1 change: 1 addition & 0 deletions manifests/skyconnect_rcp-uart-802154.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: SkyConnect Multi-PAN
device: EFR32MG21A020F512IM32
base_project: src/rcp-uart-802154
filename: "{manifest_name}_gsdk_{sdk_version}"

gbl:
fw_type: rcp-uart-802154
Expand Down
1 change: 1 addition & 0 deletions manifests/yellow_bootloader-uart-xmodem.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Yellow Bootloader
device: MGM210PA32JIA
base_project: src/bootloader-uart-xmodem
filename: "{manifest_name}_{gecko_bootloader_version}"

gbl:
fw_type: gecko-bootloader
Expand Down
1 change: 1 addition & 0 deletions manifests/yellow_ncp-uart-hw.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Yellow Zigbee
device: MGM210PA32JIA
base_project: src/ncp-uart-hw
filename: "{manifest_name}_{ezsp_version}"

gbl:
fw_type: ncp-uart-hw
Expand Down
1 change: 1 addition & 0 deletions manifests/yellow_ot-rcp.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Yellow OpenThread RCP
device: MGM210PA32JIA
base_project: src/ot-rcp
filename: "{manifest_name}_{ot_rcp_version.split('/')[-1]}_gsdk_{sdk_version}"

gbl:
fw_type: ot-rcp
Expand Down
60 changes: 48 additions & 12 deletions tools/build_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
yaml = YAML(typ="safe")


def evaulate_f_string(f_string: str, variables: dict[str, typing.Any]) -> str:
"""
Evaluates an `f`-string with the given locals.
"""

return eval("f" + repr(f_string), variables)


def ensure_folder(path: str | pathlib.Path) -> pathlib.Path:
"""Ensure that the path exists and is a folder."""
path = pathlib.Path(path)
Expand Down Expand Up @@ -89,15 +97,21 @@ def parse_override(override: str) -> tuple[str, dict | list]:
raise argparse.ArgumentTypeError(f"Invalid JSON: {exc}")


def parse_prefixed_output(output: str) -> tuple[str, pathlib.Path]:
def parse_prefixed_output(output: str) -> tuple[str, pathlib.Path | None]:
"""Parse a prefixed output parameter."""
if ":" not in output:
if ":" in output:
prefix, _, path = output.partition(":")
path = pathlib.Path(path)
else:
prefix = output
path = None

if prefix not in ("gbl", "hex", "out"):
raise argparse.ArgumentTypeError(
"Override must be of the form (e.g.) `gbl=output.gbl`"
"Output format is of the form `gbl:overridden_filename.gbl` or just `gbl`"
)

prefix, _, path = output.partition(":")
return prefix, pathlib.Path(path)
return prefix, path


def get_git_commit_id(repo: pathlib.Path) -> str:
Expand Down Expand Up @@ -145,6 +159,13 @@ def main():
required=True,
help="Output file prefixed with its file type",
)
parser.add_argument(
"--output-dir",
dest="output_dir",
type=pathlib.Path,
default=pathlib.Path("."),
help="Output directory for artifacts, will be created if it does not exist",
)
parser.add_argument(
"--no-clean-build-dir",
action="store_false",
Expand Down Expand Up @@ -207,11 +228,6 @@ def main():
if args.toolchains != get_toolchain_default_paths():
args.toolchains = args.toolchains[len(get_toolchain_default_paths()) :]

# Template variables for C defines
value_template_env = {
"git_repo_hash": get_git_commit_id(repo=pathlib.Path(__file__).parent.parent),
}

manifest = yaml.load(args.manifest.read_text())

for key, override in args.overrides:
Expand Down Expand Up @@ -264,6 +280,12 @@ def main():
# Otherwise, append it
output_config.append({"name": name, "value": value})

# Template variables for C defines
value_template_env = {
"git_repo_hash": get_git_commit_id(repo=pathlib.Path(__file__).parent.parent),
"manifest_name": args.manifest.stem,
}

# Copy the base project into the output directory
if args.clean_build_dir:
with contextlib.suppress(OSError):
Expand Down Expand Up @@ -581,11 +603,25 @@ def main():

output_artifact = (cmake_build_root / base_project_name).with_suffix(".gbl")

# Read the metadata extracted from the source and build trees
extracted_gbl_metadata = json.loads(
(output_artifact.parent / "gbl_metadata.json").read_text()
)
base_filename = evaulate_f_string(
manifest.get("filename", "{manifest_name}"),
{**value_template_env, **extracted_gbl_metadata},
)

args.output_dir.mkdir(exist_ok=True)

# Copy the output artifacts
for extension, path in args.outputs:
for extension, output_path in args.outputs:
if output_path is None:
output_path = f"{base_filename}.{extension}"

shutil.copy(
src=output_artifact.with_suffix(f".{extension}"),
dst=path,
dst=args.output_dir / output_path,
)

if args.clean_build_dir:
Expand Down
144 changes: 71 additions & 73 deletions tools/create_gbl.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,88 +158,86 @@ def main():
(project_root / "gbl_metadata.yaml").read_text()
)

# Only create GBL metadata JSON if there is something to create
if "fw_type" in gbl_metadata or "baudrate" in gbl_metadata:
# Prepare the GBL metadata
metadata = {
"metadata_version": 1,
"sdk_version": slcp["sdk"]["version"],
"fw_type": gbl_metadata["fw_type"],
"baudrate": gbl_metadata["baudrate"],
}

# Compute the dynamic metadata
gbl_dynamic = gbl_metadata.get("dynamic", [])

if "ezsp_version" in gbl_dynamic:
gbl_dynamic.remove("ezsp_version")
zigbee_esf_props = parse_properties_file(
(gsdk_path / "protocol/zigbee/esf.properties").read_text()
)
metadata["ezsp_version"] = zigbee_esf_props["version"][0]
# Prepare the GBL metadata
metadata = {
"metadata_version": 1,
"sdk_version": slcp["sdk"]["version"],
"fw_type": gbl_metadata.get("fw_type"),
"baudrate": gbl_metadata.get("baudrate"),
}

# Compute the dynamic metadata
gbl_dynamic = gbl_metadata.get("dynamic", [])

if "ezsp_version" in gbl_dynamic:
gbl_dynamic.remove("ezsp_version")
zigbee_esf_props = parse_properties_file(
(gsdk_path / "protocol/zigbee/esf.properties").read_text()
)
metadata["ezsp_version"] = zigbee_esf_props["version"][0]

if "cpc_version" in gbl_dynamic:
gbl_dynamic.remove("cpc_version")
sl_gsdk_version_h = parse_c_header_defines(
(gsdk_path / "platform/common/inc/sl_gsdk_version.h").read_text()
)
metadata["cpc_version"] = ".".join(
[
str(sl_gsdk_version_h["SL_GSDK_MAJOR_VERSION"]),
str(sl_gsdk_version_h["SL_GSDK_MINOR_VERSION"]),
str(sl_gsdk_version_h["SL_GSDK_PATCH_VERSION"]),
]
)
if "cpc_version" in gbl_dynamic:
gbl_dynamic.remove("cpc_version")
sl_gsdk_version_h = parse_c_header_defines(
(gsdk_path / "platform/common/inc/sl_gsdk_version.h").read_text()
)
metadata["cpc_version"] = ".".join(
[
str(sl_gsdk_version_h["SL_GSDK_MAJOR_VERSION"]),
str(sl_gsdk_version_h["SL_GSDK_MINOR_VERSION"]),
str(sl_gsdk_version_h["SL_GSDK_PATCH_VERSION"]),
]
)

try:
internal_app_config_h = parse_c_header_defines(
(project_root / "config/internal_app_config.h").read_text()
)
except FileNotFoundError:
internal_app_config_h = {}

if "CPC_SECONDARY_APP_VERSION_SUFFIX" in internal_app_config_h:
metadata["cpc_version"] += internal_app_config_h[
"CPC_SECONDARY_APP_VERSION_SUFFIX"
]

if "zwave_version" in gbl_dynamic:
gbl_dynamic.remove("zwave_version")
zwave_esf_props = parse_properties_file(
(gsdk_path / "protocol/z-wave/esf.properties").read_text()
try:
internal_app_config_h = parse_c_header_defines(
(project_root / "config/internal_app_config.h").read_text()
)
metadata["zwave_version"] = zwave_esf_props["version"][0]
except FileNotFoundError:
internal_app_config_h = {}

if "CPC_SECONDARY_APP_VERSION_SUFFIX" in internal_app_config_h:
metadata["cpc_version"] += internal_app_config_h[
"CPC_SECONDARY_APP_VERSION_SUFFIX"
]

if "zwave_version" in gbl_dynamic:
gbl_dynamic.remove("zwave_version")
zwave_esf_props = parse_properties_file(
(gsdk_path / "protocol/z-wave/esf.properties").read_text()
)
metadata["zwave_version"] = zwave_esf_props["version"][0]

if "ot_rcp_version" in gbl_dynamic:
gbl_dynamic.remove("ot_rcp_version")
openthread_config_h = parse_c_header_defines(
(project_root / "config/sl_openthread_generic_config.h").read_text()
)
metadata["ot_rcp_version"] = openthread_config_h["PACKAGE_STRING"]
if "ot_rcp_version" in gbl_dynamic:
gbl_dynamic.remove("ot_rcp_version")
openthread_config_h = parse_c_header_defines(
(project_root / "config/sl_openthread_generic_config.h").read_text()
)
metadata["ot_rcp_version"] = openthread_config_h["PACKAGE_STRING"]

if "gecko_bootloader_version" in gbl_dynamic:
gbl_dynamic.remove("gecko_bootloader_version")
btl_config_h = parse_c_header_defines(
(gsdk_path / "platform/bootloader/config/btl_config.h").read_text()
)
if "gecko_bootloader_version" in gbl_dynamic:
gbl_dynamic.remove("gecko_bootloader_version")
btl_config_h = parse_c_header_defines(
(gsdk_path / "platform/bootloader/config/btl_config.h").read_text()
)

metadata["gecko_bootloader_version"] = ".".join(
[
str(btl_config_h["BOOTLOADER_VERSION_MAIN_MAJOR"]),
str(btl_config_h["BOOTLOADER_VERSION_MAIN_MINOR"]),
str(btl_config_h["BOOTLOADER_VERSION_MAIN_CUSTOMER"]),
]
)
metadata["gecko_bootloader_version"] = ".".join(
[
str(btl_config_h["BOOTLOADER_VERSION_MAIN_MAJOR"]),
str(btl_config_h["BOOTLOADER_VERSION_MAIN_MINOR"]),
str(btl_config_h["BOOTLOADER_VERSION_MAIN_CUSTOMER"]),
]
)

if gbl_dynamic:
raise ValueError(f"Unknown dynamic metadata: {gbl_dynamic}")
if gbl_dynamic:
raise ValueError(f"Unknown dynamic metadata: {gbl_dynamic}")

print("Generated GBL metadata:", metadata, flush=True)
print("Generated GBL metadata:", metadata, flush=True)

# Write it to a file for `commander` to read
(artifact_root / "gbl_metadata.json").write_text(
json.dumps(metadata, sort_keys=True)
)
# Write it to a file for `commander` to read
(artifact_root / "gbl_metadata.json").write_text(
json.dumps(metadata, sort_keys=True)
)

# Make sure the Commander binary is included in the PATH on macOS
if sys.platform == "darwin":
Expand Down