Skip to content

Commit

Permalink
Include firmware and stack versions in filenames (#55)
Browse files Browse the repository at this point in the history
* Include version strings in generated firmware names

* Unconditionally write the GBL metadata JSON

* Use the output name as the archive name

* Fix quoting

* Add a filename format to multi-PAN as well

* Use the correct folder name
  • Loading branch information
puddly authored May 6, 2024
1 parent 6033307 commit d44ebf9
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 106 deletions.
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/[email protected]

- 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/[email protected]
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

0 comments on commit d44ebf9

Please sign in to comment.