mirror of
https://git.yoctoproject.org/meta-arm
synced 2026-11-14 02:47:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a19f2dba81 |
No files matched your search
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
image: ${MIRROR_GHCR}/siemens/kas/kas:5.5
|
image: ${MIRROR_GHCR}/siemens/kas/kas:5.2
|
||||||
|
|
||||||
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,7 +284,6 @@ 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
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# 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: "0102"
|
version: "0101"
|
||||||
version_label: "2026.11"
|
version_label: "2026.02"
|
||||||
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: 0102-01
|
- revision: 0100-01
|
||||||
date: 2026-11-09
|
date: 2026-05-01
|
||||||
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.1.html'
|
value: 'https://docs.yoctoproject.org/next/migration-guides/migration-6.0.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,73 +2,6 @@
|
|||||||
|
|
||||||
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,15 +7,6 @@ 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.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).
|
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).
|
||||||
|
|
||||||
## 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 with Cortex-A320 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 are performed on the FVP in the later section.
|
It will be used when the SystemReady-IR tests is 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 with Cortex-A320:
|
To build and run ACS tests on Corstone-1000:
|
||||||
|
|
||||||
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 with Cortex-A320:
|
|||||||
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 with Cortex-A320.
|
This prebuilt ACS image includes v5.13 kernel, which does not provide USB driver support for Corstone-1000.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
3. Decompress the pre-built ACS live image.
|
3. Decompress the pre-built ACS live image.
|
||||||
@@ -172,15 +172,14 @@ 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 with Cortex-A320 supports UEFI.
|
as the Corstone-1000 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 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 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 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,
|
- 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.
|
||||||
after which the system boots normally and reaches the Linux command prompt.
|
- 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.
|
||||||
- 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.
|
||||||
|
|
||||||
@@ -384,7 +383,7 @@ To run the test:
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Run the following commands in order to run the Corstone-1000 with Cortex-A320 Linux kernel.
|
6. Run the following commands in order to run the Corstone-1000 Linux kernel.
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
Otherwise, the execution ends up in the ACS live image.
|
Otherwise, the execution ends up in the ACS live image.
|
||||||
@@ -421,7 +420,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 with Cortex-A320 Linux kernel.
|
9. Run the following commands to boot the Corstone-1000 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.
|
||||||
@@ -525,7 +524,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 with Cortex-A320 Linux kernel, otherwise, the execution ends up in the ACS live image.
|
8. Run the following commands in order to run the Corstone-1000 Linux kernel, otherwise, the execution ends up in the ACS live image.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ unzip $kernel_addr 0x90000000
|
$ unzip $kernel_addr 0x90000000
|
||||||
@@ -546,7 +545,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 with Cortex-A320 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 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
|
||||||
@@ -622,28 +621,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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
@@ -655,28 +654,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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
@@ -688,28 +687,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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- 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: 1
|
- FW type: 0
|
||||||
- FW version: 6
|
- FW version: 6
|
||||||
- Lowest supported version: 0
|
- Lowest supported version: 0
|
||||||
- Capsule flags: 0
|
- Capsule flags: 0
|
||||||
@@ -720,14 +719,14 @@ See the [UEFI documentation](https://uefi.org/specs/UEFI/2.10/23_Firmware_Update
|
|||||||
|
|
||||||
## Linux distributions {.reference}
|
## Linux distributions {.reference}
|
||||||
|
|
||||||
This section describes the steps to install major Linux distributions to the Corstone-1000 with Cortex-A320 Host Processor.
|
This sections 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:
|
||||||
|
|
||||||
- [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 with Cortex-A320 software stack.
|
Follow the instructions below to install the Linux distributions to the Corstone-1000 software stack.
|
||||||
|
|
||||||
### Prepare installation media {.reference}
|
### Prepare installation media {.reference}
|
||||||
|
|
||||||
@@ -742,7 +741,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 operating system.
|
2. Create the installation media which will contain the necessary files to install the operation 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.
|
||||||
@@ -750,9 +749,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 with Cortex-A320 on-board non-volatile storage size is insufficient for installing the distributions.
|
Corstone-1000 on-board non-volatile storage size is insufficient for installing the distributions.
|
||||||
|
|
||||||
1. Create a 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
1. Create an 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 \
|
||||||
@@ -915,7 +914,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.11
|
-b CORSTONE1000-2026.05
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
||||||
@@ -928,7 +927,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 a320-fvp \
|
-d fvp \
|
||||||
-v ${CERTIFICATE_VALIDITY_DURATION_IN_DAYS}
|
-v ${CERTIFICATE_VALIDITY_DURATION_IN_DAYS}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -979,7 +978,7 @@ To run an unsigned image boot test:
|
|||||||
|
|
||||||
```
|
```
|
||||||
corstone1000# \
|
corstone1000# \
|
||||||
load mmc 1:1 $loadaddr corstone1000_secureboot_a320-fvp_images/Image_a320-fvp; \
|
load mmc 1:1 $loadaddr corstone1000_secureboot_fvp_images/Image_fvp; \
|
||||||
loadm $loadaddr $kernel_addr_r $filesize; \
|
loadm $loadaddr $kernel_addr_r $filesize; \
|
||||||
bootefi $kernel_addr_r $fdtcontroladdr
|
bootefi $kernel_addr_r $fdtcontroladdr
|
||||||
|
|
||||||
@@ -998,7 +997,7 @@ Load the signed kernel image.
|
|||||||
|
|
||||||
```
|
```
|
||||||
corstone1000# \
|
corstone1000# \
|
||||||
load mmc 1:1 $loadaddr corstone1000_secureboot_a320-fvp_images/Image_a320-fvp.signed; \
|
load mmc 1:1 $loadaddr corstone1000_secureboot_fvp_images/Image_fvp.signed; \
|
||||||
loadm $loadaddr $kernel_addr_r $filesize; \
|
loadm $loadaddr $kernel_addr_r $filesize; \
|
||||||
bootefi $kernel_addr_r $fdtcontroladdr
|
bootefi $kernel_addr_r $fdtcontroladdr
|
||||||
```
|
```
|
||||||
@@ -1037,7 +1036,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 with Cortex-A320 FVP and wait until it boots to Linux.
|
1. Start the Corstone-1000 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:
|
||||||
|
|
||||||
@@ -1059,7 +1058,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 with Cortex-A320 FVP.
|
2. Run the Corstone-1000 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:\
|
||||||
@@ -1083,7 +1082,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.11
|
-b CORSTONE1000-2026.05
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Copy the additional kas configuration file to:
|
2. Copy the additional kas configuration file to:
|
||||||
@@ -1108,14 +1107,14 @@ To build on Ethos-U85 NPU:
|
|||||||
cd ${WORKSPACE}
|
cd ${WORKSPACE}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Re-build the Corstone-1000 with Cortex-A320 FVP software stack as follows:
|
5. Re-build the Corstone-1000 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 with Cortex-A320 FVP:
|
6. Run the Corstone-1000 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:\
|
||||||
@@ -1123,7 +1122,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 with Cortex-A320 FVP, build and run the FVP and inspect the CPU model
|
7. To verify you are running the Corstone-1000 FVP (Cortex-A320), 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 [Blacksail 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 [Wrynose 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.1.bb` |
|
| Recipe | `${WORKSPACE}/meta-arm/meta-arm/recipes-bsp/trusted-firmware-m/trusted-firmware-m_2.3.0.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.11
|
git clone https://git.yoctoproject.org/git/meta-arm -b CORSTONE1000-2026.05
|
||||||
```
|
```
|
||||||
|
|
||||||
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,68 +10,6 @@ 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,12 +19,6 @@ 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 `Blacksail release <meta-arm-repository-release-branch_>`__.
|
Corstone-1000 software stack is built on top of Yocto Project's `Wrynose 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.1.bb`` |
|
| Recipe | ``${WORKSPACE}/meta-arm/meta-arm/recipes-bsp/trusted-firmware-m/trusted-firmware-m_2.3.0.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.11
|
git clone https://git.yoctoproject.org/git/meta-arm -b CORSTONE1000-2026.05
|
||||||
|
|
||||||
#. 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 r2p0-00rel0``
|
#. Download the FPGA bit file image ``AN550: Arm® Corstone™-1000 for MPS3 Version 2.0``
|
||||||
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.11) <https://git.yoctoproject.org/meta-arm/tag/?h=CORSTONE1000-2026.11>`__
|
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 `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>`__.
|
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>`__.
|
||||||
|
|
||||||
|
|
||||||
.. _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.11
|
git clone https://git.gitlab.arm.com/arm-reference-solutions/iot-platform-assets.git -b CORSTONE1000-2026.05
|
||||||
|
|
||||||
#. 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 are performed on the FVP in the later section.
|
It will be used when the SystemReady-IR tests is 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 section below describe how to build and run ACS tests on Corstone-1000.
|
This sections 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 10-15 minutes on FVP.
|
The capsule update takes about 8 minutes to complete on MPS3 and between 15-30 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}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${BL2_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${TFM_S_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${TFM_S_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${FIP_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${FIP_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${INITRAMFS_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${INITRAMFS_GUID}`` | 0 | 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}`` | 1 | 7 | 0 | 7 | 0 |
|
| 0 | ``${BL2_GUID}`` | 0 | 7 | 0 | 7 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${TFM_S_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${TFM_S_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${FIP_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${FIP_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+-----------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${INITRAMFS_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${INITRAMFS_GUID}`` | 0 | 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}`` | 1 | 7 | 1 | 5 | 0 |
|
| 0 | ``${BL2_GUID}`` | 0 | 7 | 1 | 5 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${TFM_S_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${TFM_S_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${FIP_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${FIP_GUID}`` | 0 | 6 | 0 | 6 | 0 |
|
||||||
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
+-------------------+------------------------+-------------+----------------+-------------------------+--------------------------+-----------------------------+
|
||||||
| 0 | ``${INITRAMFS_GUID}`` | 1 | 6 | 0 | 6 | 0 |
|
| 0 | ``${INITRAMFS_GUID}`` | 0 | 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 section describes the steps to install major Linux distributions to the Corstone-1000 Host Processor.
|
This sections 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 operating system.
|
#. Create the installation media which will contain the necessary files to install the operation 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 a 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
#. Create an 10 GB GUID Partition Table (GPT) formatted MultiMediaCard (MMC) image.
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
@@ -1674,13 +1674,6 @@ 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.
|
||||||
@@ -1795,7 +1788,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.11
|
-b CORSTONE1000-2026.05
|
||||||
|
|
||||||
#. 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.
|
||||||
|
|
||||||
@@ -2179,12 +2172,10 @@ 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 ``secure_debug_manager`` element immediately below the existing
|
add the following element alongside the existing ``dtsl_config`` parameter:
|
||||||
``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
|
||||||
@@ -2202,8 +2193,7 @@ 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, rename the platform to a unique title in the
|
In the same file, give the copied platform a unique title:
|
||||||
``<extension name="ds">`` extension block:
|
|
||||||
|
|
||||||
.. code-block:: xml
|
.. code-block:: xml
|
||||||
|
|
||||||
@@ -2311,7 +2301,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.1.html
|
.. _meta-arm-repository-release-branch: https://docs.yoctoproject.org/next/migration-guides/migration-6.0.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
|
||||||
|
|||||||
+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-x86_64.sha256sum] = "1e4dc409cf4d33ef0a2e92c345f9e340302d355858de9bc3353758b6463895b0"
|
SRC_URI[gcc-aarch64.sha256sum] = "a1c6fdda8b479ea3e235d38dc0994790b840648b60e9fbaf88c82ca117a7a2df"
|
||||||
SRC_URI[gcc-aarch64.sha256sum] = "63942f3f86e69d7beefa2b946bdb23437628529e7b29f97d32229f65d0905e40"
|
SRC_URI[gcc-x86_64.sha256sum] = "1b07847728d455f18895f1ebd5d71a40f2ccb7cb3a84ca9a874d7f961a318ce4"
|
||||||
|
|
||||||
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] = "a0dc18893171d39dec82fd8d57eeab3da836fd3df4b61978db3eccbf4a6bb948"
|
SRC_URI[gcc-aarch64.sha256sum] = "06979e0c8171de58e5dc2a2b2019330a290f30930f27728af98a83e1a7369b3a"
|
||||||
SRC_URI[gcc-x86_64.sha256sum] = "463df9665c246fa13bab2102115bbf83440661b6eb70b44f3dfd4712ca30037a"
|
SRC_URI[gcc-x86_64.sha256sum] = "563bebb2b97d53382b956d6ee1fe61e2cae26699901417234a37df505ef9b5fa"
|
||||||
|
|
||||||
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 = "676ab03c6a3d78f10ddc0f8b96c02475da24392f"
|
SRCREV = "ce12c6e53838f1cf07d50b616b72db57a81539a4"
|
||||||
B = "${WORKDIR}/build"
|
B = "${WORKDIR}/build"
|
||||||
|
|
||||||
COMPATIBLE_MACHINE ?= "invalid"
|
COMPATIBLE_MACHINE ?= "invalid"
|
||||||
@@ -12,10 +12,6 @@ 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,8 +44,3 @@ 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"
|
|
||||||
+4
-1
@@ -1,4 +1,4 @@
|
|||||||
From 0debd3a1f4b5abf0c75c26ec1e85d9fa33510c89 Mon Sep 17 00:00:00 2001
|
From 7d2f6b6e31fad2c96c6fc55c17e79f8c96b4bfcf 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,3 +36,6 @@ 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
@@ -1,127 +0,0 @@
|
|||||||
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
@@ -1,38 +0,0 @@
|
|||||||
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,8 +4,4 @@ SRCREV_edk2 ?= "2970e5699ba6267f3384ffab20f96647578aebc8"
|
|||||||
SRCREV_edk2-platforms ?= "c98cdf8de9bd84a3267623e64f4388e21de69512"
|
SRCREV_edk2-platforms ?= "c98cdf8de9bd84a3267623e64f4388e21de69512"
|
||||||
|
|
||||||
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
|
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
|
||||||
SRC_URI += " \
|
SRC_URI += "file://0001-DynamicTablesPkg-fix-SmbiosType2Genertor-build-failu.patch"
|
||||||
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 \
|
|
||||||
"
|
|
||||||
+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.11.0
|
# v4.10.0
|
||||||
SRCREV_optee-ta ?= "f09f4cdad21028289d4e8fc573893b75f7228dc7"
|
SRCREV_optee-ta ?= "a09269b15de635e1816fe832e26adfbfb44c5455"
|
||||||
|
|
||||||
SRCREV_FORMAT = "ms-tpm_optee-ta"
|
SRCREV_FORMAT = "ms-tpm_optee-ta"
|
||||||
|
|
||||||
File renamed without changes.
@@ -1,4 +0,0 @@
|
|||||||
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.11.0
|
# v4.10.0
|
||||||
SRCREV = "da81fb7b30781029bc9d4acfddc7c60b08deb1f3"
|
SRCREV = "9f5e90918093c1d1cd264d8149081b64ab7ba672"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -1,4 +0,0 @@
|
|||||||
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.11.0
|
# v4.10.0
|
||||||
SRCREV = "71e8f3f320eef46a9369700bf30b91d67427d535"
|
SRCREV = "934c7edb74a26e90f68024cf441073528444177f"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -1,32 +0,0 @@
|
|||||||
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)}"
|
|
||||||
File renamed without changes.
@@ -1,6 +0,0 @@
|
|||||||
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.11.0
|
# v4.10.0
|
||||||
SRCREV = "f54190f22611dd5ad5d22546fd4209aa100f56bd"
|
SRCREV = "753afbbee1682f5d16fd30e87b31058a4fd4f4b8"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -1,17 +0,0 @@
|
|||||||
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.11.0
|
# v4.10.0
|
||||||
SRCREV = "179740b43809069ee567d58ebd8e0f85ef8168e2"
|
SRCREV = "88c93e87a5c172363ee986ded036a25cafcc9d2c"
|
||||||
PV .= "+git"
|
PV .= "+git"
|
||||||
UPSTREAM_CHECK_COMMITS = "1"
|
UPSTREAM_CHECK_COMMITS = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,279 +0,0 @@
|
|||||||
#!/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())
|
|
||||||
@@ -1,731 +0,0 @@
|
|||||||
#!/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)
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
#!/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()
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
#!/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