diff --git a/testing/taskcluster/docs/index.rst b/testing/taskcluster/docs/index.rst index 0611baeb2aa3..a9b8c50c18f0 100644 --- a/testing/taskcluster/docs/index.rst +++ b/testing/taskcluster/docs/index.rst @@ -171,6 +171,31 @@ additional-parameters *optional* Dictionary of additional parameters to pass to template expansion. +when + *optional* Dictionary of conditions that must be met for this task + to run. See the section below for more details. + +Conditional Execution +--------------------- + +The ``when`` generic task dictionary entry can declare conditions that +must be true for a task to run. Valid entries in this dictionary are +described below. + +file_patterns + List of path patterns that will be matched against all files changed. + + The set of changed files is obtained from version control. If the changed + files could not be determined, this condition is ignored and no filtering + occurs. + + Values use the ``mozpack`` matching code. ``*`` is a wildcard for + all path characters except ``/``. ``**`` matches all directories. To + e.g. match against all ``.js`` files, one would use ``**/*.js``. + + If a single pattern matches a single changed file, the task will be + scheduled. + Developing ========== diff --git a/testing/taskcluster/mach_commands.py b/testing/taskcluster/mach_commands.py index 64eed0366464..7033a1327af2 100644 --- a/testing/taskcluster/mach_commands.py +++ b/testing/taskcluster/mach_commands.py @@ -274,9 +274,14 @@ class Graph(object): @CommandArgument('--dry-run', action='store_true', default=False, help="Stub out taskIds and date fields from the task definitions.") + @CommandArgument('--ignore-conditions', + action='store_true', + help='Run tasks even if their conditions are not met') def create_graph(self, **params): from functools import partial + from mozpack.path import match as mozpackmatch + from slugid import nice as slugid import taskcluster_graph.transform.routes as routes_transform @@ -324,6 +329,7 @@ class Graph(object): # Default to current time if querying the head rev fails pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime()) vcs_info = query_vcs_info(params['head_repository'], params['head_rev']) + changed_files = set() if vcs_info: pushdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(vcs_info.pushdate)) @@ -333,6 +339,8 @@ class Graph(object): sys.stderr.write('%s %s\n' % ( c['node'][0:12], c['desc'].splitlines()[0])) + changed_files |= set(c['files']) + # Template parameters used when expanding the graph seen_images = {} parameters = dict(gaia_info().items() + { @@ -390,6 +398,41 @@ class Graph(object): 'name': 'task graph local' } + # Filter the job graph according to conditions met by this invocation run. + def should_run(task): + # Old style build or test task that doesn't define conditions. Always runs. + if 'when' not in task: + return True + + # Command line override to not filter. + if params['ignore_conditions']: + return True + + when = task['when'] + + # If the task defines file patterns and we have a set of changed + # files to compare against, only run if a file pattern matches one + # of the changed files. + file_patterns = when.get('file_patterns', None) + if file_patterns and changed_files: + for pattern in file_patterns: + for path in changed_files: + if mozpackmatch(path, pattern): + sys.stderr.write('scheduling %s because pattern %s ' + 'matches %s\n' % (task['task'], + pattern, + path)) + return True + + # No file patterns matched. Discard task. + sys.stderr.write('discarding %s because no relevant files changed\n' % + task['task']) + return False + + return True + + job_graph = filter(should_run, job_graph) + all_routes = {} for build in job_graph: diff --git a/testing/taskcluster/taskcluster_graph/commit_parser.py b/testing/taskcluster/taskcluster_graph/commit_parser.py index a2f9b7a86d61..369c4cda8f1c 100644 --- a/testing/taskcluster/taskcluster_graph/commit_parser.py +++ b/testing/taskcluster/taskcluster_graph/commit_parser.py @@ -350,6 +350,7 @@ def parse_commit(message, jobs): # TODO support declaring a different build type 'build_type': name, 'interactive': args.interactive, + 'when': task.get('when', {}) }) # Times that test jobs will be scheduled diff --git a/testing/taskcluster/tasks/branches/base_jobs.yml b/testing/taskcluster/tasks/branches/base_jobs.yml index b5adbbfd5416..835890f56cda 100644 --- a/testing/taskcluster/tasks/branches/base_jobs.yml +++ b/testing/taskcluster/tasks/branches/base_jobs.yml @@ -309,4 +309,19 @@ tests: tasks: eslint-gecko: task: tasks/tests/eslint-gecko.yml - root: true \ No newline at end of file + root: true + when: + file_patterns: + # Files that are likely audited. + - '**/*.js' + - '**/*.jsm' + - '**/*.jsx' + - '**/*.html' + - '**/*.xml' + # Run when eslint policies change. + - '**/.eslintignore' + - '**/*eslintrc*' + # The plugin implementing custom checks. + - 'testing/eslint-plugin-mozilla/**' + # Other misc lint related files. + - 'tools/lint/**'