# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import logging from pathlib import Path from typing import List, Union from mozlint.parser import Parser from mozlint.pathutils import filterpaths from taskgraph.optimize.base import OptimizationStrategy from taskgraph.util.path import match as match_path from gecko_taskgraph import GECKO logger = logging.getLogger(__name__) class TGMozlintParser(Parser): """ Mozlint Parser that skips validation. This is needed because decision tasks use sparse clones and the files themselves are not present. """ def _validate(self, linter): pass class SkipUnlessMozlint(OptimizationStrategy): """ Optimization strategy for mozlint tests. Uses the mozlint YAML file for each test to determine whether or not a test should run. Using: - The optimization relies on having `files_changed` set in the decision task parameters. - Register with register_strategy. The argument is the path to MozLint YAML configuration files ("/tools/lint" for mozilla-central): - In source-test/mozlint.yml, set the optimization strategy for each job by filename: - For Mozlint jobs that run multiple linters at once use a list of filenames: """ def __init__(self, linters_path: str): self.mozlint_parser = TGMozlintParser(GECKO) self.linters_path = Path(GECKO) / linters_path def should_remove_task( self, task, params, mozlint_confs: Union[str, List[str]] ) -> bool: include = [] exclude = [] extensions = [] exclude_extensions = [] support_files = ["python/mozlint/**", "tools/lint/**"] files_changed = params["files_changed"] if isinstance(mozlint_confs, str): mozlint_confs = [mozlint_confs] for mozlint_conf in mozlint_confs: mozlint_yaml = str(self.linters_path / mozlint_conf) logger.info(f"Loading file patterns for {task.label} from {mozlint_yaml}.") linter_config = self.mozlint_parser(mozlint_yaml) for config in linter_config: include += config.get("include", []) exclude += config.get("exclude", []) extensions += [e.strip(".") for e in config.get("extensions", [])] exclude_extensions += [ e.strip(".") for e in config.get("exclude_extensions", []) ] support_files += config.get("support-files", []) # Support files may not be part of "include" patterns, so check first # Do not remove (return False) if any changed for pattern in set(support_files): for path in files_changed: if match_path(path, pattern): return False to_lint, to_exclude = filterpaths( GECKO, list(files_changed), include=include, exclude=exclude, extensions=extensions, exclude_extensions=exclude_extensions, ) # to_lint should be an empty list if there is nothing to check if not to_lint: return True return False