mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
fba96d58c2
build: - upgrade idf-build-apps to 2.x - unify get_pytest_apps and get_cmake_apps to get_all_apps - returns (test_apps, non_test_apps) tuple - add tests for the new get_all_apps assign: - generate build report - generate target test pipeline based on the build report target test: - download artifacts from minio server - users can use `pytest --pipeline-id xxxxx` to download and flash the binaries from the artifacts .post: - generate target test reports
151 lines
5.6 KiB
Python
151 lines
5.6 KiB
Python
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
import glob
|
|
import os
|
|
import typing as t
|
|
from datetime import timedelta
|
|
from zipfile import ZIP_DEFLATED, ZipFile
|
|
|
|
import minio
|
|
from artifacts_handler import ArtifactType, get_minio_client, getenv
|
|
from idf_build_apps import App
|
|
from idf_build_apps.utils import rmdir
|
|
from idf_ci_utils import IDF_PATH
|
|
from idf_pytest.constants import DEFAULT_BUILD_LOG_FILENAME
|
|
|
|
|
|
class AppUploader:
|
|
TYPE_PATTERNS_DICT = {
|
|
ArtifactType.MAP_AND_ELF_FILES: [
|
|
'bootloader/*.map',
|
|
'bootloader/*.elf',
|
|
'*.map',
|
|
'*.elf',
|
|
],
|
|
ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES: [
|
|
'*.bin',
|
|
'bootloader/*.bin',
|
|
'partition_table/*.bin',
|
|
'flasher_args.json',
|
|
'flash_project_args',
|
|
'config/sdkconfig.json',
|
|
'project_description.json',
|
|
],
|
|
ArtifactType.LOGS: [
|
|
DEFAULT_BUILD_LOG_FILENAME,
|
|
],
|
|
}
|
|
|
|
def __init__(self, pipeline_id: t.Union[str, int, None] = None) -> None:
|
|
self.pipeline_id = str(pipeline_id or '1')
|
|
|
|
self._client = get_minio_client()
|
|
|
|
def get_app_object_name(self, app_path: str, zip_name: str, artifact_type: ArtifactType) -> str:
|
|
return f'{self.pipeline_id}/{artifact_type.value}/{app_path}/{zip_name}'
|
|
|
|
def _upload_app(self, app_build_path: str, artifact_type: ArtifactType) -> bool:
|
|
app_path, build_dir = os.path.split(app_build_path)
|
|
zip_filename = f'{build_dir}.zip'
|
|
|
|
has_file = False
|
|
with ZipFile(
|
|
zip_filename,
|
|
'w',
|
|
compression=ZIP_DEFLATED,
|
|
# 1 is the fastest compression level
|
|
# the size differs not much between 1 and 9
|
|
compresslevel=1,
|
|
) as zw:
|
|
for pattern in self.TYPE_PATTERNS_DICT[artifact_type]:
|
|
for file in glob.glob(os.path.join(app_build_path, pattern), recursive=True):
|
|
zw.write(file)
|
|
has_file = True
|
|
|
|
uploaded = False
|
|
try:
|
|
if has_file:
|
|
obj_name = self.get_app_object_name(app_path, zip_filename, artifact_type)
|
|
print(f'Created archive file: {zip_filename}, uploading as {obj_name}')
|
|
self._client.fput_object(getenv('IDF_S3_BUCKET'), obj_name, zip_filename)
|
|
uploaded = True
|
|
finally:
|
|
os.remove(zip_filename)
|
|
return uploaded
|
|
|
|
def upload_app(self, app_build_path: str, artifact_type: t.Optional[ArtifactType] = None) -> None:
|
|
uploaded = False
|
|
if not artifact_type:
|
|
for _artifact_type in [
|
|
ArtifactType.MAP_AND_ELF_FILES,
|
|
ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES,
|
|
ArtifactType.LOGS,
|
|
]:
|
|
uploaded |= self._upload_app(app_build_path, _artifact_type)
|
|
else:
|
|
uploaded = self._upload_app(app_build_path, artifact_type)
|
|
|
|
if uploaded:
|
|
rmdir(app_build_path, exclude_file_patterns=DEFAULT_BUILD_LOG_FILENAME)
|
|
|
|
def _download_app(self, app_build_path: str, artifact_type: ArtifactType) -> None:
|
|
app_path, build_dir = os.path.split(app_build_path)
|
|
zip_filename = f'{build_dir}.zip'
|
|
|
|
# path are relative to IDF_PATH
|
|
current_dir = os.getcwd()
|
|
os.chdir(IDF_PATH)
|
|
try:
|
|
obj_name = self.get_app_object_name(app_path, zip_filename, artifact_type)
|
|
print(f'Downloading {obj_name}')
|
|
try:
|
|
try:
|
|
self._client.stat_object(getenv('IDF_S3_BUCKET'), obj_name)
|
|
except minio.error.S3Error as e:
|
|
raise SystemExit(
|
|
f'No such file on minio server: {obj_name}. '
|
|
f'Probably the build failed or the artifacts got expired. '
|
|
f'Full error message: {str(e)}'
|
|
)
|
|
else:
|
|
self._client.fget_object(getenv('IDF_S3_BUCKET'), obj_name, zip_filename)
|
|
print(f'Downloaded to {zip_filename}')
|
|
except minio.error.S3Error as e:
|
|
raise SystemExit('Shouldn\'t happen, please report this bug in the CI channel' + str(e))
|
|
|
|
with ZipFile(zip_filename, 'r') as zr:
|
|
zr.extractall()
|
|
|
|
os.remove(zip_filename)
|
|
finally:
|
|
os.chdir(current_dir)
|
|
|
|
def download_app(self, app_build_path: str, artifact_type: t.Optional[ArtifactType] = None) -> None:
|
|
if not artifact_type:
|
|
for _artifact_type in [ArtifactType.MAP_AND_ELF_FILES, ArtifactType.BUILD_DIR_WITHOUT_MAP_AND_ELF_FILES]:
|
|
self._download_app(app_build_path, _artifact_type)
|
|
else:
|
|
self._download_app(app_build_path, artifact_type)
|
|
|
|
def get_app_presigned_url(self, app: App, artifact_type: ArtifactType) -> str:
|
|
obj_name = self.get_app_object_name(app.app_dir, f'{app.build_dir}.zip', artifact_type)
|
|
try:
|
|
self._client.stat_object(
|
|
getenv('IDF_S3_BUCKET'),
|
|
obj_name,
|
|
)
|
|
except minio.error.S3Error:
|
|
return ''
|
|
else:
|
|
return self._client.get_presigned_url( # type: ignore
|
|
'GET', getenv('IDF_S3_BUCKET'), obj_name, expires=timedelta(days=4)
|
|
)
|
|
|
|
|
|
def get_app_uploader() -> t.Optional['AppUploader']:
|
|
if parent_pipeline_id := os.getenv('PARENT_PIPELINE_ID'):
|
|
return AppUploader(parent_pipeline_id)
|
|
|
|
return None
|