mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
ci: lint yaml files that use dependencies: []
together with needs
This commit is contained in:
parent
a5b261f699
commit
fc802da68c
@ -172,6 +172,7 @@ build_clang_test_apps_esp32c6:
|
|||||||
extends:
|
extends:
|
||||||
- .build_template
|
- .build_template
|
||||||
- .rules:build:check
|
- .rules:build:check
|
||||||
|
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||||
needs:
|
needs:
|
||||||
- job: fast_template_app
|
- job: fast_template_app
|
||||||
artifacts: false
|
artifacts: false
|
||||||
@ -272,6 +273,7 @@ build_template_app:
|
|||||||
- .build_template_app_template
|
- .build_template_app_template
|
||||||
- .rules:build
|
- .rules:build
|
||||||
stage: host_test
|
stage: host_test
|
||||||
|
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||||
needs:
|
needs:
|
||||||
- job: fast_template_app
|
- job: fast_template_app
|
||||||
artifacts: false
|
artifacts: false
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
image: $ESP_ENV_IMAGE
|
image: $ESP_ENV_IMAGE
|
||||||
tags:
|
tags:
|
||||||
- host_test
|
- host_test
|
||||||
dependencies: []
|
dependencies: # set dependencies to null to avoid missing artifacts issue
|
||||||
|
|
||||||
check_pre_commit:
|
check_pre_commit:
|
||||||
extends:
|
extends:
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#
|
#
|
||||||
# SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
@ -11,7 +10,8 @@ from collections import defaultdict
|
|||||||
from itertools import product
|
from itertools import product
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from idf_ci_utils import IDF_PATH, GitlabYmlConfig
|
from idf_ci_utils import GitlabYmlConfig
|
||||||
|
from idf_ci_utils import IDF_PATH
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pygraphviz as pgv
|
import pygraphviz as pgv
|
||||||
@ -201,9 +201,13 @@ class RulesWriter:
|
|||||||
def new_rules_str(self): # type: () -> str
|
def new_rules_str(self): # type: () -> str
|
||||||
res = []
|
res = []
|
||||||
for k, v in sorted(self.rules.items()):
|
for k, v in sorted(self.rules.items()):
|
||||||
if '.rules:' + k not in self.yml_config.used_rules:
|
if k.startswith('pattern'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if '.rules:' + k not in self.yml_config.used_templates:
|
||||||
print(f'WARNING: unused rule: {k}, skipping...')
|
print(f'WARNING: unused rule: {k}, skipping...')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
res.append(self.RULES_TEMPLATE.format(k, self._format_rule(k, v)))
|
res.append(self.RULES_TEMPLATE.format(k, self._format_rule(k, v)))
|
||||||
return '\n\n'.join(res)
|
return '\n\n'.join(res)
|
||||||
|
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Check gitlab ci yaml files
|
Check gitlab ci yaml files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import typing as t
|
import typing as t
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
from idf_ci_utils import IDF_PATH, GitlabYmlConfig, get_submodule_dirs
|
from idf_ci_utils import get_submodule_dirs
|
||||||
|
from idf_ci_utils import GitlabYmlConfig
|
||||||
|
from idf_ci_utils import IDF_PATH
|
||||||
|
|
||||||
|
|
||||||
class YmlLinter:
|
class YmlLinter:
|
||||||
@ -50,9 +49,10 @@ class YmlLinter:
|
|||||||
if (
|
if (
|
||||||
k not in self.yml_config.global_keys
|
k not in self.yml_config.global_keys
|
||||||
and k not in self.yml_config.anchors
|
and k not in self.yml_config.anchors
|
||||||
|
and k not in self.yml_config.templates
|
||||||
and k not in self.yml_config.jobs
|
and k not in self.yml_config.jobs
|
||||||
):
|
):
|
||||||
raise SystemExit(f'Parser incorrect. Key {k} not in global keys, rules or jobs')
|
raise SystemExit(f'Parser incorrect. Key {k} not in global keys, anchors, templates, or jobs')
|
||||||
|
|
||||||
def _lint_default_values_artifacts(self) -> None:
|
def _lint_default_values_artifacts(self) -> None:
|
||||||
defaults_artifacts = self.yml_config.default.get('artifacts', {})
|
defaults_artifacts = self.yml_config.default.get('artifacts', {})
|
||||||
@ -79,13 +79,30 @@ class YmlLinter:
|
|||||||
for item in undefined_patterns:
|
for item in undefined_patterns:
|
||||||
self._errors.append(f'undefined pattern {item}. Please add {item} to .patterns-submodule')
|
self._errors.append(f'undefined pattern {item}. Please add {item} to .patterns-submodule')
|
||||||
|
|
||||||
def _lint_gitlab_yml_rules(self) -> None:
|
def _lint_gitlab_yml_templates(self) -> None:
|
||||||
unused_rules = self.yml_config.rules - self.yml_config.used_rules
|
unused_templates = self.yml_config.templates.keys() - self.yml_config.used_templates
|
||||||
for item in unused_rules:
|
for item in unused_templates:
|
||||||
self._errors.append(f'Unused rule: {item}, please remove it')
|
# known unused ones
|
||||||
undefined_rules = self.yml_config.used_rules - self.yml_config.rules
|
if item not in [
|
||||||
for item in undefined_rules:
|
'.before_script:fetch:target_test', # used in dynamic pipeline
|
||||||
self._errors.append(f'Undefined rule: {item}')
|
]:
|
||||||
|
self._errors.append(f'Unused template: {item}, please remove it')
|
||||||
|
|
||||||
|
undefined_templates = self.yml_config.used_templates - self.yml_config.templates.keys()
|
||||||
|
for item in undefined_templates:
|
||||||
|
self._errors.append(f'Undefined template: {item}')
|
||||||
|
|
||||||
|
def _lint_dependencies_and_needs(self) -> None:
|
||||||
|
"""
|
||||||
|
Use `dependencies: []` together with `needs: []` could cause missing artifacts issue.
|
||||||
|
"""
|
||||||
|
for job_name, d in self.yml_config.jobs.items():
|
||||||
|
if 'dependencies' in d and 'needs' in d:
|
||||||
|
if d['dependencies'] is not None and d['needs']:
|
||||||
|
self._errors.append(
|
||||||
|
f'job {job_name} has both `dependencies` and `needs` defined. '
|
||||||
|
f'Please set `dependencies:` (to null) explicitly to avoid missing artifacts issue'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -125,7 +125,9 @@ class GitlabYmlConfig:
|
|||||||
|
|
||||||
all_config = dict()
|
all_config = dict()
|
||||||
root_yml = yaml.load(open(root_yml_filepath), Loader=yaml.FullLoader)
|
root_yml = yaml.load(open(root_yml_filepath), Loader=yaml.FullLoader)
|
||||||
for item in root_yml['include']:
|
|
||||||
|
# expanding "include"
|
||||||
|
for item in root_yml.pop('include', []) or []:
|
||||||
all_config.update(yaml.load(open(os.path.join(IDF_PATH, item)), Loader=yaml.FullLoader))
|
all_config.update(yaml.load(open(os.path.join(IDF_PATH, item)), Loader=yaml.FullLoader))
|
||||||
|
|
||||||
if 'default' in all_config:
|
if 'default' in all_config:
|
||||||
@ -133,6 +135,16 @@ class GitlabYmlConfig:
|
|||||||
|
|
||||||
self._config = all_config
|
self._config = all_config
|
||||||
|
|
||||||
|
# anchor is the string that will be reused in templates
|
||||||
|
self._anchor_keys: t.Set[str] = set()
|
||||||
|
# template is a dict that will be extended
|
||||||
|
self._template_keys: t.Set[str] = set()
|
||||||
|
self._used_template_keys: t.Set[str] = set() # tracing the used templates
|
||||||
|
# job is a dict that will be executed
|
||||||
|
self._job_keys: t.Set[str] = set()
|
||||||
|
|
||||||
|
self.expand_extends()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default(self) -> t.Dict[str, t.Any]:
|
def default(self) -> t.Dict[str, t.Any]:
|
||||||
return self._defaults
|
return self._defaults
|
||||||
@ -147,33 +159,66 @@ class GitlabYmlConfig:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def anchors(self) -> t.Dict[str, t.Any]:
|
def anchors(self) -> t.Dict[str, t.Any]:
|
||||||
return {k: v for k, v in self.config.items() if k.startswith('.')}
|
return {k: v for k, v in self.config.items() if k in self._anchor_keys}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def jobs(self) -> t.Dict[str, t.Any]:
|
def jobs(self) -> t.Dict[str, t.Any]:
|
||||||
return {k: v for k, v in self.config.items() if not k.startswith('.') and k not in self.global_keys}
|
return {k: v for k, v in self.config.items() if k in self._job_keys}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def rules(self) -> t.Set[str]:
|
def templates(self) -> t.Dict[str, t.Any]:
|
||||||
return {k for k, _ in self.anchors.items() if self._is_rule_key(k)}
|
return {k: v for k, v in self.config.items() if k in self._template_keys}
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def used_rules(self) -> t.Set[str]:
|
def used_templates(self) -> t.Set[str]:
|
||||||
res = set()
|
return self._used_template_keys
|
||||||
|
|
||||||
for v in self.config.values():
|
def expand_extends(self) -> None:
|
||||||
if not isinstance(v, dict):
|
"""
|
||||||
|
expand the `extends` key in-place.
|
||||||
|
"""
|
||||||
|
for k, v in self.config.items():
|
||||||
|
if k in self.global_keys:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for item in to_list(v.get('extends')):
|
if isinstance(v, (str, list)):
|
||||||
if self._is_rule_key(item):
|
self._anchor_keys.add(k)
|
||||||
res.add(item)
|
elif k.startswith('.if-'):
|
||||||
|
self._anchor_keys.add(k)
|
||||||
|
elif k.startswith('.'):
|
||||||
|
self._template_keys.add(k)
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
self._job_keys.add(k)
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unknown type for key {k} with value {v}')
|
||||||
|
|
||||||
return res
|
# no need to expand anchor
|
||||||
|
|
||||||
@staticmethod
|
# expand template first
|
||||||
def _is_rule_key(key: str) -> bool:
|
for k in self._template_keys:
|
||||||
return key.startswith('.rules:') or key.endswith('template')
|
self._expand_extends(k)
|
||||||
|
|
||||||
|
# expand job
|
||||||
|
for k in self._job_keys:
|
||||||
|
self._expand_extends(k)
|
||||||
|
|
||||||
|
def _expand_extends(self, name: str) -> t.Dict[str, t.Any]:
|
||||||
|
extends = to_list(self.config[name].pop('extends', None))
|
||||||
|
original_d = self.config[name].copy()
|
||||||
|
if not extends:
|
||||||
|
return self.config[name] # type: ignore
|
||||||
|
|
||||||
|
d = {}
|
||||||
|
while extends:
|
||||||
|
self._used_template_keys.update(extends)
|
||||||
|
|
||||||
|
for i in extends:
|
||||||
|
d.update(self._expand_extends(i))
|
||||||
|
|
||||||
|
extends = to_list(self.config[name].pop('extends', None))
|
||||||
|
|
||||||
|
self.config[name] = {**d, **original_d}
|
||||||
|
return self.config[name] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def get_all_manifest_files() -> t.List[str]:
|
def get_all_manifest_files() -> t.List[str]:
|
||||||
|
Loading…
Reference in New Issue
Block a user