From 18bb5bacca9618315c5003dde8da668b869ead53 Mon Sep 17 00:00:00 2001 From: Fu Hanxi Date: Wed, 19 Nov 2025 10:51:20 +0100 Subject: [PATCH] ci: move the `retry_failed_jobs` to pre_check stage --- .gitlab-ci.yml | 1 - .gitlab/ci/common.yml | 1 - .gitlab/ci/pre_check.yml | 12 +++++----- .gitlab/ci/retry_failed_jobs.yml | 15 ------------ tools/ci/dynamic_pipelines/constants.py | 4 ---- tools/ci/dynamic_pipelines/report.py | 22 +++--------------- .../scripts/generate_report.py | 2 +- .../templates/retry-jobs.png | Bin 17552 -> 0 bytes 8 files changed, 10 insertions(+), 47 deletions(-) delete mode 100644 .gitlab/ci/retry_failed_jobs.yml delete mode 100644 tools/ci/dynamic_pipelines/templates/retry-jobs.png diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9c33fe48f6..7c02159d1a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,5 +29,4 @@ include: - ".gitlab/ci/host-test.yml" - ".gitlab/ci/deploy.yml" - ".gitlab/ci/post_deploy.yml" - - ".gitlab/ci/retry_failed_jobs.yml" - ".gitlab/ci/test-win.yml" diff --git a/.gitlab/ci/common.yml b/.gitlab/ci/common.yml index a74c634098..1161176b02 100644 --- a/.gitlab/ci/common.yml +++ b/.gitlab/ci/common.yml @@ -12,7 +12,6 @@ stages: - test_deploy - deploy - post_deploy - - retry_failed_jobs variables: # System environment diff --git a/.gitlab/ci/pre_check.yml b/.gitlab/ci/pre_check.yml index 758bef51a4..49c3920ebd 100644 --- a/.gitlab/ci/pre_check.yml +++ b/.gitlab/ci/pre_check.yml @@ -202,13 +202,13 @@ gcc_static_analyzer: - echo "CONFIG_COMPILER_STATIC_ANALYZER=y" >> ${ANALYZING_APP}/sdkconfig.defaults - idf-build-apps build -p ${ANALYZING_APP} -redundant_pass_job: +retry_failed_jobs: extends: - .pre_check_template + - .rules:dev-push tags: [shiny, fast_run] - cache: [] - variables: - GIT_STRATEGY: none - before_script: [] + allow_failure: true script: - - echo "This job is redundant to ensure the 'retry_failed_jobs' job can exist and not be skipped" + - echo "Retrieving and retrying all failed jobs for the pipeline..." + - python tools/ci/python_packages/gitlab_api.py retry_failed_jobs $CI_MERGE_REQUEST_PROJECT_ID --pipeline_id $CI_PIPELINE_ID + when: manual diff --git a/.gitlab/ci/retry_failed_jobs.yml b/.gitlab/ci/retry_failed_jobs.yml deleted file mode 100644 index 8eff2ed6b4..0000000000 --- a/.gitlab/ci/retry_failed_jobs.yml +++ /dev/null @@ -1,15 +0,0 @@ -retry_failed_jobs: - stage: retry_failed_jobs - tags: [shiny, fast_run] - allow_failure: true - image: $ESP_ENV_IMAGE - dependencies: null - before_script: [] - cache: [] - extends: [] - script: - - echo "Retrieving and retrying all failed jobs for the pipeline..." - - python tools/ci/python_packages/gitlab_api.py retry_failed_jobs $CI_MERGE_REQUEST_PROJECT_ID --pipeline_id $CI_PIPELINE_ID - when: manual - needs: - - redundant_pass_job diff --git a/tools/ci/dynamic_pipelines/constants.py b/tools/ci/dynamic_pipelines/constants.py index a85e9a5793..40dbe346d1 100644 --- a/tools/ci/dynamic_pipelines/constants.py +++ b/tools/ci/dynamic_pipelines/constants.py @@ -15,10 +15,6 @@ TOP_N_APPS_BY_SIZE_DIFF = 10 SIZE_DIFFERENCE_BYTES_THRESHOLD = 500 BINARY_SIZE_METRIC_NAME = 'binary_size' -RETRY_JOB_PICTURE_PATH = 'tools/ci/dynamic_pipelines/templates/retry-jobs.png' -RETRY_JOB_TITLE = '\n\nRetry failed jobs with with help of "retry_failed_jobs" stage of the pipeline:' -RETRY_JOB_PICTURE_LINK = '![Retry Jobs Image]({pic_url})' - KNOWN_GENERATE_TEST_CHILD_PIPELINE_WARNINGS_FILEPATH = os.path.join( IDF_PATH, 'tools', 'ci', 'dynamic_pipelines', 'templates', 'known_generate_test_child_pipeline_warnings.yml' ) diff --git a/tools/ci/dynamic_pipelines/report.py b/tools/ci/dynamic_pipelines/report.py index 1ae1f04778..24b0ed9a03 100644 --- a/tools/ci/dynamic_pipelines/report.py +++ b/tools/ci/dynamic_pipelines/report.py @@ -19,16 +19,12 @@ from .constants import COMMENT_START_MARKER from .constants import CSS_STYLES_FILEPATH from .constants import JS_SCRIPTS_FILEPATH from .constants import REPORT_TEMPLATE_FILEPATH -from .constants import RETRY_JOB_PICTURE_LINK -from .constants import RETRY_JOB_PICTURE_PATH -from .constants import RETRY_JOB_TITLE from .constants import SIZE_DIFFERENCE_BYTES_THRESHOLD from .constants import TOP_N_APPS_BY_SIZE_DIFF from .models import GitlabJob from .models import TestCase from .utils import format_permalink from .utils import get_artifacts_url -from .utils import get_repository_file_url from .utils import is_url @@ -286,25 +282,13 @@ class ReportGenerator: return comment - def _update_mr_comment(self, comment: str, print_retry_jobs_message: bool) -> None: - retry_job_picture_comment = (f'{RETRY_JOB_TITLE}\n\n{RETRY_JOB_PICTURE_LINK}').format( - pic_url=get_repository_file_url(RETRY_JOB_PICTURE_PATH) - ) - del_retry_job_pic_pattern = re.escape(RETRY_JOB_TITLE) + r'.*?' + re.escape(f'{RETRY_JOB_PICTURE_PATH})') - + def _update_mr_comment(self, comment: str) -> None: new_comment = f'{COMMENT_START_MARKER}\n\n{comment}' - if print_retry_jobs_message: - new_comment += retry_job_picture_comment for note in self.mr.notes.list(iterator=True): if note.body.startswith(COMMENT_START_MARKER): updated_str = self._get_updated_comment(note.body, comment) - # Add retry job message only if any job has failed - if print_retry_jobs_message: - updated_str = re.sub(del_retry_job_pic_pattern, '', updated_str, flags=re.DOTALL) - updated_str += retry_job_picture_comment - note.body = updated_str try: note.save() @@ -321,7 +305,7 @@ class ReportGenerator: updated_str = f'{existing_comment.strip()}\n\n{new_comment}' return updated_str - def post_report(self, print_retry_jobs_message: bool = False) -> None: + def post_report(self) -> None: comment = self._generate_comment() print(comment) @@ -330,7 +314,7 @@ class ReportGenerator: print('No MR found, skip posting comment') return - self._update_mr_comment(comment, print_retry_jobs_message=print_retry_jobs_message) + self._update_mr_comment(comment) class BuildReportGenerator(ReportGenerator): diff --git a/tools/ci/dynamic_pipelines/scripts/generate_report.py b/tools/ci/dynamic_pipelines/scripts/generate_report.py index 83829c8558..1f40fff50a 100644 --- a/tools/ci/dynamic_pipelines/scripts/generate_report.py +++ b/tools/ci/dynamic_pipelines/scripts/generate_report.py @@ -110,7 +110,7 @@ def generate_jobs_report(args: argparse.Namespace) -> None: report_generator = JobReportGenerator( args.project_id, args.mr_iid, args.pipeline_id, args.job_id, args.commit_id, args.local_commit_id, jobs=jobs ) - report_generator.post_report(print_retry_jobs_message=any(job.is_failed for job in jobs)) + report_generator.post_report() if GitlabEnvVars().IDF_CI_IS_DEBUG_PIPELINE: print('Debug pipeline detected, exit non-zero to fail the pipeline in order to block merge') diff --git a/tools/ci/dynamic_pipelines/templates/retry-jobs.png b/tools/ci/dynamic_pipelines/templates/retry-jobs.png deleted file mode 100644 index a8a60112c01546ea8c8ff06fc5b8d30f83ed4bcd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17552 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