mirror of
https://git.yoctoproject.org/meta-arm
synced 2026-11-14 02:47:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb64770a72 | ||
|
|
c338c73b79 | ||
|
|
43f47aac1a | ||
|
|
37d1e093ef | ||
|
|
108aeb6b71 | ||
|
|
3f2c662098 | ||
|
|
854b732c4a | ||
|
|
2d33942407 | ||
|
|
2fb4a73c21 | ||
|
|
0fb130619a | ||
|
|
4775233f85 | ||
|
|
00829fe4e6 | ||
|
|
2f7d298be0 | ||
|
|
01baa55216 | ||
|
|
3c39f2ecb5 | ||
|
|
fe4086fc8b |
No files matched your search
+2
-1
@@ -1,4 +1,4 @@
|
|||||||
image: ${MIRROR_GHCR}/siemens/kas/kas:5.2
|
image: ${MIRROR_GHCR}/siemens/kas/kas:5.5
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
# These are needed as the k8s executor doesn't respect the container
|
# These are needed as the k8s executor doesn't respect the container
|
||||||
@@ -284,6 +284,7 @@ qemuarm64:
|
|||||||
- DISTRO: poky-tiny
|
- DISTRO: poky-tiny
|
||||||
FIRMWARE: [u-boot, edk2]
|
FIRMWARE: [u-boot, edk2]
|
||||||
TESTING: testimage
|
TESTING: testimage
|
||||||
|
- TF: tf-rmm
|
||||||
|
|
||||||
qemuarm-secureboot:
|
qemuarm-secureboot:
|
||||||
extends: .build
|
extends: .build
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/siemens/kas/master/kas/schema-kas.json
|
||||||
|
|
||||||
|
header:
|
||||||
|
version: 14
|
||||||
|
|
||||||
|
target:
|
||||||
|
- trusted-firmware-rmm
|
||||||
@@ -10,8 +10,8 @@ product: Corstone-1000 with Cortex-A320
|
|||||||
product_type: Open Source Projects
|
product_type: Open Source Projects
|
||||||
product_revision:
|
product_revision:
|
||||||
- prefix: Version
|
- prefix: Version
|
||||||
version: "0101"
|
version: "0102"
|
||||||
version_label: "2026.02"
|
version_label: "2026.11"
|
||||||
output_type: "software"
|
output_type: "software"
|
||||||
conformance_notice: "true"
|
conformance_notice: "true"
|
||||||
content_type: User Guide
|
content_type: User Guide
|
||||||
@@ -24,8 +24,8 @@ themes:
|
|||||||
intended_audience: Software Developers
|
intended_audience: Software Developers
|
||||||
terminology_review: new
|
terminology_review: new
|
||||||
releases:
|
releases:
|
||||||
- revision: 0100-01
|
- revision: 0102-01
|
||||||
date: 2026-05-01
|
date: 2026-11-09
|
||||||
change_summary: Update
|
change_summary: Update
|
||||||
permissions: nonconfidential
|
permissions: nonconfidential
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ variables:
|
|||||||
- name: secure_debug_manager_armds_integration
|
- name: secure_debug_manager_armds_integration
|
||||||
value: 'https://github.com/ARM-software/secure-debug-manager?tab=readme-ov-file#arm-development-studio-integration'
|
value: 'https://github.com/ARM-software/secure-debug-manager?tab=readme-ov-file#arm-development-studio-integration'
|
||||||
- name: meta_arm_repository_release_branch
|
- name: meta_arm_repository_release_branch
|
||||||
value: 'https://docs.yoctoproject.org/next/migration-guides/migration-6.0.html'
|
value: 'https://docs.yoctoproject.org/next/migration-guides/migration-6.1.html'
|
||||||
- name: arm_ulink_pro_website
|
- name: arm_ulink_pro_website
|
||||||
value: 'https://www.arm.com/products/development-tools/debug-probes/ulink-pro'
|
value: 'https://www.arm.com/products/development-tools/debug-probes/ulink-pro'
|
||||||
- name: arm_ds_website
|
- name: arm_ds_website
|
||||||
|
|||||||
@@ -2,6 +2,73 @@
|
|||||||
|
|
||||||
This document contains a summary of the new features, changes and fixes in each release of the Corstone-1000 with Cortex-A320 software stack.
|
This document contains a summary of the new features, changes and fixes in each release of the Corstone-1000 with Cortex-A320 software stack.
|
||||||
|
|
||||||
|
## Version 2026.11 {.reference}
|
||||||
|
|
||||||
|
The following changes are present in this release:
|
||||||
|
|
||||||
|
- Continued Corstone-1000 with Cortex-A320 enablement with U-Boot 2026.07, TF-A 2.15.0, TF-M 2.3.1, OP-TEE 4.10.0, Trusted Services 1.3.0, and Linux 6.19.
|
||||||
|
- Updated TF-M to include Corstone-1000 security fixes and the latest firmware-update partition fixes.
|
||||||
|
|
||||||
|
### Corstone-1000 with Cortex-A320 components versions {.reference}
|
||||||
|
|
||||||
|
The following component versions are available:
|
||||||
|
|
||||||
|
Table: Corstone-1000 with Cortex-A320 component versions
|
||||||
|
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| Component | Version |
|
||||||
|
+========================================+===================================+
|
||||||
|
| linux-yocto | 6.19 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| u-boot | 2026.07 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| optee-client | 4.10.0 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| optee-os | 4.10.0 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| trusted-firmware-a | 2.15.0 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| trusted-firmware-m | 2.3.1 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| libts | v1.3.0 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| ts-sp-{se-proxy, smm-gateway} | v1.3.0 |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
| ts-psa-{crypto, iat, its. ps}-api-test | 74dc6646ff |
|
||||||
|
+----------------------------------------+-----------------------------------+
|
||||||
|
|
||||||
|
### Yocto distribution components versions {.reference}
|
||||||
|
|
||||||
|
The following Yocto distribution components versions are available:
|
||||||
|
|
||||||
|
Table: Yocto distribution component versions
|
||||||
|
|
||||||
|
+-------------------+------------+
|
||||||
|
| Component | Version |
|
||||||
|
+===================+============+
|
||||||
|
| meta-arm | blacksail |
|
||||||
|
+-------------------+------------+
|
||||||
|
| bitbake | 634f8dede6 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| meta-openembedded | a9d69bc066 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| openembedded-core | e182a0c4b9 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| meta-yocto | d0119aa2eb |
|
||||||
|
+-------------------+------------+
|
||||||
|
| meta-secure-core | 56316bacdb |
|
||||||
|
+-------------------+------------+
|
||||||
|
| busybox | 1.38.0 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| musl | 1.2.6 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| gcc-arm-none-eabi | 15.3.rel1 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| gcc-cross-aarch64 | 16.2.0 |
|
||||||
|
+-------------------+------------+
|
||||||
|
| openssl | 4.0.3 |
|
||||||
|
+-------------------+------------+
|
||||||
|
|
||||||
## Version 2026.05 {.reference}
|
## Version 2026.05 {.reference}
|
||||||
|
|
||||||
The following changes are present in this release:
|
The following changes are present in this release:
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ intended for safety-critical applications. Should your software or hardware
|
|||||||
prove defective, you assume the entire cost of all necessary servicing, repair
|
prove defective, you assume the entire cost of all necessary servicing, repair
|
||||||
or correction.
|
or correction.
|
||||||
|
|
||||||
|
## Release notes - 2026.11 {.reference}
|
||||||
|
|
||||||
|
The following knowns issues and limitations are present in this release:
|
||||||
|
|
||||||
|
- Crypto isolation is not supported in the Secure world of Corstone-1000 with Cortex-A320. Additionally, clients in
|
||||||
|
the Normal world are not isolated from one another. Therefore, if an end user wants to add a new
|
||||||
|
Secure Partition (SP) (such as a software TPM) that accesses the Crypto service via the SE-Proxy,
|
||||||
|
they are responsible for implementing their own isolation mechanisms to ensure proper security boundaries.
|
||||||
|
|
||||||
## Release notes - 2026.05 {.reference}
|
## Release notes - 2026.05 {.reference}
|
||||||
|
|
||||||
The following knowns issues and limitations are present in this release:
|
The following knowns issues and limitations are present in this release:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ All the tests in this chapter assume you have already built the software stack a
|
|||||||
|
|
||||||
## Reports {.reference}
|
## Reports {.reference}
|
||||||
|
|
||||||
Reports for the tests conducted on the [Corstone-1000 software (CORSTONE1000-2026.05)](https://git.yoctoproject.org/meta-arm/tag/?h=CORSTONE1000-2026.05) release are available for reference in the [GitLab repo](https://gitlab.arm.com/arm-reference-solutions/arm-reference-solutions-test-report/-/tree/CORSTONE1000-2026.05/embedded-a/corstone1000/CORSTONE1000-2026.05?ref_type=tags).
|
Reports for the tests conducted on the [Corstone-1000 software (CORSTONE1000-2026.11)](https://git.yoctoproject.org/meta-arm/tag/?h=CORSTONE1000-2026.11) release are available for reference in the [GitLab repo](https://gitlab.arm.com/arm-reference-solutions/arm-reference-solutions-test-report/-/tree/CORSTONE1000-2026.11/embedded-a/corstone1000/CORSTONE1000-2026.11?ref_type=tags).
|
||||||
|
|
||||||
## SystemReady IR {.reference}
|
## SystemReady IR {.reference}
|
||||||
|
|
||||||
@@ -29,8 +29,8 @@ A storage with EFI System Partition (ESP) must exist in the system for the UEFI-
|
|||||||
|
|
||||||
### Use the EFI system partition {.reference}
|
### Use the EFI system partition {.reference}
|
||||||
|
|
||||||
The ESP disk image will automatically be used by the Corstone-1000 FVP as the 2nd MMC card image.
|
The ESP disk image will automatically be used by the Corstone-1000 with Cortex-A320 FVP as the 2nd MMC card image.
|
||||||
It will be used when the SystemReady-IR tests is performed on the FVP in the later section.
|
It will be used when the SystemReady-IR tests are performed on the FVP in the later section.
|
||||||
|
|
||||||
### Run SystemReady IR ACS tests {.reference}
|
### Run SystemReady IR ACS tests {.reference}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ The `BOOT` partition is also used to store test results in the `acs_results` fol
|
|||||||
Ensure that the `acs_results` folder is empty before starting the test.
|
Ensure that the `acs_results` folder is empty before starting the test.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
To build and run ACS tests on Corstone-1000:
|
To build and run ACS tests on Corstone-1000 with Cortex-A320:
|
||||||
|
|
||||||
1. On your host development machine, clone the [Arm SystemReady ACS repository](https://github.com/ARM-software/arm-systemready/).
|
1. On your host development machine, clone the [Arm SystemReady ACS repository](https://github.com/ARM-software/arm-systemready/).
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ To build and run ACS tests on Corstone-1000:
|
|||||||
2. Find the pre-built ACS live image in `${WORKSPACE}/arm-systemready/IR/prebuilt_images/v23.09_2.1.0/ir-acs-live-image-generic-arm64.wic.xz`.
|
2. Find the pre-built ACS live image in `${WORKSPACE}/arm-systemready/IR/prebuilt_images/v23.09_2.1.0/ir-acs-live-image-generic-arm64.wic.xz`.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
This prebuilt ACS image includes v5.13 kernel, which does not provide USB driver support for Corstone-1000.
|
This prebuilt ACS image includes v5.13 kernel, which does not provide USB driver support for Corstone-1000 with Cortex-A320.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
3. Decompress the pre-built ACS live image.
|
3. Decompress the pre-built ACS live image.
|
||||||
@@ -172,14 +172,15 @@ Table: Payload GUIDs
|
|||||||
+-----------+--------------------------------------+
|
+-----------+--------------------------------------+
|
||||||
|
|
||||||
The following section describes the steps to update the firmware using Capsule Update
|
The following section describes the steps to update the firmware using Capsule Update
|
||||||
as the Corstone-1000 supports UEFI.
|
as the Corstone-1000 with Cortex-A320 supports UEFI.
|
||||||
|
|
||||||
The firmware update process is tested with an invalid capsule and with valid capsules to validate the robustness and
|
The firmware update process is tested with an invalid capsule and with valid capsules to validate the robustness and
|
||||||
error-handling capabilities of the firmware update mechanism.
|
error-handling capabilities of the firmware update mechanism.
|
||||||
|
|
||||||
- Positive full capsule update test: The Corstone-1000 is provided with a valid full capsule, which it applies successfully. The system then boots normally and reaches the Linux command prompt.
|
- Positive full capsule update test: The Corstone-1000 with Cortex-A320 is provided with a valid full capsule, which it applies successfully. The system then boots normally and reaches the Linux command prompt.
|
||||||
- Positive partial capsule update test: The Corstone-1000 is provided with a valid partial capsule that specifies an update for a single component only. The capsule is applied successfully, after which the system boots normally and reaches the Linux command prompt.
|
- Positive partial capsule update test: The Corstone-1000 with Cortex-A320 is provided with a valid partial capsule that specifies an update for a single component only. The capsule is applied successfully,
|
||||||
- Rollback protection capsule update test: The Corstone-1000 is provided with an outdated capsule containing lower version numbers for all payloads. The capsule is correctly rejected due to rollback protection, and the previously installed firmware is retained.
|
after which the system boots normally and reaches the Linux command prompt.
|
||||||
|
- Rollback protection capsule update test: The Corstone-1000 with Cortex-A320 is provided with an outdated capsule containing lower version numbers for all payloads. The capsule is correctly rejected due to rollback protection, and the previously installed firmware is retained.
|
||||||
|
|
||||||
Three different capsules are therefore needed to perform the tests.
|
Three different capsules are therefore needed to perform the tests.
|
||||||
|
|
||||||
@@ -383,7 +384,7 @@ To run the test:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Run the following commands in order to run the Corstone-1000 Linux kernel.
|
6. Run the following commands in order to run the Corstone-1000 with Cortex-A320 Linux kernel.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
Otherwise, the execution ends up in the ACS live image.
|
Otherwise, the execution ends up in the ACS live image.
|
||||||
@@ -420,7 +421,7 @@ To run the test:
|
|||||||
Hit any key to stop autoboot:
|
Hit any key to stop autoboot:
|
||||||
```
|
```
|
||||||
|
|
||||||
9. Run the following commands to boot the Corstone-1000 Linux kernel.
|
9. Run the following commands to boot the Corstone-1000 with Cortex-A320 Linux kernel.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
If these commands are not executed, the system will default to booting into the ACS live image.
|
If these commands are not executed, the system will default to booting into the ACS live image.
|
||||||
@@ -524,7 +525,7 @@ To run the rollback protection capsule update test:
|
|||||||
Hit any key to stop autoboot:
|
Hit any key to stop autoboot:
|
||||||
```
|
```
|
||||||
|
|
||||||
8. Run the following commands in order to run the Corstone-1000 Linux kernel, otherwise, the execution ends up in the ACS live image.
|
8. Run the following commands in order to run the Corstone-1000 with Cortex-A320 Linux kernel, otherwise, the execution ends up in the ACS live image.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ unzip $kernel_addr 0x90000000
|
$ unzip $kernel_addr 0x90000000
|
||||||
@@ -545,7 +546,7 @@ To run the rollback protection capsule update test:
|
|||||||
Hit any key to stop autoboot:
|
Hit any key to stop autoboot:
|
||||||
```
|
```
|
||||||
|
|
||||||
11. Run the following commands to boot the Corstone-1000 Linux kernel. If these commands are not executed, the system will default to booting into the ACS live image.
|
11. Run the following commands to boot the Corstone-1000 with Cortex-A320 Linux kernel. If these commands are not executed, the system will default to booting into the ACS live image.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ unzip $kernel_addr 0x90000000
|
$ unzip $kernel_addr 0x90000000
|
||||||
@@ -621,28 +622,28 @@ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status
|
|||||||
The following list shows the details of the first four ESRT entries for the positive capsule update test:
|
The following list shows the details of the first four ESRT entries for the positive capsule update test:
|
||||||
|
|
||||||
- BL2 (`${BL2_GUID}`):
|
- BL2 (`${BL2_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- TFM_S (`${TFM_S_GUID}`):
|
- TFM_S (`${TFM_S_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- FIP (`${FIP_GUID}`):
|
- FIP (`${FIP_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- INITRAMFS (`${INITRAMFS_GUID}`):
|
- INITRAMFS (`${INITRAMFS_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
@@ -654,28 +655,28 @@ The following list shows the details of the first four ESRT entries for the posi
|
|||||||
The following list shows the details of the first four ESRT entries for the positive partial capsule update test:
|
The following list shows the details of the first four ESRT entries for the positive partial capsule update test:
|
||||||
|
|
||||||
- BL2 (`${BL2_GUID}`):
|
- BL2 (`${BL2_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 7
|
- FW version: 7
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 7
|
- Last attempt version: 7
|
||||||
- TFM_S (`${TFM_S_GUID}`):
|
- TFM_S (`${TFM_S_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- FIP (`${FIP_GUID}`):
|
- FIP (`${FIP_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- INITRAMFS (`${INITRAMFS_GUID}`):
|
- INITRAMFS (`${INITRAMFS_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
@@ -687,28 +688,28 @@ The following list shows the details of the first four ESRT entries for the posi
|
|||||||
The following list shows the details of the first four ESRT entries for the rollback protection capsule update test:
|
The following list shows the details of the first four ESRT entries for the rollback protection capsule update test:
|
||||||
|
|
||||||
- BL2 (`${BL2_GUID}`):
|
- BL2 (`${BL2_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 7
|
- FW version: 7
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 1
|
- Last attempt status: 1
|
||||||
- Last attempt version: 5
|
- Last attempt version: 5
|
||||||
- TFM_S (`${TFM_S_GUID}`):
|
- TFM_S (`${TFM_S_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- FIP (`${FIP_GUID}`):
|
- FIP (`${FIP_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
- Last attempt status: 0
|
- Last attempt status: 0
|
||||||
- Last attempt version: 6
|
- Last attempt version: 6
|
||||||
- INITRAMFS (`${INITRAMFS_GUID}`):
|
- INITRAMFS (`${INITRAMFS_GUID}`):
|
||||||
- FW type: 0
|
- FW type: 1
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
@@ -719,14 +720,14 @@ See the [UEFI documentation](https://uefi.org/specs/UEFI/2.10/23_Firmware_Update
|
|||||||
|
|
||||||
## Linux distributions {.reference}
|
## Linux distributions {.reference}
|
||||||
|
|
||||||
This sections describes the steps to install major Linux distributions to the Corstone-1000 Host Processor.
|
This section describes the steps to install major Linux distributions to the Corstone-1000 with Cortex-A320 Host Processor.
|
||||||
|
|
||||||
The Linux distributions to be installed are:
|
The Linux distributions to be installed are:
|
||||||
|
|
||||||
- [Debian](https://www.debian.org/)
|
- [Debian](https://www.debian.org/)
|
||||||
- [openSUSE](https://www.opensuse.org/)
|
- [openSUSE](https://www.opensuse.org/)
|
||||||
|
|
||||||
Follow the instructions below to install the Linux distributions to the Corstone-1000 software stack.
|
Follow the instructions below to install the Linux distributions to the Corstone-1000 with Cortex-A320 software stack.
|
||||||
|
|
||||||
### Prepare installation media {.reference}
|
### Prepare installation media {.reference}
|
||||||
|
|
||||||
@@ -741,7 +742,7 @@ Follow the instructions below to create the installation media.
|
|||||||
|
|
||||||
Note that the location of the ISO file on the development machine will be referred to as `${DISTRO_INSTALLER_ISO_PATH}`.
|
Note that the location of the ISO file on the development machine will be referred to as `${DISTRO_INSTALLER_ISO_PATH}`.
|
||||||
|
|
||||||
2. Create the installation media which will contain the necessary files to install the operation system.
|
2. Create the installation media which will contain the necessary files to install the operating system.
|
||||||
|
|
||||||
The distribution installer ISO file does not need to be burnt to a USB drive.
|
The distribution installer ISO file does not need to be burnt to a USB drive.
|
||||||
It will be used as is when starting the FVP install the distribution.
|
It will be used as is when starting the FVP install the distribution.
|
||||||
@@ -749,9 +750,9 @@ Follow the instructions below to create the installation media.
|
|||||||
### Prepare system drive {.reference}
|
### Prepare system drive {.reference}
|
||||||
|
|
||||||
A system (or boot) drive, to store all the operating system files and used to boot the distribution, is required as
|
A system (or boot) drive, to store all the operating system files and used to boot the distribution, is required as
|
||||||
Corstone-1000 on-board non-volatile storage size is insufficient for installing the distributions.
|
Corstone-1000 with Cortex-A320 on-board non-volatile storage size is insufficient for installing the distributions.
|
||||||
|
|
||||||
1. Create an 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
1. Create a 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
||||||
|
|
||||||
```
|
```
|
||||||
dd if=/dev/zero of=${WORKSPACE}/fvp_distro_system_drive.img \
|
dd if=/dev/zero of=${WORKSPACE}/fvp_distro_system_drive.img \
|
||||||
@@ -914,7 +915,7 @@ To generate keys:
|
|||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
|
|
||||||
git clone https://gitlab.arm.com/arm-reference-solutions/iot-platform-assets \
|
git clone https://gitlab.arm.com/arm-reference-solutions/iot-platform-assets \
|
||||||
-b CORSTONE1000-2026.05
|
-b CORSTONE1000-2026.11
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Set the current working directory to build directory's subdirectory containing the software stack build images.
|
3. Set the current working directory to build directory's subdirectory containing the software stack build images.
|
||||||
@@ -927,7 +928,7 @@ To generate keys:
|
|||||||
|
|
||||||
```
|
```
|
||||||
${WORKSPACE}/iot-platform-assets/corstone1000/secureboot/create_keys_and_sign.sh \
|
${WORKSPACE}/iot-platform-assets/corstone1000/secureboot/create_keys_and_sign.sh \
|
||||||
-d fvp \
|
-d a320-fvp \
|
||||||
-v ${CERTIFICATE_VALIDITY_DURATION_IN_DAYS}
|
-v ${CERTIFICATE_VALIDITY_DURATION_IN_DAYS}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -978,7 +979,7 @@ To run an unsigned image boot test:
|
|||||||
|
|
||||||
```
|
```
|
||||||
corstone1000# \
|
corstone1000# \
|
||||||
load mmc 1:1 $loadaddr corstone1000_secureboot_fvp_images/Image_fvp; \
|
load mmc 1:1 $loadaddr corstone1000_secureboot_a320-fvp_images/Image_a320-fvp; \
|
||||||
loadm $loadaddr $kernel_addr_r $filesize; \
|
loadm $loadaddr $kernel_addr_r $filesize; \
|
||||||
bootefi $kernel_addr_r $fdtcontroladdr
|
bootefi $kernel_addr_r $fdtcontroladdr
|
||||||
|
|
||||||
@@ -997,7 +998,7 @@ Load the signed kernel image.
|
|||||||
|
|
||||||
```
|
```
|
||||||
corstone1000# \
|
corstone1000# \
|
||||||
load mmc 1:1 $loadaddr corstone1000_secureboot_fvp_images/Image_fvp.signed; \
|
load mmc 1:1 $loadaddr corstone1000_secureboot_a320-fvp_images/Image_a320-fvp.signed; \
|
||||||
loadm $loadaddr $kernel_addr_r $filesize; \
|
loadm $loadaddr $kernel_addr_r $filesize; \
|
||||||
bootefi $kernel_addr_r $fdtcontroladdr
|
bootefi $kernel_addr_r $fdtcontroladdr
|
||||||
```
|
```
|
||||||
@@ -1036,7 +1037,7 @@ to communicate between the normal world and the secure world to run the [Arm Pla
|
|||||||
|
|
||||||
The tests use the `arm_tstee` driver to access Trusted Services Secure Partitions from user space. The driver is included in the Linux Kernel, starting from v6.10.
|
The tests use the `arm_tstee` driver to access Trusted Services Secure Partitions from user space. The driver is included in the Linux Kernel, starting from v6.10.
|
||||||
|
|
||||||
1. Start the Corstone-1000 FVP and wait until it boots to Linux.
|
1. Start the Corstone-1000 with Cortex-A320 FVP and wait until it boots to Linux.
|
||||||
|
|
||||||
2. Run the PSA API tests by running the commands below in the order shown:
|
2. Run the PSA API tests by running the commands below in the order shown:
|
||||||
|
|
||||||
@@ -1058,7 +1059,7 @@ Symmetric multiprocessing (SMP) mode is supported on the Corstone-1000 with Cort
|
|||||||
meta-arm/kas/corstone1000-multicore.yml
|
meta-arm/kas/corstone1000-multicore.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Run the Corstone-1000 FVP.
|
2. Run the Corstone-1000 with Cortex-A320 FVP.
|
||||||
|
|
||||||
```
|
```
|
||||||
kas shell meta-arm/kas/corstone1000-a320-fvp.yml:meta-arm/ci/debug.yml:\
|
kas shell meta-arm/kas/corstone1000-a320-fvp.yml:meta-arm/ci/debug.yml:\
|
||||||
@@ -1082,7 +1083,7 @@ To build on Ethos-U85 NPU:
|
|||||||
```
|
```
|
||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
git clone https://git.gitlab.arm.com/arm-reference-solutions/iot-platform-assets.git \
|
git clone https://git.gitlab.arm.com/arm-reference-solutions/iot-platform-assets.git \
|
||||||
-b CORSTONE1000-2026.05
|
-b CORSTONE1000-2026.11
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Copy the additional kas configuration file to:
|
2. Copy the additional kas configuration file to:
|
||||||
@@ -1107,14 +1108,14 @@ To build on Ethos-U85 NPU:
|
|||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Re-build the Corstone-1000 FVP software stack as follows:
|
5. Re-build the Corstone-1000 with Cortex-A320 FVP software stack as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
kas build meta-arm/kas/corstone1000-a320-fvp.yml:meta-arm/ci/debug.yml:\
|
kas build meta-arm/kas/corstone1000-a320-fvp.yml:meta-arm/ci/debug.yml:\
|
||||||
meta-arm/kas/ethos-u85-test.yml
|
meta-arm/kas/ethos-u85-test.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Run the Corstone-1000 FVP:
|
6. Run the Corstone-1000 with Cortex-A320 FVP:
|
||||||
|
|
||||||
```
|
```
|
||||||
kas shell meta-arm/kas/corstone1000-a320-fvp.yml:meta-arm/ci/debug.yml:\
|
kas shell meta-arm/kas/corstone1000-a320-fvp.yml:meta-arm/ci/debug.yml:\
|
||||||
@@ -1122,7 +1123,7 @@ To build on Ethos-U85 NPU:
|
|||||||
-c "../meta-arm/scripts/runfvp"
|
-c "../meta-arm/scripts/runfvp"
|
||||||
```
|
```
|
||||||
|
|
||||||
7. To verify you are running the Corstone-1000 FVP (Cortex-A320), build and run the FVP and inspect the CPU model
|
7. To verify you are running the Corstone-1000 with Cortex-A320 FVP, build and run the FVP and inspect the CPU model
|
||||||
reported in `/proc/cpuinfo` as shown below. Inside the FVP shell, confirm the core type:
|
reported in `/proc/cpuinfo` as shown below. Inside the FVP shell, confirm the core type:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The Arm Corstone-1000 with Cortex-A320 software stack uses the Yocto Project to build a tiny Linux distribution suitable for the Arm Corstone-1000 with Cortex-A320 platform (kernel and initramfs filesystem less than 6 MB on the flash).
|
The Arm Corstone-1000 with Cortex-A320 software stack uses the Yocto Project to build a tiny Linux distribution suitable for the Arm Corstone-1000 with Cortex-A320 platform (kernel and initramfs filesystem less than 6 MB on the flash).
|
||||||
|
|
||||||
The Corstone-1000 with Cortex-A320 software stack can be run on [Arm Corstone-1000 with Cortex-A320 FVP (Fixed Virtual Platform)](https://developer.arm.com/downloads/-/arm-ecosystem-fvps) and is built on top of Yocto Project's [Wrynose release]($meta_arm_repository_release_branch).
|
The Corstone-1000 with Cortex-A320 software stack can be run on [Arm Corstone-1000 with Cortex-A320 FVP (Fixed Virtual Platform)](https://developer.arm.com/downloads/-/arm-ecosystem-fvps) and is built on top of Yocto Project's [Blacksail release]($meta_arm_repository_release_branch).
|
||||||
|
|
||||||
The Yocto Project relies on the [BitBake](https://docs.yoctoproject.org/bitbake.html#bitbake-documentation) tool as its build tool. Please see the [Yocto Project documentation](https://docs.yoctoproject.org/) for more information.
|
The Yocto Project relies on the [BitBake](https://docs.yoctoproject.org/bitbake.html#bitbake-documentation) tool as its build tool. Please see the [Yocto Project documentation](https://docs.yoctoproject.org/) for more information.
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ Table: Trusted Firmware-M secure enclave components
|
|||||||
| Type | Path |
|
| Type | Path |
|
||||||
| --------- | ---------------------------------------------------------------------------------------------------------------- |
|
| --------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||||
| bbappend | `${WORKSPACE}/meta-arm/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m_%.bbappend` |
|
| bbappend | `${WORKSPACE}/meta-arm/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m_%.bbappend` |
|
||||||
| Recipe | `${WORKSPACE}/meta-arm/meta-arm/recipes-bsp/trusted-firmware-m/trusted-firmware-m_2.3.0.bb` |
|
| Recipe | `${WORKSPACE}/meta-arm/meta-arm/recipes-bsp/trusted-firmware-m/trusted-firmware-m_2.3.1.bb` |
|
||||||
|
|
||||||
## Build {.reference}
|
## Build {.reference}
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ Building binaries natively on Windows and AArch64 Linux is not supported. Use an
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
git clone https://git.yoctoproject.org/git/meta-arm -b CORSTONE1000-2026.05
|
git clone https://git.yoctoproject.org/git/meta-arm -b CORSTONE1000-2026.11
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Accept the EULA on the [Arm Developer](https://developer.arm.com/downloads/-/arm-ecosystem-fvps/eula) site to build a Corstone-1000 with Cortex-A320 image for FVP as follows:
|
4. Accept the EULA on the [Arm Developer](https://developer.arm.com/downloads/-/arm-ecosystem-fvps/eula) site to build a Corstone-1000 with Cortex-A320 image for FVP as follows:
|
||||||
|
|||||||
@@ -10,6 +10,68 @@ Change Log
|
|||||||
This document contains a summary of the new features, changes and
|
This document contains a summary of the new features, changes and
|
||||||
fixes in each release of Corstone-1000 software stack.
|
fixes in each release of Corstone-1000 software stack.
|
||||||
|
|
||||||
|
***************
|
||||||
|
Version 2026.11
|
||||||
|
***************
|
||||||
|
|
||||||
|
Changes
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Upgraded key Corstone-1000 components to U-Boot 2026.07, TF-A 2.15.0, TF-M 2.3.1, OP-TEE 4.10.0, and Linux 6.19.
|
||||||
|
- Updated TF-M to include Corstone-1000 security fixes and the latest firmware-update partition fixes.
|
||||||
|
|
||||||
|
Corstone-1000 components versions
|
||||||
|
=================================
|
||||||
|
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| linux-yocto | 6.19 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| u-boot | 2026.07 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| external-system | 0.1.0 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| optee-client | 4.10.0 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| optee-os | 4.10.0 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| trusted-firmware-a | 2.15.0 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| trusted-firmware-m | 2.3.1 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| libts | v1.3.0 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| ts-sp-{se-proxy, smm-gateway} | v1.3.0 |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
| ts-psa-{crypto, iat, its. ps}-api-test | 74dc6646ff |
|
||||||
|
+-------------------------------------------+-------------------+
|
||||||
|
|
||||||
|
Yocto distribution components versions
|
||||||
|
======================================
|
||||||
|
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| meta-arm | blacksail |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| bitbake | 634f8dede6 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| meta-openembedded | a9d69bc066 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| openembedded-core | e182a0c4b9 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| meta-yocto | d0119aa2eb |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| meta-secure-core | 56316bacdb |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| busybox | 1.38.0 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| musl | 1.2.6 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| gcc-arm-none-eabi | 15.3.rel1 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| gcc-cross-aarch64 | 16.2.0 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
| openssl | 4.0.3 |
|
||||||
|
+-------------------------------------------+----------------+
|
||||||
|
|
||||||
***************
|
***************
|
||||||
Version 2026.05
|
Version 2026.05
|
||||||
***************
|
***************
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ intended for safety-critical applications. Should Your Software or Your Hardware
|
|||||||
prove defective, you assume the entire cost of all necessary servicing, repair
|
prove defective, you assume the entire cost of all necessary servicing, repair
|
||||||
or correction.
|
or correction.
|
||||||
|
|
||||||
|
***********************
|
||||||
|
Release notes - 2026.11
|
||||||
|
***********************
|
||||||
|
|
||||||
|
The same notes as the 2025.05 release still apply.
|
||||||
|
|
||||||
***********************
|
***********************
|
||||||
Release notes - 2026.05
|
Release notes - 2026.05
|
||||||
***********************
|
***********************
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ The Corstone-1000 software stack can be run on:
|
|||||||
Yocto Stable Branch
|
Yocto Stable Branch
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Corstone-1000 software stack is built on top of Yocto Project's `Wrynose release <meta-arm-repository-release-branch_>`__.
|
Corstone-1000 software stack is built on top of Yocto Project's `Blacksail release <meta-arm-repository-release-branch_>`__.
|
||||||
|
|
||||||
Software Components
|
Software Components
|
||||||
-------------------
|
-------------------
|
||||||
@@ -173,7 +173,7 @@ Secure Enclave Components
|
|||||||
+----------+-------------------------------------------------------------------------------------------------------+
|
+----------+-------------------------------------------------------------------------------------------------------+
|
||||||
| bbappend | ``${WORKSPACE}/meta-arm/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m_%.bbappend`` |
|
| bbappend | ``${WORKSPACE}/meta-arm/meta-arm-bsp/recipes-bsp/trusted-firmware-m/trusted-firmware-m_%.bbappend`` |
|
||||||
+----------+-------------------------------------------------------------------------------------------------------+
|
+----------+-------------------------------------------------------------------------------------------------------+
|
||||||
| Recipe | ``${WORKSPACE}/meta-arm/meta-arm/recipes-bsp/trusted-firmware-m/trusted-firmware-m_2.3.0.bb`` |
|
| Recipe | ``${WORKSPACE}/meta-arm/meta-arm/recipes-bsp/trusted-firmware-m/trusted-firmware-m_2.3.1.bb`` |
|
||||||
+----------+-------------------------------------------------------------------------------------------------------+
|
+----------+-------------------------------------------------------------------------------------------------------+
|
||||||
|
|
||||||
************************************
|
************************************
|
||||||
@@ -233,7 +233,7 @@ Build
|
|||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
git clone https://git.yoctoproject.org/git/meta-arm -b CORSTONE1000-2026.05
|
git clone https://git.yoctoproject.org/git/meta-arm -b CORSTONE1000-2026.11
|
||||||
|
|
||||||
#. Build a Corstone-1000 image:
|
#. Build a Corstone-1000 image:
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ Flash
|
|||||||
Using smaller cards or unsupported file systems (e.g., exFAT, NTFS) may cause the flashing
|
Using smaller cards or unsupported file systems (e.g., exFAT, NTFS) may cause the flashing
|
||||||
process to fail or the device to become unresponsive.
|
process to fail or the device to become unresponsive.
|
||||||
|
|
||||||
#. Download the FPGA bit file image ``AN550: Arm® Corstone™-1000 for MPS3 Version 2.0``
|
#. Download the FPGA bit file image ``AN550: Arm® Corstone™-1000 for MPS3 Version 2.0 r2p0-00rel0``
|
||||||
on the `Arm Developer website <https://developer.arm.com/tools-and-software/development-boards/fpga-prototyping-boards/download-fpga-images>`__.
|
on the `Arm Developer website <https://developer.arm.com/tools-and-software/development-boards/fpga-prototyping-boards/download-fpga-images>`__.
|
||||||
Click on the ``Download AN550 bundle`` button and login to download the file.
|
Click on the ``Download AN550 bundle`` button and login to download the file.
|
||||||
|
|
||||||
@@ -544,8 +544,8 @@ Tests
|
|||||||
Reports
|
Reports
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Reports for the tests conducted on the `Corstone-1000 software (CORSTONE1000-2026.05) <https://git.yoctoproject.org/meta-arm/tag/?h=CORSTONE1000-2026.05>`__
|
Reports for the tests conducted on the `Corstone-1000 software (CORSTONE1000-2026.11) <https://git.yoctoproject.org/meta-arm/tag/?h=CORSTONE1000-2026.11>`__
|
||||||
release are available for reference `here <https://gitlab.arm.com/arm-reference-solutions/arm-reference-solutions-test-report/-/tree/CORSTONE1000-2026.05/embedded-a/corstone1000/CORSTONE1000-2026.05?ref_type=tags>`__.
|
release are available for reference `here <https://gitlab.arm.com/arm-reference-solutions/arm-reference-solutions-test-report/-/tree/CORSTONE1000-2026.11/embedded-a/corstone1000/CORSTONE1000-2026.11?ref_type=tags>`__.
|
||||||
|
|
||||||
|
|
||||||
.. _clean-secure-flash:
|
.. _clean-secure-flash:
|
||||||
@@ -564,7 +564,7 @@ Clean Secure Flash
|
|||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
git clone https://git.gitlab.arm.com/arm-reference-solutions/iot-platform-assets.git -b CORSTONE1000-2026.05
|
git clone https://git.gitlab.arm.com/arm-reference-solutions/iot-platform-assets.git -b CORSTONE1000-2026.11
|
||||||
|
|
||||||
#. Copy the secure flash cleaning Git patch file to your copy of `meta-arm`.
|
#. Copy the secure flash cleaning Git patch file to your copy of `meta-arm`.
|
||||||
|
|
||||||
@@ -691,7 +691,7 @@ FVP
|
|||||||
===
|
===
|
||||||
|
|
||||||
The ESP disk image will automatically be used by the Corstone-1000 FVP as the 2nd MMC card image.
|
The ESP disk image will automatically be used by the Corstone-1000 FVP as the 2nd MMC card image.
|
||||||
It will be used when the SystemReady-IR tests is performed on the FVP in the later section.
|
It will be used when the SystemReady-IR tests are performed on the FVP in the later section.
|
||||||
|
|
||||||
|
|
||||||
****************************
|
****************************
|
||||||
@@ -749,7 +749,7 @@ The ``BOOT`` partition is also used to store test results in the ``acs_results``
|
|||||||
Ensure that the ``acs_results`` folder is empty before starting the test.
|
Ensure that the ``acs_results`` folder is empty before starting the test.
|
||||||
|
|
||||||
|
|
||||||
This sections below describe how to build and run ACS tests on Corstone-1000.
|
This section below describe how to build and run ACS tests on Corstone-1000.
|
||||||
|
|
||||||
.. _mps3-instructions-for-acs-image:
|
.. _mps3-instructions-for-acs-image:
|
||||||
|
|
||||||
@@ -1126,7 +1126,7 @@ Positive Full Capsule Update Test
|
|||||||
The capsule update will be started.
|
The capsule update will be started.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
The capsule update takes about 8 minutes to complete on MPS3 and between 15-30 minutes on FVP.
|
The capsule update takes about 8 minutes to complete on MPS3 and between 10-15 minutes on FVP.
|
||||||
|
|
||||||
The Corstone-1000 will reset after successfully applying the capsule.
|
The Corstone-1000 will reset after successfully applying the capsule.
|
||||||
|
|
||||||
@@ -1437,13 +1437,13 @@ The following table shows the details of the first four ESRT entries for the pos
|
|||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| ``capsule_flags`` | ``fw_class`` | ``fw_type`` | ``fw_version`` | ``last_attempt_status`` | ``last_attempt_version`` | ``lowest_supported_fw_ver`` |
|
| ``capsule_flags`` | ``fw_class`` | ``fw_type`` | ``fw_version`` | ``last_attempt_status`` | ``last_attempt_version`` | ``lowest_supported_fw_ver`` |
|
||||||
+===================+=======================+=============+================+=========================+==========================+=============================+
|
+===================+=======================+=============+================+=========================+==========================+=============================+
|
||||||
| 0 | ``${BL2_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${BL2_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${TFM_S_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${TFM_S_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${FIP_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${FIP_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${INITRAMFS_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${INITRAMFS_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
|
|
||||||
Positive Partial Capsule Update Test ESRT
|
Positive Partial Capsule Update Test ESRT
|
||||||
@@ -1454,13 +1454,13 @@ The following table shows the details of the first four ESRT entries for the pos
|
|||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| ``capsule_flags`` | ``fw_class`` | ``fw_type`` | ``fw_version`` | ``last_attempt_status`` | ``last_attempt_version`` | ``lowest_supported_fw_ver`` |
|
| ``capsule_flags`` | ``fw_class`` | ``fw_type`` | ``fw_version`` | ``last_attempt_status`` | ``last_attempt_version`` | ``lowest_supported_fw_ver`` |
|
||||||
+===================+=======================+=============+================+=========================+==========================+=============================+
|
+===================+=======================+=============+================+=========================+==========================+=============================+
|
||||||
| 0 | ``${BL2_GUID}`` | 0 | 7 | 0 | 7 | 0 |
|
| 0 | ``${BL2_GUID}`` | 1 | 7 | 0 | 7 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${TFM_S_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${TFM_S_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${FIP_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${FIP_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${INITRAMFS_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${INITRAMFS_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
|
|
||||||
Rollback Protection Capsule Update Test ESRT
|
Rollback Protection Capsule Update Test ESRT
|
||||||
@@ -1471,13 +1471,13 @@ The following table shows the details of the first four ESRT entries for the rol
|
|||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| ``capsule_flags`` | ``fw_class`` | ``fw_type`` | ``fw_version`` | ``last_attempt_status`` | ``last_attempt_version`` | ``lowest_supported_fw_ver`` |
|
| ``capsule_flags`` | ``fw_class`` | ``fw_type`` | ``fw_version`` | ``last_attempt_status`` | ``last_attempt_version`` | ``lowest_supported_fw_ver`` |
|
||||||
+===================+========================+=============+================+=========================+==========================+=============================+
|
+===================+========================+=============+================+=========================+==========================+=============================+
|
||||||
| 0 | ``${BL2_GUID}`` | 0 | 7 | 1 | 5 | 0 |
|
| 0 | ``${BL2_GUID}`` | 1 | 7 | 1 | 5 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${TFM_S_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${TFM_S_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${FIP_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${FIP_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${INITRAMFS_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
| 0 | ``${INITRAMFS_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
|
|
||||||
See the `UEFI documentation <https://uefi.org/specs/UEFI/2.10/23_Firmware_Update_and_Reporting.html#id29>`__ for more information on the significance of the table fields.
|
See the `UEFI documentation <https://uefi.org/specs/UEFI/2.10/23_Firmware_Update_and_Reporting.html#id29>`__ for more information on the significance of the table fields.
|
||||||
@@ -1486,7 +1486,7 @@ See the `UEFI documentation <https://uefi.org/specs/UEFI/2.10/23_Firmware_Update
|
|||||||
Linux Distributions
|
Linux Distributions
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
This sections describes the steps to install major Linux distributions to the Corstone-1000 Host Processor.
|
This section describes the steps to install major Linux distributions to the Corstone-1000 Host Processor.
|
||||||
|
|
||||||
The Linux distributions to be installed are:
|
The Linux distributions to be installed are:
|
||||||
|
|
||||||
@@ -1512,7 +1512,7 @@ Follow the instructions below to create the installation media.
|
|||||||
|
|
||||||
The location of the ISO file on the development machine will be referred to as ``${DISTRO_INSTALLER_ISO_PATH}``.
|
The location of the ISO file on the development machine will be referred to as ``${DISTRO_INSTALLER_ISO_PATH}``.
|
||||||
|
|
||||||
#. Create the installation media which will contain the necessary files to install the operation system.
|
#. Create the installation media which will contain the necessary files to install the operating system.
|
||||||
|
|
||||||
- **MPS3**:
|
- **MPS3**:
|
||||||
|
|
||||||
@@ -1553,7 +1553,7 @@ Corstone-1000 on-board non-volatile storage size is insufficient for installing
|
|||||||
#. Do not yet connect this blank USB drive to the MPS3. It will be used as the primary drive to boot the distribution.
|
#. Do not yet connect this blank USB drive to the MPS3. It will be used as the primary drive to boot the distribution.
|
||||||
|
|
||||||
- FVP:
|
- FVP:
|
||||||
#. Create an 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
#. Create a 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
@@ -1674,6 +1674,13 @@ Boot Distribution
|
|||||||
#. Once the installation is complete, unplug the installation media.
|
#. Once the installation is complete, unplug the installation media.
|
||||||
#. Perform a cold boot of the MPS3.
|
#. Perform a cold boot of the MPS3.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
As a workaround, if U-Boot does not detect the USB drive that contains
|
||||||
|
the installed operating system, copy the contents of that USB drive to
|
||||||
|
another supported USB drive that U-Boot can detect, then boot from the
|
||||||
|
new USB drive.
|
||||||
|
|
||||||
- FVP
|
- FVP
|
||||||
|
|
||||||
The target should automatically boot into the installed operating system image.
|
The target should automatically boot into the installed operating system image.
|
||||||
@@ -1788,7 +1795,7 @@ Generate Keys, Signed Image and Unsigned Image
|
|||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
|
|
||||||
git clone https://gitlab.arm.com/arm-reference-solutions/iot-platform-assets \
|
git clone https://gitlab.arm.com/arm-reference-solutions/iot-platform-assets \
|
||||||
-b CORSTONE1000-2026.05
|
-b CORSTONE1000-2026.11
|
||||||
|
|
||||||
#. Set the current working directory to build directory's subdirectory containing the software stack build images.
|
#. Set the current working directory to build directory's subdirectory containing the software stack build images.
|
||||||
|
|
||||||
@@ -2172,10 +2179,12 @@ For more information about this, see the following resources:
|
|||||||
#. Enable the built-in PSA-ADAC Secure Debug Manager in the copied database.
|
#. Enable the built-in PSA-ADAC Secure Debug Manager in the copied database.
|
||||||
In
|
In
|
||||||
``${CS1000_ADAC_DB}/Boards/Arm_Development_Boards/MPS3_Corstone_1000_ADAC_611/project_types.xml``,
|
``${CS1000_ADAC_DB}/Boards/Arm_Development_Boards/MPS3_Corstone_1000_ADAC_611/project_types.xml``,
|
||||||
add the following element alongside the existing ``dtsl_config`` parameter:
|
add the ``secure_debug_manager`` element immediately below the existing
|
||||||
|
``dtsl_config`` parameter:
|
||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<param default="DtslScript" id="dtsl_config" type="string" visible="false"/>
|
||||||
<secure_debug_manager type="adac_sdc600" manifest="CDB://../../../ATP/manifest.xml" device="CSAPBCOM" reset_type="nSRST"/>
|
<secure_debug_manager type="adac_sdc600" manifest="CDB://../../../ATP/manifest.xml" device="CSAPBCOM" reset_type="nSRST"/>
|
||||||
|
|
||||||
This selects the built-in PSA-ADAC implementation, its authentication-token
|
This selects the built-in PSA-ADAC implementation, its authentication-token
|
||||||
@@ -2193,7 +2202,8 @@ For more information about this, see the following resources:
|
|||||||
DPv3 is required so that Arm Development Studio uses the ADIv6 debug-port
|
DPv3 is required so that Arm Development Studio uses the ADIv6 debug-port
|
||||||
addressing needed to reach ``CSAPBCOM``.
|
addressing needed to reach ``CSAPBCOM``.
|
||||||
|
|
||||||
In the same file, give the copied platform a unique title:
|
In the same file, rename the platform to a unique title in the
|
||||||
|
``<extension name="ds">`` extension block:
|
||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
@@ -2301,7 +2311,7 @@ For more information about this, see the following resources:
|
|||||||
*Copyright (c) 2022-2026, Arm Limited. All rights reserved.*
|
*Copyright (c) 2022-2026, Arm Limited. All rights reserved.*
|
||||||
|
|
||||||
.. _arm-developer-fvp: https://developer.arm.com/tools-and-software/open-source-software/arm-platforms-software/arm-ecosystem-fvps
|
.. _arm-developer-fvp: https://developer.arm.com/tools-and-software/open-source-software/arm-platforms-software/arm-ecosystem-fvps
|
||||||
.. _meta-arm-repository-release-branch: https://docs.yoctoproject.org/next/migration-guides/migration-6.0.html
|
.. _meta-arm-repository-release-branch: https://docs.yoctoproject.org/next/migration-guides/migration-6.1.html
|
||||||
.. _arm-ds-website: https://www.arm.com/products/development-tools/embedded-and-software/arm-development-studio
|
.. _arm-ds-website: https://www.arm.com/products/development-tools/embedded-and-software/arm-development-studio
|
||||||
.. _arm-ulink-pro-d-website: https://www.arm.com/products/development-tools/debug-probes/ulink-pro
|
.. _arm-ulink-pro-d-website: https://www.arm.com/products/development-tools/debug-probes/ulink-pro
|
||||||
.. _arm-dstream-website: https://www.arm.com/products/development-tools/debug-probes/dstream-st
|
.. _arm-dstream-website: https://www.arm.com/products/development-tools/debug-probes/dstream-st
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-2
@@ -12,8 +12,8 @@ LIC_FILES_CHKSUM:aarch64 = "file://share/doc/gcc/Copying.html;md5=90014a59d1783b
|
|||||||
LIC_FILES_CHKSUM:x86-64 = "file://share/doc/gcc/Copying.html;md5=90014a59d1783b37a10240d4d0002c6e"
|
LIC_FILES_CHKSUM:x86-64 = "file://share/doc/gcc/Copying.html;md5=90014a59d1783b37a10240d4d0002c6e"
|
||||||
|
|
||||||
SRC_URI = "https://gitlab.arm.com/api/v4/projects/tooling%2Fgnu-toolchains-for-arm/packages/generic/gnu-toolchain/${PV}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}.tar.xz;name=gcc-${HOST_ARCH}"
|
SRC_URI = "https://gitlab.arm.com/api/v4/projects/tooling%2Fgnu-toolchains-for-arm/packages/generic/gnu-toolchain/${PV}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}.tar.xz;name=gcc-${HOST_ARCH}"
|
||||||
SRC_URI[gcc-aarch64.sha256sum] = "a1c6fdda8b479ea3e235d38dc0994790b840648b60e9fbaf88c82ca117a7a2df"
|
SRC_URI[gcc-x86_64.sha256sum] = "1e4dc409cf4d33ef0a2e92c345f9e340302d355858de9bc3353758b6463895b0"
|
||||||
SRC_URI[gcc-x86_64.sha256sum] = "1b07847728d455f18895f1ebd5d71a40f2ccb7cb3a84ca9a874d7f961a318ce4"
|
SRC_URI[gcc-aarch64.sha256sum] = "63942f3f86e69d7beefa2b946bdb23437628529e7b29f97d32229f65d0905e40"
|
||||||
|
|
||||||
S = "${UNPACKDIR}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}"
|
S = "${UNPACKDIR}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}"
|
||||||
|
|
||||||
+2
-2
@@ -12,8 +12,8 @@ LIC_FILES_CHKSUM:aarch64 = "file://share/doc/gcc/Copying.html;md5=90014a59d1783b
|
|||||||
LIC_FILES_CHKSUM:x86-64 = "file://share/doc/gcc/Copying.html;md5=90014a59d1783b37a10240d4d0002c6e"
|
LIC_FILES_CHKSUM:x86-64 = "file://share/doc/gcc/Copying.html;md5=90014a59d1783b37a10240d4d0002c6e"
|
||||||
|
|
||||||
SRC_URI = "https://gitlab.arm.com/api/v4/projects/tooling%2Fgnu-toolchains-for-arm/packages/generic/gnu-toolchain/${PV}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}.tar.xz;name=gcc-${HOST_ARCH}"
|
SRC_URI = "https://gitlab.arm.com/api/v4/projects/tooling%2Fgnu-toolchains-for-arm/packages/generic/gnu-toolchain/${PV}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}.tar.xz;name=gcc-${HOST_ARCH}"
|
||||||
SRC_URI[gcc-aarch64.sha256sum] = "06979e0c8171de58e5dc2a2b2019330a290f30930f27728af98a83e1a7369b3a"
|
SRC_URI[gcc-aarch64.sha256sum] = "a0dc18893171d39dec82fd8d57eeab3da836fd3df4b61978db3eccbf4a6bb948"
|
||||||
SRC_URI[gcc-x86_64.sha256sum] = "563bebb2b97d53382b956d6ee1fe61e2cae26699901417234a37df505ef9b5fa"
|
SRC_URI[gcc-x86_64.sha256sum] = "463df9665c246fa13bab2102115bbf83440661b6eb70b44f3dfd4712ca30037a"
|
||||||
|
|
||||||
S = "${UNPACKDIR}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}"
|
S = "${UNPACKDIR}/arm-gnu-toolchain-${PV}-${HOST_ARCH}-${BINNAME}"
|
||||||
|
|
||||||
+1
-1
@@ -13,7 +13,7 @@ SRC_URI = "gitsm://git.trustedfirmware.org/hafnium/hafnium.git;protocol=https;br
|
|||||||
file://0001-arm-hafnium-fix-kernel-tool-linking.patch \
|
file://0001-arm-hafnium-fix-kernel-tool-linking.patch \
|
||||||
file://0001-work-around-visibility-issue.patch;patchdir=third_party/dtc \
|
file://0001-work-around-visibility-issue.patch;patchdir=third_party/dtc \
|
||||||
"
|
"
|
||||||
SRCREV = "ce12c6e53838f1cf07d50b616b72db57a81539a4"
|
SRCREV = "676ab03c6a3d78f10ddc0f8b96c02475da24392f"
|
||||||
B = "${WORKDIR}/build"
|
B = "${WORKDIR}/build"
|
||||||
|
|
||||||
COMPATIBLE_MACHINE ?= "invalid"
|
COMPATIBLE_MACHINE ?= "invalid"
|
||||||
@@ -12,6 +12,10 @@ LIC_FILES_CHKSUM = "file://docs/license.rst;md5=6ed7bace7b0bc63021c6eba7b524039e
|
|||||||
SRCREV = "da738d5eae93af342fdc4995dd3c05acb4c9d757"
|
SRCREV = "da738d5eae93af342fdc4995dd3c05acb4c9d757"
|
||||||
SRCBRANCH = "master"
|
SRCBRANCH = "master"
|
||||||
|
|
||||||
|
# The tools use the TF-A source when performing a version check, not
|
||||||
|
# the version of this within the source. So, disable checking
|
||||||
|
UPSTREAM_VERSION_UNKNOWN = "1"
|
||||||
|
|
||||||
inherit python_poetry_core
|
inherit python_poetry_core
|
||||||
|
|
||||||
BBCLASSEXTEND = "native nativesdk"
|
BBCLASSEXTEND = "native nativesdk"
|
||||||
|
|||||||
@@ -44,3 +44,8 @@ do_deploy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addtask deploy after do_install
|
addtask deploy after do_install
|
||||||
|
|
||||||
|
# FIXME - The RMM build embeds TMPDIR paths in the generated binaries.
|
||||||
|
# Skip the buildpaths QA check until these references can be removed.
|
||||||
|
INSANE_SKIP:${PN} += "buildpaths"
|
||||||
|
INSANE_SKIP:${PN}-dbg += "buildpaths"
|
||||||
+1
-4
@@ -1,4 +1,4 @@
|
|||||||
From 7d2f6b6e31fad2c96c6fc55c17e79f8c96b4bfcf Mon Sep 17 00:00:00 2001
|
From 0debd3a1f4b5abf0c75c26ec1e85d9fa33510c89 Mon Sep 17 00:00:00 2001
|
||||||
From: Yeoreum Yun <yeoreum.yun@arm.com>
|
From: Yeoreum Yun <yeoreum.yun@arm.com>
|
||||||
Date: Mon, 10 Aug 2026 15:40:06 +0100
|
Date: Mon, 10 Aug 2026 15:40:06 +0100
|
||||||
Subject: [PATCH] DynamicTablesPkg: fix SmbiosType2Genertor build failure with
|
Subject: [PATCH] DynamicTablesPkg: fix SmbiosType2Genertor build failure with
|
||||||
@@ -36,6 +36,3 @@ index 1250ef8059e9..5345d2b6d233 100644
|
|||||||
{
|
{
|
||||||
DEBUG ((DEBUG_ERROR, "%a: Invalid Baseboard object list\n", __func__));
|
DEBUG ((DEBUG_ERROR, "%a: Invalid Baseboard object list\n", __func__));
|
||||||
return EFI_INVALID_PARAMETER;
|
return EFI_INVALID_PARAMETER;
|
||||||
--
|
|
||||||
2.39.5
|
|
||||||
|
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
From 1c89a7982247eb994956140caf794f4f40e85de9 Mon Sep 17 00:00:00 2001
|
||||||
|
From: santhoshkumarv <santhoshkumarv@ami.com>
|
||||||
|
Date: Mon, 28 Sep 2026 06:06:24 -0700
|
||||||
|
Subject: [PATCH] NetworkPkg/UefiPxeBcDxe: Fix ICMP buffer overflow in
|
||||||
|
IPv4/IPv6 handlers (CVE-2026-97631)
|
||||||
|
|
||||||
|
Root Cause:
|
||||||
|
Incorrect buffer copy logic in ICMP error handling allowed large packets
|
||||||
|
to write beyond the allocated buffer. This could overwrite adjacent memory
|
||||||
|
with attacker-controlled data, causing memory corruption.
|
||||||
|
|
||||||
|
Resolution:
|
||||||
|
Added proper boundary checks and corrected buffer offset calculations
|
||||||
|
in IPv4 and IPv6 ICMP error handlers. Over sized ICMP error packets are now
|
||||||
|
safely truncated when the buffer is full, preventing memory corruption.
|
||||||
|
|
||||||
|
Signed-off-by: santhoshkumarV <santhoshkumarv@ami.com>
|
||||||
|
|
||||||
|
Upstream-Status: Backport [560ed9b5b146ed622d7eb7c40254a9099bd65b84]
|
||||||
|
CVE: CVE-2026-97631
|
||||||
|
Signed-off-by: Jon Mason <jon.mason@arm.com>
|
||||||
|
---
|
||||||
|
NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c | 68 ++++++++++----------------
|
||||||
|
1 file changed, 26 insertions(+), 42 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c b/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
|
||||||
|
index 887ea3e7d7d8..9e97ba4904c7 100644
|
||||||
|
--- a/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
|
||||||
|
+++ b/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
|
||||||
|
@@ -252,6 +252,7 @@ PxeBcIcmpErrorDpcHandle (
|
||||||
|
UINTN Index;
|
||||||
|
UINT32 CopiedLen;
|
||||||
|
UINT8 *IcmpError;
|
||||||
|
+ UINT32 FragmentLen;
|
||||||
|
|
||||||
|
Private = (PXEBC_PRIVATE_DATA *)Context;
|
||||||
|
Mode = &Private->Mode;
|
||||||
|
@@ -317,28 +318,19 @@ PxeBcIcmpErrorDpcHandle (
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
- // Copy the right ICMP error message into mode data.
|
||||||
|
+ // Copy ICMP error fragments into Mode->IcmpError in order. The maximum
|
||||||
|
+ // buffer size is sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR); truncate any excess.
|
||||||
|
//
|
||||||
|
- CopiedLen = 0;
|
||||||
|
- IcmpError = (UINT8 *)&Mode->IcmpError;
|
||||||
|
-
|
||||||
|
- for (Index = 0; Index < RxData->FragmentCount; Index++) {
|
||||||
|
- CopiedLen += RxData->FragmentTable[Index].FragmentLength;
|
||||||
|
- if (CopiedLen <= sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)) {
|
||||||
|
- CopyMem (
|
||||||
|
- IcmpError,
|
||||||
|
- RxData->FragmentTable[Index].FragmentBuffer,
|
||||||
|
- RxData->FragmentTable[Index].FragmentLength
|
||||||
|
- );
|
||||||
|
- } else {
|
||||||
|
- CopyMem (
|
||||||
|
- IcmpError,
|
||||||
|
- RxData->FragmentTable[Index].FragmentBuffer,
|
||||||
|
- CopiedLen - sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)
|
||||||
|
- );
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- IcmpError += CopiedLen;
|
||||||
|
+ for (Index = 0, IcmpError = (UINT8 *)&Mode->IcmpError, CopiedLen = 0;
|
||||||
|
+ (Index < RxData->FragmentCount) &&
|
||||||
|
+ (CopiedLen < sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR));
|
||||||
|
+ Index++, IcmpError += FragmentLen, CopiedLen += FragmentLen)
|
||||||
|
+ {
|
||||||
|
+ FragmentLen = MIN (
|
||||||
|
+ RxData->FragmentTable[Index].FragmentLength,
|
||||||
|
+ (UINT32)(sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR) - CopiedLen)
|
||||||
|
+ );
|
||||||
|
+ CopyMem (IcmpError, RxData->FragmentTable[Index].FragmentBuffer, FragmentLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_RECYCLE:
|
||||||
|
@@ -387,6 +379,7 @@ PxeBcIcmp6ErrorDpcHandle (
|
||||||
|
UINT8 Type;
|
||||||
|
UINT32 CopiedLen;
|
||||||
|
UINT8 *Icmp6Error;
|
||||||
|
+ UINT32 FragmentLen;
|
||||||
|
|
||||||
|
Private = (PXEBC_PRIVATE_DATA *)Context;
|
||||||
|
Mode = &Private->Mode;
|
||||||
|
@@ -449,28 +442,19 @@ PxeBcIcmp6ErrorDpcHandle (
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
- // Copy the right ICMP6 error message into mode data.
|
||||||
|
+ // Copy ICMP error fragments into Mode->IcmpError in order. The maximum
|
||||||
|
+ // buffer size is sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR); truncate any excess.
|
||||||
|
//
|
||||||
|
- CopiedLen = 0;
|
||||||
|
- Icmp6Error = (UINT8 *)&Mode->IcmpError;
|
||||||
|
-
|
||||||
|
- for (Index = 0; Index < RxData->FragmentCount; Index++) {
|
||||||
|
- CopiedLen += RxData->FragmentTable[Index].FragmentLength;
|
||||||
|
- if (CopiedLen <= sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)) {
|
||||||
|
- CopyMem (
|
||||||
|
- Icmp6Error,
|
||||||
|
- RxData->FragmentTable[Index].FragmentBuffer,
|
||||||
|
- RxData->FragmentTable[Index].FragmentLength
|
||||||
|
- );
|
||||||
|
- } else {
|
||||||
|
- CopyMem (
|
||||||
|
- Icmp6Error,
|
||||||
|
- RxData->FragmentTable[Index].FragmentBuffer,
|
||||||
|
- CopiedLen - sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR)
|
||||||
|
- );
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- Icmp6Error += CopiedLen;
|
||||||
|
+ for (Index = 0, Icmp6Error = (UINT8 *)&Mode->IcmpError, CopiedLen = 0;
|
||||||
|
+ (Index < RxData->FragmentCount) &&
|
||||||
|
+ (CopiedLen < sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR));
|
||||||
|
+ Index++, Icmp6Error += FragmentLen, CopiedLen += FragmentLen)
|
||||||
|
+ {
|
||||||
|
+ FragmentLen = MIN (
|
||||||
|
+ RxData->FragmentTable[Index].FragmentLength,
|
||||||
|
+ (UINT32)(sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR) - CopiedLen)
|
||||||
|
+ );
|
||||||
|
+ CopyMem (Icmp6Error, RxData->FragmentTable[Index].FragmentBuffer, FragmentLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
ON_RECYCLE:
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
From bff45da697fecf64927fbc38eacfcf41f20b763b Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michael D Kinney <michael.d.kinney@intel.com>
|
||||||
|
Date: Wed, 14 Oct 2026 12:34:35 -0700
|
||||||
|
Subject: [PATCH] NetworkPkg/UefiPxeBcDxe: Clear ICMP error buffer before
|
||||||
|
copying
|
||||||
|
|
||||||
|
Zero the ICMP error buffer before copying fragments so short packets do not
|
||||||
|
leave stale bytes from a previous error.
|
||||||
|
|
||||||
|
Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com>
|
||||||
|
|
||||||
|
Upstream-Status: Backport [fa789071ff0b29f1e568bfe153ef7f3d76bd60e4]
|
||||||
|
CVE: CVE-2026-97631
|
||||||
|
Signed-off-by: Jon Mason <jon.mason@arm.com>
|
||||||
|
---
|
||||||
|
NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c | 2 ++
|
||||||
|
1 file changed, 2 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c b/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
|
||||||
|
index 9e97ba4904c7..7795e4f187d2 100644
|
||||||
|
--- a/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
|
||||||
|
+++ b/NetworkPkg/UefiPxeBcDxe/PxeBcSupport.c
|
||||||
|
@@ -321,6 +321,7 @@ PxeBcIcmpErrorDpcHandle (
|
||||||
|
// Copy ICMP error fragments into Mode->IcmpError in order. The maximum
|
||||||
|
// buffer size is sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR); truncate any excess.
|
||||||
|
//
|
||||||
|
+ ZeroMem (&Mode->IcmpError, sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR));
|
||||||
|
for (Index = 0, IcmpError = (UINT8 *)&Mode->IcmpError, CopiedLen = 0;
|
||||||
|
(Index < RxData->FragmentCount) &&
|
||||||
|
(CopiedLen < sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR));
|
||||||
|
@@ -445,6 +446,7 @@ PxeBcIcmp6ErrorDpcHandle (
|
||||||
|
// Copy ICMP error fragments into Mode->IcmpError in order. The maximum
|
||||||
|
// buffer size is sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR); truncate any excess.
|
||||||
|
//
|
||||||
|
+ ZeroMem (&Mode->IcmpError, sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR));
|
||||||
|
for (Index = 0, Icmp6Error = (UINT8 *)&Mode->IcmpError, CopiedLen = 0;
|
||||||
|
(Index < RxData->FragmentCount) &&
|
||||||
|
(CopiedLen < sizeof (EFI_PXE_BASE_CODE_ICMP_ERROR));
|
||||||
@@ -4,4 +4,8 @@ SRCREV_edk2 ?= "2970e5699ba6267f3384ffab20f96647578aebc8"
|
|||||||
SRCREV_edk2-platforms ?= "c98cdf8de9bd84a3267623e64f4388e21de69512"
|
SRCREV_edk2-platforms ?= "c98cdf8de9bd84a3267623e64f4388e21de69512"
|
||||||
|
|
||||||
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
|
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
|
||||||
SRC_URI += "file://0001-DynamicTablesPkg-fix-SmbiosType2Genertor-build-failu.patch"
|
SRC_URI += " \
|
||||||
|
file://0001-DynamicTablesPkg-fix-SmbiosType2Genertor-build-failu.patch \
|
||||||
|
file://0002-NetworkPkg-UefiPxeBcDxe-Fix-ICMP-buffer-overflow-in-.patch \
|
||||||
|
file://0003-NetworkPkg-UefiPxeBcDxe-Clear-ICMP-error-buffer-befo.patch \
|
||||||
|
"
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
CONFIG_DEVMEM=y
|
||||||
CONFIG_STRICT_DEVMEM=n
|
CONFIG_STRICT_DEVMEM=n
|
||||||
+2
-2
@@ -33,8 +33,8 @@ SRC_URI = "\
|
|||||||
# As per optee-ftpm TA documentation, we have to use this SHA of MS TPM reference
|
# As per optee-ftpm TA documentation, we have to use this SHA of MS TPM reference
|
||||||
SRCREV_ms-tpm ?= "98b60a44aba79b15fcce1c0d1e46cf5918400f6a"
|
SRCREV_ms-tpm ?= "98b60a44aba79b15fcce1c0d1e46cf5918400f6a"
|
||||||
|
|
||||||
# v4.10.0
|
# v4.11.0
|
||||||
SRCREV_optee-ta ?= "a09269b15de635e1816fe832e26adfbfb44c5455"
|
SRCREV_optee-ta ?= "f09f4cdad21028289d4e8fc573893b75f7228dc7"
|
||||||
|
|
||||||
SRCREV_FORMAT = "ms-tpm_optee-ta"
|
SRCREV_FORMAT = "ms-tpm_optee-ta"
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
require recipes-security/optee/optee-client.inc
|
||||||
|
|
||||||
|
# v4.11.0
|
||||||
|
SRCREV = "da81fb7b30781029bc9d4acfddc7c60b08deb1f3"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
require recipes-security/optee/optee-client.inc
|
require recipes-security/optee/optee-client.inc
|
||||||
|
|
||||||
# v4.10.0
|
# v4.11.0
|
||||||
SRCREV = "9f5e90918093c1d1cd264d8149081b64ab7ba672"
|
SRCREV = "da81fb7b30781029bc9d4acfddc7c60b08deb1f3"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
require recipes-security/optee/optee-examples.inc
|
||||||
|
|
||||||
|
# v4.11.0
|
||||||
|
SRCREV = "71e8f3f320eef46a9369700bf30b91d67427d535"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
require recipes-security/optee/optee-examples.inc
|
require recipes-security/optee/optee-examples.inc
|
||||||
|
|
||||||
# v4.10.0
|
# v4.11.0
|
||||||
SRCREV = "934c7edb74a26e90f68024cf441073528444177f"
|
SRCREV = "71e8f3f320eef46a9369700bf30b91d67427d535"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
require recipes-security/optee/optee-os_${PV}.bb
|
||||||
|
|
||||||
|
SUMMARY = "OP-TEE Trusted OS TA devkit"
|
||||||
|
DESCRIPTION = "OP-TEE TA devkit for build TAs"
|
||||||
|
HOMEPAGE = "https://www.op-tee.org/"
|
||||||
|
|
||||||
|
DEPENDS += "python3-pycryptodome-native"
|
||||||
|
DEPENDS:append:toolchain-clang = " lld-native"
|
||||||
|
|
||||||
|
do_install() {
|
||||||
|
#install TA devkit
|
||||||
|
install -d ${D}${includedir}/optee/export-user_ta/
|
||||||
|
for f in ${B}/export-ta_${OPTEE_ARCH}/* ; do
|
||||||
|
cp -aR $f ${D}${includedir}/optee/export-user_ta/
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
do_deploy() {
|
||||||
|
echo "Do not inherit do_deploy from optee-os."
|
||||||
|
}
|
||||||
|
|
||||||
|
SPDX_DEPLOY_ALLOW_EMPTY:task-deploy = "1"
|
||||||
|
|
||||||
|
FILES:${PN} = "${includedir}/optee/"
|
||||||
|
|
||||||
|
# Build paths are currently embedded
|
||||||
|
INSANE_SKIP:${PN}-dev += "buildpaths"
|
||||||
|
|
||||||
|
# Include extra headers needed by SPMC tests to TA DEVKIT.
|
||||||
|
# Supported after op-tee v3.20
|
||||||
|
EXTRA_OEMAKE:append = "${@bb.utils.contains('MACHINE_FEATURES', 'optee-spmc-test', \
|
||||||
|
' CFG_SPMC_TESTS=y', '' , d)}"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
require recipes-security/optee/optee-os.inc
|
||||||
|
|
||||||
|
DEPENDS += "dtc-native"
|
||||||
|
|
||||||
|
# v4.11.0
|
||||||
|
SRCREV = "f54190f22611dd5ad5d22546fd4209aa100f56bd"
|
||||||
@@ -2,8 +2,8 @@ require recipes-security/optee/optee-os.inc
|
|||||||
|
|
||||||
DEPENDS += "dtc-native"
|
DEPENDS += "dtc-native"
|
||||||
|
|
||||||
# v4.10.0
|
# v4.11.0
|
||||||
SRCREV = "753afbbee1682f5d16fd30e87b31058a4fd4f4b8"
|
SRCREV = "f54190f22611dd5ad5d22546fd4209aa100f56bd"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
require recipes-security/optee/optee-test.inc
|
||||||
|
|
||||||
|
# v4.11.0
|
||||||
|
SRCREV = "179740b43809069ee567d58ebd8e0f85ef8168e2"
|
||||||
|
|
||||||
|
LIC_FILES_CHKSUM = "file://LICENSE.md;md5=a8fa504109e4cd7ea575bc49ea4be560 \
|
||||||
|
file://LICENSE-BSD;md5=dca16d6efa93b55d0fd662ae5cd6feeb \
|
||||||
|
file://LICENSE-GPL;md5=10e86b5d2a6cb0e2b9dcfdd26a9ac58d \
|
||||||
|
"
|
||||||
|
|
||||||
|
# Include ffa_spmc test group if the SPMC test is enabled.
|
||||||
|
# Supported after op-tee v3.20
|
||||||
|
EXTRA_OEMAKE:append = "${@bb.utils.contains('MACHINE_FEATURES', 'optee-spmc-test', \
|
||||||
|
' CFG_SPMC_TESTS=y CFG_SECURE_PARTITION=y', '' , d)}"
|
||||||
|
|
||||||
|
RDEPENDS:${PN} += "${@bb.utils.contains('MACHINE_FEATURES', 'optee-spmc-test', \
|
||||||
|
' arm-ffa-user', '' , d)}"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
require recipes-security/optee/optee-test.inc
|
require recipes-security/optee/optee-test.inc
|
||||||
|
|
||||||
# v4.10.0
|
# v4.11.0
|
||||||
SRCREV = "88c93e87a5c172363ee986ded036a25cafcc9d2c"
|
SRCREV = "179740b43809069ee567d58ebd8e0f85ef8168e2"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2026 Arm Limited
|
||||||
|
"""Report dangling patches, unreferenced BSP includes, and candidate obsolete BSP recipes.
|
||||||
|
|
||||||
|
Static analysis only; no BitBake environment required. Recipe/include results are
|
||||||
|
review-only. Patch deletion is opt-in and still requires care with dynamic SRC_URI.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import fnmatch
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
META_EXT = {'.bb', '.bbappend', '.inc', '.conf', '.bbclass'}
|
||||||
|
ASSIGN = re.compile(r'^\s*([A-Za-z0-9_${}/.+%-]+(?::[A-Za-z0-9_${}/.+%-]+)*)\s*(?:\?\?=|\?=|:=|\+=|=\+|\.=|=\.|=)\s*(.*)$')
|
||||||
|
RECIPE = re.compile(r'^(.+)_([^_]+)\.(bb|bbappend)$')
|
||||||
|
TOKEN = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.+%-]*')
|
||||||
|
|
||||||
|
|
||||||
|
def git(cmd, root):
|
||||||
|
return subprocess.run(['git', '-C', str(root), *cmd], capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
|
def load(root):
|
||||||
|
r = git(['rev-parse', '--show-toplevel'], root)
|
||||||
|
if r.returncode:
|
||||||
|
raise RuntimeError('Not in a Git checkout; specify --root')
|
||||||
|
root = Path(r.stdout.decode().strip()).resolve()
|
||||||
|
r = git(['ls-files', '-z'], root)
|
||||||
|
if r.returncode:
|
||||||
|
raise RuntimeError('git ls-files failed')
|
||||||
|
paths = [Path(p.decode('utf-8', 'surrogateescape')) for p in r.stdout.split(b'\0') if p]
|
||||||
|
sources = {}
|
||||||
|
for path in paths:
|
||||||
|
if path.suffix in META_EXT:
|
||||||
|
try:
|
||||||
|
sources[path] = (root / path).read_text(encoding='utf-8', errors='replace')
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return root, paths, sources
|
||||||
|
|
||||||
|
|
||||||
|
def in_bsp(path):
|
||||||
|
return path.parts and path.parts[0] == 'meta-arm-bsp'
|
||||||
|
|
||||||
|
|
||||||
|
def recipe_tree(path):
|
||||||
|
return any(part.startswith('recipes-') for part in path.parts)
|
||||||
|
|
||||||
|
|
||||||
|
def refs(name, sources, skip=None):
|
||||||
|
return [p for p, text in sources.items() if p != skip and name in text]
|
||||||
|
|
||||||
|
|
||||||
|
def patches(paths, sources):
|
||||||
|
for path in paths:
|
||||||
|
if path.suffix != '.patch' or not recipe_tree(path):
|
||||||
|
continue
|
||||||
|
found = refs(path.name, sources)
|
||||||
|
if not found:
|
||||||
|
yield ('patches', path, 'No literal filename reference in tracked metadata', True)
|
||||||
|
|
||||||
|
|
||||||
|
def includes(paths, sources):
|
||||||
|
for path in paths:
|
||||||
|
if not in_bsp(path) or path.suffix != '.inc':
|
||||||
|
continue
|
||||||
|
found = refs(path.name, sources, path)
|
||||||
|
if found:
|
||||||
|
continue
|
||||||
|
# A basename may be constructed from variable expansions, e.g.
|
||||||
|
# require ${BPN}.inc. Treat these as ambiguous rather than safe.
|
||||||
|
parent = path.parent
|
||||||
|
dynamic = [p for p, content in sources.items()
|
||||||
|
if p != path and (p.parent == parent or parent in p.parents)
|
||||||
|
and re.search(r'(?m)^\s*(?:require|include)\s+[^\n#]*\$\{', content)]
|
||||||
|
note = 'No literal include reference found'
|
||||||
|
if dynamic:
|
||||||
|
note += '; dynamic include(s) in ' + ', '.join(str(p) for p in dynamic[:3])
|
||||||
|
yield ('includes', path, note + ' (review only)', False)
|
||||||
|
|
||||||
|
|
||||||
|
def logical_lines(text):
|
||||||
|
return re.sub(r'\\\s*\n\s*', ' ', text).splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
def metadata(sources):
|
||||||
|
selections = defaultdict(list)
|
||||||
|
mentions = defaultdict(set)
|
||||||
|
for path, content in sources.items():
|
||||||
|
for line in logical_lines(content):
|
||||||
|
line = line.split('#', 1)[0]
|
||||||
|
m = ASSIGN.match(line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
key, value = m.groups()
|
||||||
|
if key.startswith('PREFERRED_VERSION_'):
|
||||||
|
name = key[len('PREFERRED_VERSION_'):].split(':', 1)[0]
|
||||||
|
versions = re.findall(r'["\']([^"\']+)["\']', value)
|
||||||
|
if versions:
|
||||||
|
selections[name].extend((v.strip(), path) for v in versions)
|
||||||
|
continue
|
||||||
|
# Only dependency/provider/image variables imply usage. Merely
|
||||||
|
# mentioning a recipe in an arbitrary string is not sufficient.
|
||||||
|
base = key.split(':', 1)[0]
|
||||||
|
if (base in {'IMAGE_INSTALL', 'CORE_IMAGE_EXTRA_INSTALL', 'PACKAGE_INSTALL',
|
||||||
|
'MACHINE_ESSENTIAL_EXTRA_RDEPENDS', 'MACHINE_ESSENTIAL_EXTRA_RRECOMMENDS',
|
||||||
|
'MACHINE_EXTRA_RDEPENDS', 'MACHINE_EXTRA_RRECOMMENDS',
|
||||||
|
'DEPENDS', 'RDEPENDS', 'RRECOMMENDS', 'EXTRA_IMAGEDEPENDS',
|
||||||
|
'PREFERRED_PROVIDER_virtual/kernel', 'PREFERRED_PROVIDER_virtual/bootloader'}
|
||||||
|
or base.startswith(('RDEPENDS_', 'RRECOMMENDS_', 'PREFERRED_PROVIDER_'))):
|
||||||
|
for token in TOKEN.findall(value):
|
||||||
|
mentions[token].add(path)
|
||||||
|
return selections, mentions
|
||||||
|
|
||||||
|
|
||||||
|
def version_matches(version, pattern):
|
||||||
|
if '${' in pattern or '$(' in pattern:
|
||||||
|
return None
|
||||||
|
return fnmatch.fnmatchcase(version, pattern.replace('%', '*'))
|
||||||
|
|
||||||
|
|
||||||
|
def recipes(paths, sources, verbose=False):
|
||||||
|
selections, mentions = metadata(sources)
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for path in paths:
|
||||||
|
if not in_bsp(path) or not recipe_tree(path):
|
||||||
|
continue
|
||||||
|
m = RECIPE.match(path.name)
|
||||||
|
if m:
|
||||||
|
name, version, kind = m.groups()
|
||||||
|
groups[name].append((path, version, kind))
|
||||||
|
|
||||||
|
for name, entries in sorted(groups.items()):
|
||||||
|
picks = selections.get(name, [])
|
||||||
|
usage = mentions.get(name, set())
|
||||||
|
# Version-specific recipes are candidates only when there is evidence
|
||||||
|
# of a version transition, or a vanished explicit pin. No pin and no
|
||||||
|
# references is suspicious, but not proof: default selection is valid.
|
||||||
|
for path, version, kind in entries:
|
||||||
|
if version == '%' or '${' in version:
|
||||||
|
continue
|
||||||
|
matched = [(p, src) for p, src in picks if version_matches(version, p)]
|
||||||
|
if matched:
|
||||||
|
if verbose:
|
||||||
|
yield ('referenced', path, 'Version selected by ' + ', '.join(str(src) for _, src in matched[:4]), False)
|
||||||
|
continue
|
||||||
|
unknown = [(p, src) for p, src in picks if version_matches(version, p) is None]
|
||||||
|
if unknown:
|
||||||
|
if verbose:
|
||||||
|
yield ('referenced', path, 'Dynamic PREFERRED_VERSION: cannot resolve statically', False)
|
||||||
|
continue
|
||||||
|
# Check versions of the same type, not unrelated bb vs bbappend.
|
||||||
|
peers = [(other, v) for other, v, k in entries if k == kind and other != path]
|
||||||
|
newer_or_selected = [v for _, v in peers if v != '%' and (not picks or any(version_matches(v, p) for p, _ in picks))]
|
||||||
|
if picks:
|
||||||
|
reason = 'No matching PREFERRED_VERSION; selected: ' + ', '.join(sorted({p for p, _ in picks}))
|
||||||
|
if not newer_or_selected:
|
||||||
|
reason += '; no alternative matching local version'
|
||||||
|
else:
|
||||||
|
reason = 'No PREFERRED_VERSION found'
|
||||||
|
if peers:
|
||||||
|
reason += '; other local versions: ' + ', '.join(sorted({v for _, v in peers}))
|
||||||
|
if usage:
|
||||||
|
reason += '; recipe/provider referenced in ' + ', '.join(str(p) for p in sorted(usage)[:3])
|
||||||
|
category = 'potentially obsolete'
|
||||||
|
elif picks or peers:
|
||||||
|
category = 'potentially obsolete'
|
||||||
|
reason += '; no direct image/dependency/provider reference found'
|
||||||
|
else:
|
||||||
|
category = 'likely unused'
|
||||||
|
reason += '; no direct image/dependency/provider reference found (default BitBake selection still possible)'
|
||||||
|
yield ('recipes', path, f'{category}: {reason}', False)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description='Find cleanup candidates in a meta-arm Git checkout (no BitBake required).',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""Checks and scope:
|
||||||
|
patches All layers: tracked .patch files with no literal filename reference
|
||||||
|
in tracked BitBake metadata. Dynamic SRC_URI can cause false positives.
|
||||||
|
includes meta-arm-bsp only: .inc files without literal references; dynamic
|
||||||
|
include/require statements are noted where detected.
|
||||||
|
recipes meta-arm-bsp only: versioned .bb/.bbappend files compared with
|
||||||
|
PREFERRED_VERSION settings and image/dependency/provider mentions.
|
||||||
|
Results are grouped by confidence: likely unused, then potentially
|
||||||
|
obsolete. A recipe may be selected implicitly without a version pin.
|
||||||
|
all Run all checks (default).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
scripts/find-unused.py # Report everything
|
||||||
|
scripts/find-unused.py recipes --verbose # Explain BSP recipe findings
|
||||||
|
scripts/find-unused.py patches # Find dangling patches
|
||||||
|
scripts/find-unused.py includes # Review BSP include files
|
||||||
|
scripts/find-unused.py patches --delete # Confirm each patch removal
|
||||||
|
scripts/find-unused.py patches --delete --force
|
||||||
|
scripts/find-unused.py --root /path/to/meta-arm all
|
||||||
|
|
||||||
|
Safety:
|
||||||
|
Reporting is the default. Only dangling patches can be deleted; recipe
|
||||||
|
and include findings are ALWAYS review-only, even with --delete --force.
|
||||||
|
Searches use tracked files from Git but read the current working tree, so
|
||||||
|
uncommitted edits to machine configurations affect the results. No BitBake
|
||||||
|
parsing, overrides, providers, or dynamic variable expansions are resolved.
|
||||||
|
""")
|
||||||
|
ap.add_argument('check', nargs='?', choices=['all', 'patches', 'includes', 'recipes'], default='all', help='Check to run (default: all)')
|
||||||
|
ap.add_argument('--root', type=Path, default=Path.cwd(), help='Path inside the meta-arm Git checkout (default: current directory)')
|
||||||
|
ap.add_argument('--verbose', action='store_true', help='Show reasons and selected-version matches, including referenced recipes')
|
||||||
|
ap.add_argument('--delete', action='store_true', help='Prompt before deleting dangling patches only')
|
||||||
|
ap.add_argument('--force', action='store_true', help='With --delete, skip prompts')
|
||||||
|
ap.add_argument('--dry-run', action='store_true', help='Explicit report-only mode (the default)')
|
||||||
|
args = ap.parse_args()
|
||||||
|
if args.force and not args.delete:
|
||||||
|
ap.error('--force requires --delete')
|
||||||
|
if args.delete and args.dry_run:
|
||||||
|
ap.error('--delete conflicts with --dry-run')
|
||||||
|
try:
|
||||||
|
root, paths, sources = load(args.root)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
ap.error(str(exc))
|
||||||
|
if args.verbose:
|
||||||
|
print(f'Repository: {root} | tracked files: {len(paths)} | metadata: {len(sources)}')
|
||||||
|
checks = {'patches': patches, 'includes': includes, 'recipes': recipes}
|
||||||
|
total = 0
|
||||||
|
for check, func in checks.items():
|
||||||
|
if args.check not in ('all', check):
|
||||||
|
continue
|
||||||
|
found = list(func(paths, sources, args.verbose) if check == 'recipes' else func(paths, sources))
|
||||||
|
visible = [f for f in found if f[0] != 'referenced']
|
||||||
|
total += len(visible)
|
||||||
|
print(f'\n{check.capitalize()} ({len(visible)} candidates):')
|
||||||
|
if check == 'recipes':
|
||||||
|
def recipe_rank(item):
|
||||||
|
category, path, reason, _ = item
|
||||||
|
if category == 'referenced':
|
||||||
|
return (2, str(path))
|
||||||
|
if reason.startswith('likely unused:'):
|
||||||
|
return (0, str(path))
|
||||||
|
return (1, str(path))
|
||||||
|
found.sort(key=recipe_rank)
|
||||||
|
last_level = None
|
||||||
|
for category, path, reason, safe in found:
|
||||||
|
if category == 'referenced' and not args.verbose:
|
||||||
|
continue
|
||||||
|
if check == 'recipes':
|
||||||
|
level = ('Referenced' if category == 'referenced' else
|
||||||
|
'Likely unused' if reason.startswith('likely unused:') else
|
||||||
|
'Potentially obsolete')
|
||||||
|
if level != last_level:
|
||||||
|
print(f' {level}:')
|
||||||
|
last_level = level
|
||||||
|
print(f' {path}')
|
||||||
|
else:
|
||||||
|
print(f' {path}')
|
||||||
|
if args.verbose or check == 'recipes':
|
||||||
|
print(f' {reason}' if check == 'recipes' else f' {reason}')
|
||||||
|
if args.delete and safe:
|
||||||
|
try:
|
||||||
|
yes = args.force or input(' Delete patch? [y/N] ').strip().lower() == 'y'
|
||||||
|
except EOFError:
|
||||||
|
yes = False
|
||||||
|
if yes:
|
||||||
|
try:
|
||||||
|
(root / path).unlink()
|
||||||
|
print(' Deleted')
|
||||||
|
except OSError as exc:
|
||||||
|
print(f' ERROR: {exc}', file=sys.stderr)
|
||||||
|
elif args.delete and not safe and category != 'referenced':
|
||||||
|
print(' Review only; not deleted')
|
||||||
|
print(f'\nTotal: {total} candidates. Static analysis; verify before removing files.')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,731 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-FileCopyrightText: 2026 Arm Limited
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""Create, publish and announce meta-arm release tags."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from datetime import date, datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from email.message import EmailMessage
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SUPPORT_URL = 'https://docs.yoctoproject.org/ref-manual/release-process.html'
|
||||||
|
VERSION_RE = re.compile(r'^(\d+)\.(\d+)(?:\.(\d+))?$')
|
||||||
|
PLATFORM_TAG_RE = re.compile(r'^[A-Z][A-Z0-9_-]*-[0-9]{4}\.[0-9]{2}\.[0-9]{2}$')
|
||||||
|
DEFAULT_LTS = ('scarthgap', 'wrynose')
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def run(*argv, check=True):
|
||||||
|
result = subprocess.run(argv, text=True, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
if check and result.returncode:
|
||||||
|
raise ReleaseError(f"{' '.join(argv)}: {result.stderr.strip()}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def git(*args, check=True):
|
||||||
|
return run('git', *args, check=check)
|
||||||
|
|
||||||
|
|
||||||
|
def series_number(version):
|
||||||
|
match = VERSION_RE.fullmatch(version)
|
||||||
|
if not match or match.group(3) is not None or int(match.group(2)) > 3:
|
||||||
|
raise ReleaseError(f'Invalid initial release version: {version}')
|
||||||
|
return int(match.group(1)) * 4 + int(match.group(2))
|
||||||
|
|
||||||
|
|
||||||
|
def series_from_number(number):
|
||||||
|
return f'{number // 4}.{number % 4}'
|
||||||
|
|
||||||
|
|
||||||
|
def previous_series(version):
|
||||||
|
number = series_number(version)
|
||||||
|
if number == 0:
|
||||||
|
raise ReleaseError('No previous release version')
|
||||||
|
return series_from_number(number - 1)
|
||||||
|
|
||||||
|
|
||||||
|
def next_patch(version):
|
||||||
|
match = VERSION_RE.fullmatch(version)
|
||||||
|
if not match:
|
||||||
|
raise ReleaseError(f'Invalid version: {version}')
|
||||||
|
return f'{match.group(1)}.{match.group(2)}.{int(match.group(3) or 0) + 1}'
|
||||||
|
|
||||||
|
|
||||||
|
def platform_tag(name, release_date=None):
|
||||||
|
"""Build a platform tag, validating both platform name and calendar date."""
|
||||||
|
if not re.fullmatch(r'[A-Za-z][A-Za-z0-9_-]*', name):
|
||||||
|
raise ReleaseError(f'Invalid platform name: {name}')
|
||||||
|
if release_date is None:
|
||||||
|
release_date = date.today().strftime('%Y.%m.%d')
|
||||||
|
try:
|
||||||
|
datetime.strptime(release_date, '%Y.%m.%d')
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ReleaseError('--date must be a valid YYYY.MM.DD date') from exc
|
||||||
|
if not re.fullmatch(r'\d{4}\.\d{2}\.\d{2}', release_date):
|
||||||
|
raise ReleaseError('--date must use YYYY.MM.DD')
|
||||||
|
return f'{name.upper()}-{release_date}'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BranchStatus:
|
||||||
|
branch: str
|
||||||
|
series: str
|
||||||
|
last_tag: str | None
|
||||||
|
commits: int | None
|
||||||
|
next_tag: str
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseManager:
|
||||||
|
def __init__(self, remote='yoctoproject', recipient='meta-arm@lists.yoctoproject.org',
|
||||||
|
lts=DEFAULT_LTS, execute=False, edit=False, as_of=None, force_lts=False, mailer='auto'):
|
||||||
|
self.remote = remote
|
||||||
|
self.recipient = recipient
|
||||||
|
self.lts = tuple(lts)
|
||||||
|
self.execute = execute
|
||||||
|
self.edit = edit
|
||||||
|
self.as_of = as_of
|
||||||
|
self.force_lts = force_lts
|
||||||
|
if mailer not in ('auto', 'sendmail', 'mutt'):
|
||||||
|
raise ReleaseError(f'Invalid mailer: {mailer}')
|
||||||
|
self.mailer = mailer
|
||||||
|
self._branches = None
|
||||||
|
self._series = None
|
||||||
|
|
||||||
|
def branch_ref(self, branch):
|
||||||
|
return f'refs/remotes/{self.remote}/{branch}'
|
||||||
|
|
||||||
|
def branches(self):
|
||||||
|
if self._branches is None:
|
||||||
|
refs = git('for-each-ref', '--format=%(refname:strip=3)',
|
||||||
|
f'refs/remotes/{self.remote}/').stdout.splitlines()
|
||||||
|
self._branches = sorted(b for b in refs if b not in ('master', 'testing') and '/' not in b)
|
||||||
|
if not self._branches:
|
||||||
|
raise ReleaseError(f'No release branches under {self.remote}')
|
||||||
|
return self._branches
|
||||||
|
|
||||||
|
def require_branch(self, branch):
|
||||||
|
if branch not in self.branches():
|
||||||
|
raise ReleaseError(f'Unknown or excluded upstream branch: {branch}')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ancestor(older, newer):
|
||||||
|
return git('merge-base', '--is-ancestor', older, newer, check=False).returncode == 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tag_commit(tag):
|
||||||
|
result = git('rev-parse', '--verify', '-q', f'refs/tags/{tag}^{{commit}}', check=False)
|
||||||
|
return result.stdout.strip() if result.returncode == 0 else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tag_object(tag):
|
||||||
|
result = git('rev-parse', '--verify', '-q', f'refs/tags/{tag}', check=False)
|
||||||
|
return result.stdout.strip() if result.returncode == 0 else None
|
||||||
|
|
||||||
|
def numeric_tags(self):
|
||||||
|
return [tag for tag in git('tag', '-l').stdout.splitlines() if VERSION_RE.fullmatch(tag)]
|
||||||
|
|
||||||
|
def discover_series(self):
|
||||||
|
"""Use tags that lie on a branch, not inherited tags on its fork history.
|
||||||
|
|
||||||
|
A branch's initial release can be at the fork itself; in that case,
|
||||||
|
resolve shared tags by proximity and fail closed if ambiguous.
|
||||||
|
"""
|
||||||
|
if self._series is not None:
|
||||||
|
return self._series
|
||||||
|
branches = self.branches()
|
||||||
|
tags = self.numeric_tags()
|
||||||
|
forks = {}
|
||||||
|
for branch in branches:
|
||||||
|
result = git('merge-base', self.branch_ref(branch), self.branch_ref('master'))
|
||||||
|
forks[branch] = result.stdout.strip()
|
||||||
|
|
||||||
|
# For every series, assign it to the branch whose tip is nearest
|
||||||
|
# to its latest reachable tag. Ties cannot establish ownership.
|
||||||
|
candidates = {}
|
||||||
|
for tag in tags:
|
||||||
|
match = VERSION_RE.fullmatch(tag)
|
||||||
|
series = f'{match.group(1)}.{match.group(2)}'
|
||||||
|
for branch in branches:
|
||||||
|
if not self.ancestor(f'refs/tags/{tag}', self.branch_ref(branch)):
|
||||||
|
continue
|
||||||
|
distance = int(git('rev-list', '--count',
|
||||||
|
f'{tag}..{self.branch_ref(branch)}').stdout)
|
||||||
|
current = candidates.setdefault(series, {})
|
||||||
|
if branch not in current or distance < current[branch][0] or (
|
||||||
|
distance == current[branch][0] and
|
||||||
|
(int(match.group(3) or 0) > current[branch][1])):
|
||||||
|
current[branch] = (distance, int(match.group(3) or 0), tag)
|
||||||
|
|
||||||
|
owners = {}
|
||||||
|
for series, choices in candidates.items():
|
||||||
|
ranked = sorted((v[0], -v[1], b) for b, v in choices.items())
|
||||||
|
if not ranked:
|
||||||
|
continue
|
||||||
|
# If multiple branches are tied, avoid guessing ownership.
|
||||||
|
if len(ranked) > 1 and ranked[0][:2] == ranked[1][:2]:
|
||||||
|
raise ReleaseError(f'Ambiguous ownership for {series}: {ranked[0][2]}, {ranked[1][2]}')
|
||||||
|
owners[series] = ranked[0][2]
|
||||||
|
|
||||||
|
by_branch = {}
|
||||||
|
for series, branch in owners.items():
|
||||||
|
if branch in by_branch:
|
||||||
|
raise ReleaseError(f'Multiple version series appear to belong to {branch}')
|
||||||
|
by_branch[branch] = series
|
||||||
|
|
||||||
|
if not by_branch:
|
||||||
|
raise ReleaseError('No release version anchors found')
|
||||||
|
|
||||||
|
# Infer only unreleased branches, after the highest established series.
|
||||||
|
missing = [b for b in branches if b not in by_branch]
|
||||||
|
if missing:
|
||||||
|
latest = max(series_number(s) for s in by_branch.values())
|
||||||
|
ordered = sorted(missing, key=lambda b: int(git('show', '-s', '--format=%ct', forks[b]).stdout))
|
||||||
|
for earlier, later in zip(ordered, ordered[1:]):
|
||||||
|
if not self.ancestor(forks[earlier], forks[later]) or forks[earlier] == forks[later]:
|
||||||
|
raise ReleaseError(f'Cannot order unreleased branches: {earlier}, {later}')
|
||||||
|
for branch in ordered:
|
||||||
|
latest += 1
|
||||||
|
by_branch[branch] = series_from_number(latest)
|
||||||
|
self._series = by_branch
|
||||||
|
return by_branch
|
||||||
|
|
||||||
|
def latest_tag(self, branch):
|
||||||
|
series = self.discover_series()[branch]
|
||||||
|
choices = []
|
||||||
|
for tag in self.numeric_tags():
|
||||||
|
match = VERSION_RE.fullmatch(tag)
|
||||||
|
if f'{match.group(1)}.{match.group(2)}' != series:
|
||||||
|
continue
|
||||||
|
if self.ancestor(f'refs/tags/{tag}', self.branch_ref(branch)):
|
||||||
|
choices.append((int(match.group(3) or 0), tag))
|
||||||
|
return max(choices)[1] if choices else None
|
||||||
|
|
||||||
|
def status(self, branch):
|
||||||
|
self.require_branch(branch)
|
||||||
|
series = self.discover_series()[branch]
|
||||||
|
last = self.latest_tag(branch)
|
||||||
|
if not last:
|
||||||
|
return BranchStatus(branch, series, None, None, f'Initial {series}')
|
||||||
|
count = int(git('rev-list', '--count', f'{last}..{self.branch_ref(branch)}').stdout)
|
||||||
|
return BranchStatus(branch, series, last, count, next_patch(last) if count else '-')
|
||||||
|
|
||||||
|
def show_status(self, branches):
|
||||||
|
print(f'{"Branch":<18} {"Version":<9} {"Last-tag":<14} {"Commits":<10} Next-tag')
|
||||||
|
for branch in branches:
|
||||||
|
state = self.status(branch)
|
||||||
|
print(f'{branch:<18} {state.series:<9} {state.last_tag or "(none)":<14} '
|
||||||
|
f'{str(state.commits) if state.commits is not None else "-":<10} {state.next_tag}')
|
||||||
|
|
||||||
|
def create_tags(self, branch, version, ref, kind):
|
||||||
|
self.require_branch(branch)
|
||||||
|
if not VERSION_RE.fullmatch(version):
|
||||||
|
raise ReleaseError(f'Invalid tag version: {version}')
|
||||||
|
target = git('rev-parse', '--verify', f'{ref}^{{commit}}').stdout.strip()
|
||||||
|
if not self.ancestor(target, self.branch_ref(branch)):
|
||||||
|
raise ReleaseError(f'{ref} is not on {branch}')
|
||||||
|
for tag in (version, f'yocto-{version}'):
|
||||||
|
old = self.tag_commit(tag)
|
||||||
|
if old and old != target:
|
||||||
|
raise ReleaseError(f'{tag} already points to another commit')
|
||||||
|
print(f'{branch}: {version} -> {target[:12]} ({kind})')
|
||||||
|
for tag in (version, f'yocto-{version}'):
|
||||||
|
if self.tag_object(tag):
|
||||||
|
print(f' Exists: {tag}')
|
||||||
|
elif self.execute:
|
||||||
|
git('tag', '-s', tag, '-m', f'{kind} for Yocto Project {version} ({branch})', target)
|
||||||
|
print(f' Created: {tag}')
|
||||||
|
else:
|
||||||
|
print(f' DRY RUN: create {tag}')
|
||||||
|
self._series = None
|
||||||
|
|
||||||
|
def release(self, branch, version):
|
||||||
|
self.require_branch(branch)
|
||||||
|
expected = self.discover_series()[branch]
|
||||||
|
if version != expected:
|
||||||
|
raise ReleaseError(f'Expected {expected} for {branch}, got {version}')
|
||||||
|
ref = self.branch_ref(branch)
|
||||||
|
if self.as_of:
|
||||||
|
ref = git('rev-list', '--first-parent', '--before', f'{self.as_of} 23:59:59',
|
||||||
|
'-1', ref).stdout.strip()
|
||||||
|
if not ref:
|
||||||
|
raise ReleaseError(f'No commit on {branch} before {self.as_of}')
|
||||||
|
self.create_tags(branch, version, ref, 'Initial release')
|
||||||
|
|
||||||
|
def update(self, branches):
|
||||||
|
created = []
|
||||||
|
for branch in branches:
|
||||||
|
state = self.status(branch)
|
||||||
|
if not state.last_tag:
|
||||||
|
print(f'{branch}: initial release {state.series} missing; skipping')
|
||||||
|
elif not state.commits:
|
||||||
|
print(f'{branch}: no new commits; skipping')
|
||||||
|
else:
|
||||||
|
self.create_tags(branch, state.next_tag, self.branch_ref(branch), 'Update')
|
||||||
|
created.append((branch, state.next_tag))
|
||||||
|
return created
|
||||||
|
|
||||||
|
def platform(self, name, ref, release_date=None):
|
||||||
|
"""Create a signed platform tag without requiring release branch discovery."""
|
||||||
|
tag = platform_tag(name, release_date)
|
||||||
|
target = git('rev-parse', '--verify', f'{ref}^{{commit}}').stdout.strip()
|
||||||
|
existing = self.tag_object(tag)
|
||||||
|
if existing:
|
||||||
|
if self.tag_commit(tag) != target:
|
||||||
|
raise ReleaseError(f'{tag} already exists at another commit')
|
||||||
|
print(f'Platform tag already exists: {tag} -> {target[:12]}')
|
||||||
|
return tag
|
||||||
|
print(f'Platform: {name}\nCommit: {target[:12]}\nTag: {tag}')
|
||||||
|
if self.execute:
|
||||||
|
message = f'{name.upper()} platform release ({tag.rsplit("-", 1)[1]})\n\nRelease of the {name} platform at commit {target}.'
|
||||||
|
git('tag', '-s', tag, '-m', message, target)
|
||||||
|
print(f'Created signed tag: {tag}')
|
||||||
|
else:
|
||||||
|
print(f'DRY RUN: Would create signed tag {tag}.')
|
||||||
|
return tag
|
||||||
|
|
||||||
|
def tags_for(self, branches):
|
||||||
|
series_map = self.discover_series()
|
||||||
|
selected = branches or self.branches()
|
||||||
|
result = set()
|
||||||
|
for branch in selected:
|
||||||
|
self.require_branch(branch)
|
||||||
|
series = series_map[branch]
|
||||||
|
for tag in self.numeric_tags():
|
||||||
|
match = VERSION_RE.fullmatch(tag)
|
||||||
|
if f'{match.group(1)}.{match.group(2)}' != series:
|
||||||
|
continue
|
||||||
|
if not self.ancestor(f'refs/tags/{tag}', self.branch_ref(branch)):
|
||||||
|
continue
|
||||||
|
paired = f'yocto-{tag}'
|
||||||
|
if not self.tag_commit(paired) or self.tag_commit(paired) != self.tag_commit(tag):
|
||||||
|
raise ReleaseError(f'Missing or mismatched paired tag: {paired}')
|
||||||
|
result.update((tag, paired))
|
||||||
|
return sorted(result)
|
||||||
|
|
||||||
|
def push(self, selections=(), all_tags=False):
|
||||||
|
"""Push selected tags, or all recognized release tags, without overwrites.
|
||||||
|
|
||||||
|
For compatibility, a selection may be an existing release branch.
|
||||||
|
Explicit tag names need not match the recognized tag patterns.
|
||||||
|
"""
|
||||||
|
if all_tags:
|
||||||
|
tags = git('tag', '-l').stdout.splitlines()
|
||||||
|
elif selections:
|
||||||
|
tags = set()
|
||||||
|
for selection in selections:
|
||||||
|
if self.tag_object(selection):
|
||||||
|
tags.add(selection)
|
||||||
|
elif selection in self.branches():
|
||||||
|
tags.update(self.tags_for((selection,)))
|
||||||
|
else:
|
||||||
|
raise ReleaseError(f'Unknown tag or release branch: {selection}')
|
||||||
|
tags = sorted(tags)
|
||||||
|
else:
|
||||||
|
tags = [t for t in git('tag', '-l').stdout.splitlines()
|
||||||
|
if VERSION_RE.fullmatch(t)
|
||||||
|
or (t.startswith('yocto-') and VERSION_RE.fullmatch(t[6:]))
|
||||||
|
or PLATFORM_TAG_RE.fullmatch(t)]
|
||||||
|
if not tags:
|
||||||
|
print('No matching release tags')
|
||||||
|
return
|
||||||
|
remote = {}
|
||||||
|
for line in git('ls-remote', '--tags', '--refs', self.remote).stdout.splitlines():
|
||||||
|
sha, ref = line.split()
|
||||||
|
remote[ref.removeprefix('refs/tags/')] = sha
|
||||||
|
pending = []
|
||||||
|
published = 0
|
||||||
|
for tag in tags:
|
||||||
|
local = self.tag_object(tag)
|
||||||
|
old = remote.get(tag)
|
||||||
|
if old is None:
|
||||||
|
pending.append(tag)
|
||||||
|
elif old == local:
|
||||||
|
published += 1
|
||||||
|
else:
|
||||||
|
raise ReleaseError(f'Tag conflict on {self.remote}: {tag} local={local} remote={old}')
|
||||||
|
print(f'Upstream: {self.remote}\nAlready published: {published}\nNew tags: {len(pending)}')
|
||||||
|
for tag in pending:
|
||||||
|
print(f' {tag}')
|
||||||
|
if pending and self.execute:
|
||||||
|
git('push', '--atomic', self.remote,
|
||||||
|
*(f'refs/tags/{tag}:refs/tags/{tag}' for tag in pending))
|
||||||
|
print(f'Published {len(pending)} tags.')
|
||||||
|
elif not self.execute:
|
||||||
|
print('DRY RUN: No tags pushed.')
|
||||||
|
|
||||||
|
def lts_description(self, branch):
|
||||||
|
if branch in self.lts or self.force_lts:
|
||||||
|
opening = ('This is a Long Term Support (LTS) release. meta-arm generally aims '
|
||||||
|
'to follow the Yocto Project\'s LTS support lifecycle on a best-effort '
|
||||||
|
'basis, with maintenance primarily focused on bug fixes and security '
|
||||||
|
'fixes. CI testing is performed on supported configurations as resources permit.')
|
||||||
|
else:
|
||||||
|
opening = ('This is NOT a Long Term Support (LTS) release. meta-arm generally aims '
|
||||||
|
'to follow the Yocto Project\'s release support lifecycle on a best-effort '
|
||||||
|
'basis. For non-LTS releases, this means approximately seven months of '
|
||||||
|
'limited maintenance, primarily consisting of bug fixes and security fixes, '
|
||||||
|
'with minimal CI testing.')
|
||||||
|
if self.lts:
|
||||||
|
names = ', '.join(self.lts[:-1]) + (' and ' if len(self.lts) > 1 else '') + self.lts[-1]
|
||||||
|
opening += f'\n\nThe currently supported LTS branches are {names}.'
|
||||||
|
return opening + ('\n\nFor more information about the Yocto Project\'s release '
|
||||||
|
f'lifecycle and LTS policy, see:\n{SUPPORT_URL}')
|
||||||
|
|
||||||
|
def announcement(self, branch, version):
|
||||||
|
self.require_branch(branch)
|
||||||
|
series = self.discover_series()[branch]
|
||||||
|
if version != series and not re.fullmatch(re.escape(series) + r'\.\d+', version):
|
||||||
|
raise ReleaseError(f'{version} does not belong to {branch} ({series})')
|
||||||
|
if not self.tag_commit(version) or self.tag_commit(version) != self.tag_commit(f'yocto-{version}'):
|
||||||
|
raise ReleaseError(f'Missing or mismatched release tag pair: {version}')
|
||||||
|
if not self.ancestor(f'refs/tags/{version}', self.branch_ref(branch)):
|
||||||
|
raise ReleaseError(f'{version} is not on {branch}')
|
||||||
|
lts_text = self.lts_description(branch)
|
||||||
|
if version == series:
|
||||||
|
previous = previous_series(version)
|
||||||
|
if not self.tag_commit(previous):
|
||||||
|
raise ReleaseError(f'Missing previous initial release tag {previous}')
|
||||||
|
count = git('rev-list', '--count', f'{previous}..{version}').stdout.strip()
|
||||||
|
subject = f'meta-arm {branch} release'
|
||||||
|
body = (f'meta-arm has released the {branch} support branch, tagged as {version}.\n\n'
|
||||||
|
f'{lts_text}\n\nThis release contains {count} commits since the previous '
|
||||||
|
f'Yocto Project release, {previous}.')
|
||||||
|
else:
|
||||||
|
patch = int(version.rsplit('.', 1)[1])
|
||||||
|
previous = series if patch == 1 else f'{series}.{patch - 1}'
|
||||||
|
if not self.tag_commit(previous) or not self.ancestor(previous, version):
|
||||||
|
raise ReleaseError(f'Missing or non-ancestor previous tag: {previous}')
|
||||||
|
log = git('log', '--reverse', '--format=%h %s', f'{previous}..{version}').stdout.strip()
|
||||||
|
count = int(git('rev-list', '--count', f'{previous}..{version}').stdout)
|
||||||
|
if not count:
|
||||||
|
raise ReleaseError('No commits in update release')
|
||||||
|
dates = git('log', '--reverse', '--format=%cs', f'{previous}..{version}').stdout.splitlines()
|
||||||
|
first, last = dates[0], git('log', '-1', '--format=%cs', version).stdout.strip()
|
||||||
|
subject = f'meta-arm {branch} update'
|
||||||
|
body = (f'meta-arm has released an updated {branch} support tag, {version}.\n\n'
|
||||||
|
f'{lts_text}\n\nThis release contains {count} commits between {first} and {last}.\n\n'
|
||||||
|
'Changes included in this release:\n\n' +
|
||||||
|
'\n'.join(' ' + line for line in log.splitlines()))
|
||||||
|
body += (f'\n\nThe release is available under the {version} and yocto-{version} tags.\n'
|
||||||
|
'Both tags are GPG signed.\n\nThanks,\nThe meta-arm team')
|
||||||
|
return subject, body
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def editor_command():
|
||||||
|
# git var GIT_EDITOR already respects GIT_EDITOR, VISUAL, EDITOR and core.editor.
|
||||||
|
result = git('var', 'GIT_EDITOR', check=False)
|
||||||
|
return result.stdout.strip() if result.returncode == 0 and result.stdout.strip() else 'vi'
|
||||||
|
|
||||||
|
def edit_message(self, subject, body):
|
||||||
|
original = f'Subject: {subject}\n\n{body}\n'
|
||||||
|
with tempfile.TemporaryDirectory(prefix='meta-arm-release-') as directory:
|
||||||
|
path = Path(directory) / 'announcement.txt'
|
||||||
|
path.write_text(original, encoding='utf-8')
|
||||||
|
editor = self.editor_command()
|
||||||
|
print(f'Opening announcement in {editor}...', flush=True)
|
||||||
|
# Interactive editors must inherit stdin, stdout, and stderr.
|
||||||
|
# Git editor settings may contain shell quoting and arguments.
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
['sh', '-c', f'{editor} "$1"', 'editor', str(path)],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as exc:
|
||||||
|
raise ReleaseError('Editor exited unsuccessfully; email not sent') from exc
|
||||||
|
edited = path.read_text(encoding='utf-8')
|
||||||
|
if edited == original:
|
||||||
|
print('Email unchanged; not sending.')
|
||||||
|
return None
|
||||||
|
header, separator, text = edited.partition('\n\n')
|
||||||
|
if not separator or not header.startswith('Subject: '):
|
||||||
|
raise ReleaseError('Edited email must begin with Subject: followed by a blank line')
|
||||||
|
new_subject = header.removeprefix('Subject: ').strip()
|
||||||
|
if not new_subject or '\r' in new_subject or '\n' in new_subject or not text.strip():
|
||||||
|
raise ReleaseError('Invalid edited email subject or body')
|
||||||
|
return new_subject, text.rstrip('\n')
|
||||||
|
|
||||||
|
def mail_backend(self):
|
||||||
|
"""Resolve the requested mailer without silently changing forced choices."""
|
||||||
|
sendmail = shutil.which('sendmail')
|
||||||
|
if not sendmail and os.access('/usr/sbin/sendmail', os.X_OK):
|
||||||
|
sendmail = '/usr/sbin/sendmail'
|
||||||
|
mutt = shutil.which('mutt')
|
||||||
|
|
||||||
|
if self.mailer == 'sendmail':
|
||||||
|
return sendmail
|
||||||
|
if self.mailer == 'mutt':
|
||||||
|
return mutt
|
||||||
|
return sendmail or mutt
|
||||||
|
|
||||||
|
def send(self, subject, body):
|
||||||
|
backend = self.mail_backend()
|
||||||
|
if not backend:
|
||||||
|
raise ReleaseError(f'Requested mailer {self.mailer!r} is unavailable' if self.mailer != 'auto'
|
||||||
|
else 'Neither sendmail nor mutt is available')
|
||||||
|
if os.path.basename(backend) == 'mutt':
|
||||||
|
subprocess.run([backend, '-s', subject, '--', self.recipient],
|
||||||
|
input=body + '\n', text=True, check=True)
|
||||||
|
else:
|
||||||
|
message = EmailMessage()
|
||||||
|
message['To'] = self.recipient
|
||||||
|
message['Subject'] = subject
|
||||||
|
message.set_content(body)
|
||||||
|
subprocess.run([backend, '-t', '-i'], input=message.as_bytes(), check=True)
|
||||||
|
|
||||||
|
def email(self, branch, version):
|
||||||
|
subject, body = self.announcement(branch, version)
|
||||||
|
if self.edit:
|
||||||
|
changed = self.edit_message(subject, body)
|
||||||
|
if changed is None:
|
||||||
|
return
|
||||||
|
subject, body = changed
|
||||||
|
print(f'\nSubject: {subject}\n\n{body}\n')
|
||||||
|
if not self.execute:
|
||||||
|
print('DRY RUN: Email not sent.')
|
||||||
|
return
|
||||||
|
for tag in (version, f'yocto-{version}'):
|
||||||
|
output = git('ls-remote', '--tags', '--refs', self.remote, f'refs/tags/{tag}').stdout
|
||||||
|
entries = dict(line.split()[::-1] for line in output.splitlines())
|
||||||
|
if entries.get(f'refs/tags/{tag}') != self.tag_object(tag):
|
||||||
|
raise ReleaseError(f'{tag} is not published identically on {self.remote}')
|
||||||
|
self.send(subject, body)
|
||||||
|
print('Email submitted successfully.')
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
print('meta-arm release environment\n' + '-' * 48)
|
||||||
|
errors = 0
|
||||||
|
inside = git('rev-parse', '--is-inside-work-tree', check=False).returncode == 0
|
||||||
|
print(f'Git repository: {"OK" if inside else "FAILED"}')
|
||||||
|
errors += not inside
|
||||||
|
url = git('remote', 'get-url', self.remote, check=False)
|
||||||
|
print(f'Upstream remote: {self.remote} ({url.stdout.strip() or "missing"})')
|
||||||
|
reachable = url.returncode == 0 and git('ls-remote', '--heads', self.remote, check=False).returncode == 0
|
||||||
|
print(f'Upstream reachable: {"OK" if reachable else "FAILED"}')
|
||||||
|
errors += not reachable
|
||||||
|
master = git('rev-parse', '--verify', '-q', f'{self.branch_ref("master")}^{{commit}}', check=False).returncode == 0
|
||||||
|
print(f'Master branch: {"OK" if master else "MISSING"}')
|
||||||
|
errors += not master
|
||||||
|
if master:
|
||||||
|
print(f'Release branches: {len(self.branches())}')
|
||||||
|
identity = (git('config', 'user.name', check=False).stdout.strip(),
|
||||||
|
git('config', 'user.email', check=False).stdout.strip())
|
||||||
|
print(f'Git identity: {identity[0]} <{identity[1]}>' if all(identity)
|
||||||
|
else 'Git identity: NOT CONFIGURED')
|
||||||
|
errors += not all(identity)
|
||||||
|
signing = git('config', 'user.signingkey', check=False).stdout.strip()
|
||||||
|
print(f'GPG signing key: {signing or "Git default (not verified)"}')
|
||||||
|
mailer_path = self.mail_backend()
|
||||||
|
mailer_note = ' (forced)' if self.mailer != 'auto' else ' (auto)'
|
||||||
|
print(f'Email delivery: {(mailer_path or "Not available") + mailer_note}')
|
||||||
|
print(f'Email recipient: {self.recipient}')
|
||||||
|
print(f'Editor: {self.editor_command()}')
|
||||||
|
print(f'LTS branches: {", ".join(self.lts)}')
|
||||||
|
print('Environment ready.' if not errors else f'Environment check failed: {errors} error(s).')
|
||||||
|
return not errors
|
||||||
|
|
||||||
|
|
||||||
|
class FriendlyArgumentParser(argparse.ArgumentParser):
|
||||||
|
"""Display the curated usage page without argparse's duplicate sections."""
|
||||||
|
|
||||||
|
def format_help(self):
|
||||||
|
return self.description + '\n'
|
||||||
|
|
||||||
|
|
||||||
|
def parser():
|
||||||
|
"""Build a CLI with a compact, example-driven help page."""
|
||||||
|
help_text = """\
|
||||||
|
Usage: meta-arm-release.py COMMAND [ARGUMENTS] [OPTIONS]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
check Check the release environment
|
||||||
|
status [BRANCH...] Show release status and pending commits
|
||||||
|
release BRANCH VERSION Create initial signed release tags (x.y)
|
||||||
|
update [BRANCH...] Tag branches containing new commits (x.y.z)
|
||||||
|
tag BRANCH VERSION REF Tag a specific historical commit
|
||||||
|
platform NAME REF Create a signed platform release tag
|
||||||
|
push [TAG|BRANCH...] Publish missing release tags to upstream
|
||||||
|
email BRANCH VERSION Preview or send an announcement
|
||||||
|
all BRANCH VERSION Initial release, updates, push and announcements
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help, -? Display this help
|
||||||
|
-n, --dry-run Preview operations (default)
|
||||||
|
-x, --execute Perform requested operations
|
||||||
|
-e, --edit Edit announcements using your configured editor
|
||||||
|
--lts Treat specified release as LTS
|
||||||
|
--as-of YYYY-MM-DD Select historical first-parent commit by date
|
||||||
|
--date YYYY.MM.DD Platform tag date (default: today)
|
||||||
|
--remote NAME Upstream remote (default: yoctoproject)
|
||||||
|
--email-to ADDRESS Override announcement recipient
|
||||||
|
--mailer {auto,sendmail,mutt} Select email delivery method (default: auto)
|
||||||
|
--lts-branches "A B" Set supported LTS branches
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
meta-arm-release.py check
|
||||||
|
meta-arm-release.py status
|
||||||
|
meta-arm-release.py status scarthgap wrynose
|
||||||
|
meta-arm-release.py release blacksail 6.1
|
||||||
|
meta-arm-release.py release blacksail 6.1 --execute
|
||||||
|
meta-arm-release.py update scarthgap whinlatter --execute
|
||||||
|
meta-arm-release.py tag wrynose 6.0 COMMIT --execute
|
||||||
|
meta-arm-release.py platform corstone1000 HEAD
|
||||||
|
meta-arm-release.py platform corstone1000 HEAD --date 2026.10.01 --execute
|
||||||
|
meta-arm-release.py push CORSTONE1000-2026.10.01 --execute
|
||||||
|
meta-arm-release.py push
|
||||||
|
meta-arm-release.py push --execute
|
||||||
|
meta-arm-release.py email blacksail 6.1
|
||||||
|
meta-arm-release.py email blacksail 6.1 --edit
|
||||||
|
meta-arm-release.py email blacksail 6.1 --edit --execute
|
||||||
|
meta-arm-release.py email blacksail 6.1 --mailer mutt --execute
|
||||||
|
meta-arm-release.py all blacksail 6.1 --execute
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
* Dry-run is the default; --execute does not ask for confirmation.
|
||||||
|
* Email previews require no mail program; automatic delivery prefers
|
||||||
|
sendmail and falls back to mutt. Use --mailer to force a backend.
|
||||||
|
* Editor selection follows Git configuration (including VISUAL/EDITOR).
|
||||||
|
* Release branches must exist upstream; branches containing '/' are skipped.
|
||||||
|
* Existing tags are never overwritten; only missing tags are pushed.
|
||||||
|
* Push without arguments discovers Yocto and platform release tags.
|
||||||
|
* Push operations use only the configured upstream remote.
|
||||||
|
* The script never checks out branches.
|
||||||
|
|
||||||
|
Environment variables:
|
||||||
|
META_ARM_RELEASE_REMOTE Override upstream remote
|
||||||
|
META_ARM_RELEASE_EMAIL Override announcement recipient
|
||||||
|
META_ARM_LTS_BRANCHES Space-separated LTS branches
|
||||||
|
META_ARM_MAILER auto, sendmail, or mutt
|
||||||
|
"""
|
||||||
|
p = FriendlyArgumentParser(
|
||||||
|
prog='meta-arm-release.py',
|
||||||
|
description=help_text,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
add_help=False,
|
||||||
|
)
|
||||||
|
p.add_argument('-h', '--help', '-?', action='help', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--execute', '-x', action='store_true', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--dry-run', '-n', action='store_true', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--edit', '-e', action='store_true', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--lts', action='store_true', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--as-of', metavar='YYYY-MM-DD', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--date', metavar='YYYY.MM.DD', help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--remote', default=os.getenv('META_ARM_RELEASE_REMOTE', 'yoctoproject'),
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--email-to', default=os.getenv('META_ARM_RELEASE_EMAIL', 'meta-arm@lists.yoctoproject.org'),
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--mailer', choices=('auto', 'sendmail', 'mutt'),
|
||||||
|
default=os.getenv('META_ARM_MAILER', 'auto'), help=argparse.SUPPRESS)
|
||||||
|
p.add_argument('--lts-branches', default=os.getenv('META_ARM_LTS_BRANCHES', 'scarthgap wrynose'),
|
||||||
|
help=argparse.SUPPRESS)
|
||||||
|
sub = p.add_subparsers(dest='command', required=True, metavar='COMMAND')
|
||||||
|
sub.add_parser('check', add_help=False)
|
||||||
|
q = sub.add_parser('status', add_help=False)
|
||||||
|
q.add_argument('branches', nargs='*')
|
||||||
|
q = sub.add_parser('release', add_help=False)
|
||||||
|
q.add_argument('branch')
|
||||||
|
q.add_argument('version')
|
||||||
|
q = sub.add_parser('update', add_help=False)
|
||||||
|
q.add_argument('branches', nargs='*')
|
||||||
|
q = sub.add_parser('tag', add_help=False)
|
||||||
|
q.add_argument('branch')
|
||||||
|
q.add_argument('version')
|
||||||
|
q.add_argument('ref')
|
||||||
|
q = sub.add_parser('platform', add_help=False)
|
||||||
|
q.add_argument('name')
|
||||||
|
q.add_argument('ref')
|
||||||
|
q = sub.add_parser('push', add_help=False)
|
||||||
|
q.add_argument('selections', nargs='*')
|
||||||
|
q = sub.add_parser('email', add_help=False)
|
||||||
|
q.add_argument('branch')
|
||||||
|
q.add_argument('version')
|
||||||
|
q = sub.add_parser('all', add_help=False)
|
||||||
|
q.add_argument('branch')
|
||||||
|
q.add_argument('version')
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None):
|
||||||
|
p = parser()
|
||||||
|
# Support global options either before or after the subcommand.
|
||||||
|
args = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
if not args or any(token in ('-h', '--help', '-?') for token in args):
|
||||||
|
p.print_help()
|
||||||
|
return 0
|
||||||
|
globals_with_value = ('--as-of', '--date', '--remote', '--email-to', '--lts-branches', '--mailer')
|
||||||
|
globals_flags = ('--execute', '-x', '--dry-run', '-n', '--edit', '-e', '--lts')
|
||||||
|
front, rest = [], []
|
||||||
|
i = 0
|
||||||
|
while i < len(args):
|
||||||
|
token = args[i]
|
||||||
|
if token in globals_with_value:
|
||||||
|
if i + 1 >= len(args):
|
||||||
|
p.error(f'{token} requires a value')
|
||||||
|
front.extend(args[i:i+2]); i += 2
|
||||||
|
elif token in globals_flags:
|
||||||
|
front.append(token); i += 1
|
||||||
|
else:
|
||||||
|
rest.append(token); i += 1
|
||||||
|
ns = p.parse_args(front + rest)
|
||||||
|
if ns.email_to.find('\n') >= 0 or ns.email_to.find('\r') >= 0:
|
||||||
|
p.error('Invalid email recipient')
|
||||||
|
if ns.date and ns.command != 'platform':
|
||||||
|
p.error('--date only applies to platform')
|
||||||
|
if ns.edit and ns.command not in ('email', 'all'):
|
||||||
|
p.error('--edit only applies to email and all')
|
||||||
|
if ns.as_of and not re.fullmatch(r'\d{4}-\d{2}-\d{2}', ns.as_of):
|
||||||
|
p.error('--as-of requires YYYY-MM-DD')
|
||||||
|
manager = ReleaseManager(ns.remote, ns.email_to, ns.lts_branches.split(),
|
||||||
|
ns.execute and not ns.dry_run, ns.edit, ns.as_of, ns.lts, ns.mailer)
|
||||||
|
if ns.command == 'check':
|
||||||
|
return 0 if manager.check() else 1
|
||||||
|
if git('rev-parse', '--is-inside-work-tree', check=False).returncode:
|
||||||
|
raise ReleaseError('Not in a Git repository')
|
||||||
|
if ns.command == 'status':
|
||||||
|
manager.show_status(ns.branches or manager.branches())
|
||||||
|
elif ns.command == 'release':
|
||||||
|
manager.release(ns.branch, ns.version)
|
||||||
|
elif ns.command == 'update':
|
||||||
|
manager.update(ns.branches or manager.branches())
|
||||||
|
elif ns.command == 'tag':
|
||||||
|
manager.require_branch(ns.branch)
|
||||||
|
expected = manager.discover_series()[ns.branch]
|
||||||
|
if ns.version != expected and not re.fullmatch(re.escape(expected) + r'\.\d+', ns.version):
|
||||||
|
raise ReleaseError(f'Wrong version series for {ns.branch}: {ns.version}')
|
||||||
|
manager.create_tags(ns.branch, ns.version, ns.ref, 'Release')
|
||||||
|
elif ns.command == 'platform':
|
||||||
|
manager.platform(ns.name, ns.ref, ns.date)
|
||||||
|
elif ns.command == 'push':
|
||||||
|
manager.push(ns.selections)
|
||||||
|
elif ns.command == 'email':
|
||||||
|
manager.email(ns.branch, ns.version)
|
||||||
|
elif ns.command == 'all':
|
||||||
|
manager.release(ns.branch, ns.version)
|
||||||
|
created = [(ns.branch, ns.version)]
|
||||||
|
created += manager.update([b for b in manager.branches() if b != ns.branch])
|
||||||
|
if manager.execute:
|
||||||
|
manager.push()
|
||||||
|
for branch, version in created:
|
||||||
|
manager.email(branch, version)
|
||||||
|
else:
|
||||||
|
print('DRY RUN: No tags created, pushed or emails sent.')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except (ReleaseError, subprocess.CalledProcessError) as error:
|
||||||
|
print(f'ERROR: {error}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
# Copyright (c) 2026 Arm Limited
|
||||||
|
"""Report the age, change history, and upstream status of tracked patches."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import datetime as dt
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def git(root, *args):
|
||||||
|
result = subprocess.run(['git', '-C', str(root), *args], capture_output=True)
|
||||||
|
if result.returncode:
|
||||||
|
raise RuntimeError(result.stderr.decode('utf-8', 'replace').strip())
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def metadata(root, path):
|
||||||
|
raw = git(root, 'log', '--follow', '--format=%as', '--', path)
|
||||||
|
dates = [x for x in raw.decode().splitlines() if x]
|
||||||
|
if not dates:
|
||||||
|
return '', '', 0
|
||||||
|
return dates[-1], dates[0], len(dates)
|
||||||
|
|
||||||
|
|
||||||
|
def upstream_status(path):
|
||||||
|
try:
|
||||||
|
content = path.read_text(encoding='utf-8', errors='replace')
|
||||||
|
except OSError as exc:
|
||||||
|
print(f'warning: cannot read {path}: {exc}', file=sys.stderr)
|
||||||
|
return 'Missing', ''
|
||||||
|
match = re.search(r'^\s*Upstream-Status:\s*(.*?)\s*$', content, re.I | re.M)
|
||||||
|
if not match:
|
||||||
|
return 'Missing', ''
|
||||||
|
full = match.group(1)
|
||||||
|
token = re.match(r'([^\s\[:]+)', full)
|
||||||
|
return (token.group(1) if token else 'Unknown'), full
|
||||||
|
|
||||||
|
|
||||||
|
def layer_of(path):
|
||||||
|
return next((p for p in path.parts if p.startswith('meta-')), '(root)')
|
||||||
|
|
||||||
|
|
||||||
|
def component_of(path):
|
||||||
|
# For standard recipes-*/<component>/files/foo.patch, choose <component>.
|
||||||
|
parts = path.parts
|
||||||
|
for i, part in enumerate(parts[:-1]):
|
||||||
|
if part.startswith('recipes-') and i + 1 < len(parts) - 1:
|
||||||
|
return parts[i + 1]
|
||||||
|
return path.parent.name
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Report Git patch age and upstreaming status from a meta-arm checkout.',
|
||||||
|
epilog='''Examples:
|
||||||
|
scripts/patch-history.py
|
||||||
|
scripts/patch-history.py --verbose --sort age
|
||||||
|
scripts/patch-history.py --verbose --sort status
|
||||||
|
scripts/patch-history.py --layer meta-arm-bsp
|
||||||
|
scripts/patch-history.py --output bsp-patches.csv
|
||||||
|
|
||||||
|
Only Git-tracked .patch and .diff files are scanned. Git rename history is
|
||||||
|
followed where possible. Ages are calculated from the oldest and newest
|
||||||
|
commits touching each file, not from patch header dates. By default, a
|
||||||
|
summary is printed to the terminal. --verbose also lists each patch and
|
||||||
|
its age. --output writes CSV for spreadsheet use; otherwise no file is
|
||||||
|
created. No files in the tree are changed.''',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
)
|
||||||
|
parser.add_argument('--layer', help='Restrict scanning to this layer/directory relative to repository root')
|
||||||
|
parser.add_argument('--output', '-o', help='Write CSV to this path (no CSV is written by default)')
|
||||||
|
parser.add_argument('--sort', choices=['path', 'age', 'updated', 'component', 'status'], default='path',
|
||||||
|
help='Patch listing and CSV row order (status: grouped alphabetically, oldest first within each status; age and updated: oldest first)')
|
||||||
|
parser.add_argument('--summary', action='store_true', help='Print aggregate statistics (already the default; retained for compatibility)')
|
||||||
|
parser.add_argument('--verbose', '-v', action='store_true', help='List every patch with its age and upstream status in the terminal')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
root = Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], stderr=subprocess.PIPE,
|
||||||
|
text=True).strip()).resolve()
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
parser.error('run this from inside a Git repository')
|
||||||
|
if args.layer:
|
||||||
|
target = (root / args.layer).resolve()
|
||||||
|
if not target.is_relative_to(root) or not target.is_dir():
|
||||||
|
parser.error('--layer must name an existing directory inside the repository')
|
||||||
|
scope = target.relative_to(root).as_posix()
|
||||||
|
else:
|
||||||
|
scope = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
files = git(root, 'ls-files', '-z', '--', *( [scope] if scope else [] )).split(b'\0')
|
||||||
|
except RuntimeError as exc:
|
||||||
|
parser.error(str(exc))
|
||||||
|
today = dt.date.today()
|
||||||
|
rows = []
|
||||||
|
for encoded in files:
|
||||||
|
if not encoded:
|
||||||
|
continue
|
||||||
|
relative = encoded.decode('utf-8', 'surrogateescape')
|
||||||
|
if Path(relative).suffix.lower() not in ('.patch', '.diff'):
|
||||||
|
continue
|
||||||
|
absolute = root / relative
|
||||||
|
if not absolute.is_file():
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
added, updated, changes = metadata(root, relative)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
print(f'warning: history unavailable for {relative}: {exc}', file=sys.stderr)
|
||||||
|
added, updated, changes = '', '', 0
|
||||||
|
status, detail = upstream_status(absolute)
|
||||||
|
rows.append({
|
||||||
|
'Patch Added': added,
|
||||||
|
'Last Updated': updated,
|
||||||
|
'Times Changed': changes,
|
||||||
|
'Component': component_of(Path(relative)),
|
||||||
|
'Upstream Status': status,
|
||||||
|
'Patch Age (Days)': (today - dt.date.fromisoformat(added)).days if added else '',
|
||||||
|
'Days Since Update': (today - dt.date.fromisoformat(updated)).days if updated else '',
|
||||||
|
'Layer': layer_of(Path(relative)),
|
||||||
|
'Patch File': relative,
|
||||||
|
'Upstream Status Detail': detail,
|
||||||
|
})
|
||||||
|
keys = {
|
||||||
|
'path': lambda r: r['Patch File'],
|
||||||
|
'age': lambda r: (-(r['Patch Age (Days)'] if isinstance(r['Patch Age (Days)'], int) else -1), r['Patch File']),
|
||||||
|
'updated': lambda r: (-(r['Days Since Update'] if isinstance(r['Days Since Update'], int) else -1), r['Patch File']),
|
||||||
|
'component': lambda r: (r['Component'], r['Patch File']),
|
||||||
|
'status': lambda r: (r['Upstream Status'].lower(), -(r['Patch Age (Days)'] if isinstance(r['Patch Age (Days)'], int) else -1), r['Patch File']),
|
||||||
|
}
|
||||||
|
rows.sort(key=keys[args.sort])
|
||||||
|
if args.output:
|
||||||
|
output = Path(args.output)
|
||||||
|
fields = ['Patch Added', 'Last Updated', 'Times Changed', 'Component', 'Upstream Status',
|
||||||
|
'Patch Age (Days)', 'Days Since Update', 'Layer', 'Patch File', 'Upstream Status Detail']
|
||||||
|
try:
|
||||||
|
with output.open('w', newline='', encoding='utf-8') as stream:
|
||||||
|
writer = csv.DictWriter(stream, fieldnames=fields)
|
||||||
|
writer.writeheader()
|
||||||
|
writer.writerows(rows)
|
||||||
|
except OSError as exc:
|
||||||
|
parser.exit(1, f'error: cannot write {output}: {exc}\n')
|
||||||
|
print(f'Wrote {len(rows)} patches to {output}')
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
print('\nPatch Details (age in days)')
|
||||||
|
print('===========================')
|
||||||
|
for row in rows:
|
||||||
|
age = row['Patch Age (Days)']
|
||||||
|
print(f"{str(age) if age != '' else 'n/a':>7} {row['Upstream Status']:<16} {row['Patch File']}")
|
||||||
|
|
||||||
|
if True: # Summary is the default terminal output.
|
||||||
|
ages = [r['Patch Age (Days)'] for r in rows if isinstance(r['Patch Age (Days)'], int)]
|
||||||
|
print('\nPatch Maintenance Summary')
|
||||||
|
print('=========================')
|
||||||
|
print(f'Total patches: {len(rows)}')
|
||||||
|
if ages:
|
||||||
|
print(f'Average patch age: {sum(ages) / len(ages):.0f} days')
|
||||||
|
print(f'Oldest patch: {max(ages)} days')
|
||||||
|
print(f'Patches older than 1 year: {sum(a > 365 for a in ages)}')
|
||||||
|
print(f'Patches older than 2 years: {sum(a > 730 for a in ages)}')
|
||||||
|
counts = Counter(r['Upstream Status'] for r in rows)
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for r in rows:
|
||||||
|
if isinstance(r['Patch Age (Days)'], int):
|
||||||
|
groups[r['Upstream Status']].append(r['Patch Age (Days)'])
|
||||||
|
print('\nUpstream Status (count, average age in days):')
|
||||||
|
for status in sorted(counts, key=str.lower):
|
||||||
|
ages_for_status = groups[status]
|
||||||
|
average = f'{sum(ages_for_status) / len(ages_for_status):.0f}' if ages_for_status else 'n/a'
|
||||||
|
print(f' {status:<20} {counts[status]:>5} {average:>6}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-FileCopyrightText: 2026 Arm Limited
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""Offline unit and integration tests for meta-arm-release.py."""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
SCRIPT = Path(__file__).with_name('meta-arm-release.py')
|
||||||
|
spec = importlib.util.spec_from_file_location('meta_arm_release', SCRIPT)
|
||||||
|
release = importlib.util.module_from_spec(spec)
|
||||||
|
import sys
|
||||||
|
sys.modules[spec.name] = release
|
||||||
|
spec.loader.exec_module(release)
|
||||||
|
|
||||||
|
|
||||||
|
class VersionTests(unittest.TestCase):
|
||||||
|
def test_initial_release_sequence(self):
|
||||||
|
self.assertEqual(release.previous_series('6.0'), '5.3')
|
||||||
|
self.assertEqual(release.previous_series('6.1'), '6.0')
|
||||||
|
self.assertEqual(release.series_from_number(release.series_number('5.3') + 1), '6.0')
|
||||||
|
|
||||||
|
def test_patch_version(self):
|
||||||
|
self.assertEqual(release.next_patch('5.0'), '5.0.1')
|
||||||
|
self.assertEqual(release.next_patch('5.0.4'), '5.0.5')
|
||||||
|
|
||||||
|
def test_bad_version(self):
|
||||||
|
with self.assertRaises(release.ReleaseError):
|
||||||
|
release.series_number('6.4')
|
||||||
|
|
||||||
|
|
||||||
|
class GitFixture(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.temp = tempfile.TemporaryDirectory()
|
||||||
|
self.addCleanup(self.temp.cleanup)
|
||||||
|
self.repo = Path(self.temp.name)
|
||||||
|
self.previous = Path.cwd()
|
||||||
|
os.chdir(self.repo)
|
||||||
|
self.addCleanup(os.chdir, self.previous)
|
||||||
|
self.git('init', '-q', '-b', 'master')
|
||||||
|
self.git('config', 'user.name', 'Test Maintainer')
|
||||||
|
self.git('config', 'user.email', 'test@example.com')
|
||||||
|
self.commit('initial')
|
||||||
|
self.remote = self.repo / 'upstream.git'
|
||||||
|
subprocess.check_call(['git', 'init', '-q', '--bare', str(self.remote)])
|
||||||
|
self.git('remote', 'add', 'yoctoproject', str(self.remote))
|
||||||
|
self.git('update-ref', 'refs/remotes/yoctoproject/master', self.git('rev-parse', 'master'))
|
||||||
|
|
||||||
|
def git(self, *args):
|
||||||
|
return subprocess.check_output(['git', *args], text=True).strip()
|
||||||
|
|
||||||
|
def commit(self, subject):
|
||||||
|
with (self.repo / 'history.txt').open('a') as stream:
|
||||||
|
stream.write(subject + '\n')
|
||||||
|
self.git('add', 'history.txt')
|
||||||
|
self.git('commit', '-q', '-m', subject)
|
||||||
|
|
||||||
|
def branch(self, name):
|
||||||
|
self.git('branch', name)
|
||||||
|
self.git('update-ref', f'refs/remotes/yoctoproject/{name}', self.git('rev-parse', name))
|
||||||
|
|
||||||
|
def refresh(self, name):
|
||||||
|
self.git('update-ref', f'refs/remotes/yoctoproject/{name}', self.git('rev-parse', name))
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseIntegrationTests(GitFixture):
|
||||||
|
def test_existing_tag_and_update(self):
|
||||||
|
self.branch('scarthgap')
|
||||||
|
self.git('checkout', '-q', 'scarthgap')
|
||||||
|
self.git('tag', '-a', '5.0', '-m', 'initial')
|
||||||
|
self.git('tag', '-a', 'yocto-5.0', '-m', 'initial')
|
||||||
|
self.commit('fix one')
|
||||||
|
self.commit('fix two')
|
||||||
|
self.refresh('scarthgap')
|
||||||
|
self.git('checkout', '-q', 'master')
|
||||||
|
manager = release.ReleaseManager()
|
||||||
|
state = manager.status('scarthgap')
|
||||||
|
self.assertEqual((state.series, state.last_tag, state.commits, state.next_tag),
|
||||||
|
('5.0', '5.0', 2, '5.0.1'))
|
||||||
|
|
||||||
|
def test_no_updates(self):
|
||||||
|
self.branch('scarthgap')
|
||||||
|
self.git('tag', '-a', '5.0', '-m', 'initial')
|
||||||
|
self.git('tag', '-a', 'yocto-5.0', '-m', 'initial')
|
||||||
|
state = release.ReleaseManager().status('scarthgap')
|
||||||
|
self.assertEqual(state.commits, 0)
|
||||||
|
self.assertEqual(state.next_tag, '-')
|
||||||
|
|
||||||
|
def test_announcement_initial_count(self):
|
||||||
|
self.branch('wrynose')
|
||||||
|
self.git('tag', '-a', '6.0', '-m', 'initial')
|
||||||
|
self.git('tag', '-a', 'yocto-6.0', '-m', 'initial')
|
||||||
|
self.commit('master change')
|
||||||
|
self.branch('blacksail')
|
||||||
|
self.git('tag', '-a', '6.1', '-m', 'initial')
|
||||||
|
self.git('tag', '-a', 'yocto-6.1', '-m', 'initial')
|
||||||
|
manager = release.ReleaseManager()
|
||||||
|
subject, body = manager.announcement('blacksail', '6.1')
|
||||||
|
self.assertEqual(subject, 'meta-arm blacksail release')
|
||||||
|
self.assertIn('1 commits since the previous Yocto Project release, 6.0', body)
|
||||||
|
self.assertIn('The meta-arm team', body)
|
||||||
|
self.assertNotIn('Changes included', body)
|
||||||
|
|
||||||
|
def test_update_announcement_lists_changes(self):
|
||||||
|
self.branch('scarthgap')
|
||||||
|
self.git('checkout', '-q', 'scarthgap')
|
||||||
|
self.git('tag', '-a', '5.0', '-m', 'initial')
|
||||||
|
self.git('tag', '-a', 'yocto-5.0', '-m', 'initial')
|
||||||
|
self.commit('fix one')
|
||||||
|
self.git('tag', '-a', '5.0.1', '-m', 'update')
|
||||||
|
self.git('tag', '-a', 'yocto-5.0.1', '-m', 'update')
|
||||||
|
self.refresh('scarthgap')
|
||||||
|
manager = release.ReleaseManager()
|
||||||
|
subject, body = manager.announcement('scarthgap', '5.0.1')
|
||||||
|
self.assertIn('fix one', body)
|
||||||
|
self.assertIn('1 commits', body)
|
||||||
|
self.assertIn('5.0.1 and yocto-5.0.1 tags', body)
|
||||||
|
|
||||||
|
def test_push_dry_run_does_not_modify_remote(self):
|
||||||
|
self.branch('scarthgap')
|
||||||
|
self.git('tag', '-a', '5.0', '-m', 'initial')
|
||||||
|
self.git('tag', '-a', 'yocto-5.0', '-m', 'initial')
|
||||||
|
manager = release.ReleaseManager(execute=False)
|
||||||
|
with patch('builtins.print') as printer:
|
||||||
|
manager.push(['scarthgap'])
|
||||||
|
self.assertTrue(any('New tags: 2' in str(call) for call in printer.call_args_list))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MailerTests(unittest.TestCase):
|
||||||
|
def test_auto_prefers_sendmail(self):
|
||||||
|
with patch.object(release.shutil, 'which', side_effect=lambda x: {'sendmail': '/usr/bin/sendmail', 'mutt': '/usr/bin/mutt'}.get(x)):
|
||||||
|
self.assertEqual(release.ReleaseManager(mailer='auto').mail_backend(), '/usr/bin/sendmail')
|
||||||
|
|
||||||
|
def test_force_mutt(self):
|
||||||
|
with patch.object(release.shutil, 'which', side_effect=lambda x: {'sendmail': '/usr/bin/sendmail', 'mutt': '/usr/bin/mutt'}.get(x)):
|
||||||
|
self.assertEqual(release.ReleaseManager(mailer='mutt').mail_backend(), '/usr/bin/mutt')
|
||||||
|
|
||||||
|
def test_force_sendmail_unavailable_does_not_fallback(self):
|
||||||
|
with patch.object(release.shutil, 'which', side_effect=lambda x: {'mutt': '/usr/bin/mutt'}.get(x)), patch.object(release.os, 'access', return_value=False):
|
||||||
|
manager = release.ReleaseManager(mailer='sendmail')
|
||||||
|
self.assertIsNone(manager.mail_backend())
|
||||||
|
with self.assertRaisesRegex(release.ReleaseError, 'sendmail'):
|
||||||
|
manager.send('subject', 'body')
|
||||||
|
|
||||||
|
def test_cli_override_env(self):
|
||||||
|
with patch.dict(os.environ, {'META_ARM_MAILER': 'mutt'}):
|
||||||
|
self.assertEqual(release.parser().parse_args(['--mailer', 'sendmail', 'check']).mailer, 'sendmail')
|
||||||
|
self.assertEqual(release.parser().parse_args(['check']).mailer, 'mutt')
|
||||||
|
|
||||||
|
def test_invalid_mailer_rejected(self):
|
||||||
|
with self.assertRaises(release.ReleaseError):
|
||||||
|
release.ReleaseManager(mailer='invalid')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(verbosity=2)
|
||||||
|
|
||||||
|
|
||||||
|
class EditorTests(unittest.TestCase):
|
||||||
|
def test_editor_inherits_terminal_streams(self):
|
||||||
|
manager = release.ReleaseManager(edit=True)
|
||||||
|
with patch.object(manager, 'editor_command', return_value='vim -c "set spell"'):
|
||||||
|
with patch.object(release.subprocess, 'run') as invoke:
|
||||||
|
result = manager.edit_message('Subject', 'Body')
|
||||||
|
self.assertIsNone(result)
|
||||||
|
invoke.assert_called_once()
|
||||||
|
args, kwargs = invoke.call_args
|
||||||
|
self.assertEqual(args[0][:3], ['sh', '-c', 'vim -c "set spell" "$1"'])
|
||||||
|
self.assertTrue(kwargs['check'])
|
||||||
|
self.assertNotIn('stdout', kwargs)
|
||||||
|
self.assertNotIn('stderr', kwargs)
|
||||||
|
self.assertNotIn('stdin', kwargs)
|
||||||
|
|
||||||
|
def test_editor_failure_prevents_send(self):
|
||||||
|
manager = release.ReleaseManager(edit=True)
|
||||||
|
with patch.object(manager, 'editor_command', return_value='vim'):
|
||||||
|
with patch.object(release.subprocess, 'run', side_effect=subprocess.CalledProcessError(1, 'vim')):
|
||||||
|
with self.assertRaisesRegex(release.ReleaseError, 'Editor exited unsuccessfully'):
|
||||||
|
manager.edit_message('Subject', 'Body')
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformReleaseTests(GitFixture):
|
||||||
|
def test_platform_tag_name_and_date(self):
|
||||||
|
self.assertEqual(release.platform_tag('corstone1000', '2026.10.01'),
|
||||||
|
'CORSTONE1000-2026.10.01')
|
||||||
|
with self.assertRaises(release.ReleaseError):
|
||||||
|
release.platform_tag('corstone1000', '2026.02.30')
|
||||||
|
|
||||||
|
def test_platform_dry_run_does_not_create_tag(self):
|
||||||
|
manager = release.ReleaseManager()
|
||||||
|
tag = manager.platform('corstone1000', 'HEAD', '2026.10.01')
|
||||||
|
self.assertEqual(tag, 'CORSTONE1000-2026.10.01')
|
||||||
|
self.assertIsNone(manager.tag_object(tag))
|
||||||
|
|
||||||
|
def test_platform_existing_tag_is_idempotent(self):
|
||||||
|
self.git('tag', '-a', 'CORSTONE1000-2026.10.01', '-m', 'release')
|
||||||
|
manager = release.ReleaseManager(execute=True)
|
||||||
|
self.assertEqual(manager.platform('corstone1000', 'HEAD', '2026.10.01'),
|
||||||
|
'CORSTONE1000-2026.10.01')
|
||||||
|
|
||||||
|
def test_push_explicit_platform_tag(self):
|
||||||
|
self.git('tag', '-a', 'CORSTONE1000-2026.10.01', '-m', 'release')
|
||||||
|
manager = release.ReleaseManager(execute=True)
|
||||||
|
manager.push(('CORSTONE1000-2026.10.01',))
|
||||||
|
remote = self.git('ls-remote', '--tags', '--refs', 'yoctoproject',
|
||||||
|
'refs/tags/CORSTONE1000-2026.10.01')
|
||||||
|
self.assertIn('refs/tags/CORSTONE1000-2026.10.01', remote)
|
||||||
|
|
||||||
|
def test_push_auto_discovers_platform_tag(self):
|
||||||
|
self.git('tag', '-a', 'CORSTONE1000-2026.10.01', '-m', 'release')
|
||||||
|
manager = release.ReleaseManager(execute=True)
|
||||||
|
manager.push()
|
||||||
|
remote = self.git('ls-remote', '--tags', '--refs', 'yoctoproject',
|
||||||
|
'refs/tags/CORSTONE1000-2026.10.01')
|
||||||
|
self.assertIn('refs/tags/CORSTONE1000-2026.10.01', remote)
|
||||||
Reference in new issue
Block a user