☠☠ backed out by d88e5dec2638 ☠ ☠ | |
author | Dustin J. Mitchell <dustin@mozilla.com> |
Tue, 01 Aug 2017 20:02:59 +0000 | |
changeset 431645 | 7c07cb7985302e288a56c29c3fac22cacc4096d2 |
parent 431644 | 9767e159a7018465824b7f6e4d504875cfa5cc6b |
child 431646 | b3eb0c93972084fd856a6d6f1bdadf8035478162 |
push id | 7785 |
push user | ryanvm@gmail.com |
push date | Thu, 21 Sep 2017 13:39:55 +0000 |
treeherder | mozilla-beta@06d4034a8a03 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ahal, glandium |
bugs | 1383880 |
milestone | 57.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/taskcluster/ci/build/android-stuff.yml +++ b/taskcluster/ci/build/android-stuff.yml @@ -34,19 +34,19 @@ android-dependencies/opt: - builds/releng_base_android_64_builds.py - disable_signing.py - platform_supports_post_upload_to_latest.py script: "mozharness/scripts/fx_desktop_build.py" secrets: true custom-build-variant-cfg: api-16-gradle-dependencies tooltool-downloads: internal job-script: taskcluster/scripts/builder/build-android-dependencies.sh - optimizations: - - - skip-unless-changed - - - "mobile/android/config/**" + optimization: + skip-unless-changed: + - "mobile/android/config/**" - "testing/mozharness/configs/builds/releng_sub_android_configs/*gradle_dependencies.py" - "**/*.gradle" - "taskcluster/docker/android-build/**" android-test/opt: description: "Android armv7 unit tests" index: product: mobile @@ -77,19 +77,19 @@ android-test/opt: config: - builds/releng_base_android_64_builds.py - disable_signing.py - platform_supports_post_upload_to_latest.py script: "mozharness/scripts/fx_desktop_build.py" secrets: true custom-build-variant-cfg: android-test tooltool-downloads: internal - optimizations: - - - skip-unless-changed - - - "mobile/android/base/**" + optimization: + skip-unless-changed: + - "mobile/android/base/**" - "mobile/android/config/**" - "mobile/android/tests/background/junit4/**" - "**/*.gradle" android-lint/opt: description: "Android lint" index: product: mobile @@ -135,19 +135,19 @@ android-lint/opt: config: - builds/releng_base_android_64_builds.py - disable_signing.py - platform_supports_post_upload_to_latest.py script: "mozharness/scripts/fx_desktop_build.py" secrets: true custom-build-variant-cfg: android-lint tooltool-downloads: internal - optimizations: - - - skip-unless-changed - - - "mobile/android/**/*.java" + optimization: + skip-unless-changed: + - "mobile/android/**/*.java" - "mobile/android/**/*.jpeg" - "mobile/android/**/*.jpg" - "mobile/android/**/*.png" - "mobile/android/**/*.svg" - "mobile/android/**/*.xml" # Manifest & android resources - "mobile/android/**/Makefile.in" - "mobile/android/config/**" - "mobile/android/**/moz.build" @@ -187,19 +187,19 @@ android-checkstyle/opt: config: - builds/releng_base_android_64_builds.py - disable_signing.py - platform_supports_post_upload_to_latest.py script: "mozharness/scripts/fx_desktop_build.py" secrets: true custom-build-variant-cfg: android-checkstyle tooltool-downloads: internal - optimizations: - - - skip-unless-changed - - - "mobile/android/**/checkstyle.xml" + optimization: + skip-unless-changed: + - "mobile/android/**/checkstyle.xml" - "mobile/android/**/*.java" - "mobile/android/**/Makefile.in" - "mobile/android/config/**" - "mobile/android/**/moz.build" - "**/*.gradle" android-findbugs/opt: description: "Android findbugs" @@ -241,15 +241,15 @@ android-findbugs/opt: config: - builds/releng_base_android_64_builds.py - disable_signing.py - platform_supports_post_upload_to_latest.py script: "mozharness/scripts/fx_desktop_build.py" secrets: true custom-build-variant-cfg: android-findbugs tooltool-downloads: internal - optimizations: - - - skip-unless-changed - - - "mobile/android/**/*.java" + optimization: + skip-unless-changed: + - "mobile/android/**/*.java" - "mobile/android/**/Makefile.in" - "mobile/android/config/**" - "mobile/android/**/moz.build" - "**/*.gradle"
--- a/taskcluster/taskgraph/generator.py +++ b/taskcluster/taskgraph/generator.py @@ -61,17 +61,17 @@ class Kind(object): # perform the transformations on the loaded inputs trans_config = TransformConfig(self.name, self.path, config, parameters, kind_dependencies_tasks) tasks = [Task(self.name, label=task_dict['label'], attributes=task_dict['attributes'], task=task_dict['task'], - optimizations=task_dict.get('optimizations'), + optimization=task_dict.get('optimization'), dependencies=task_dict.get('dependencies')) for task_dict in transforms(trans_config, inputs)] return tasks class TaskGraphGenerator(object): """ The central controller for taskgraph. This handles all phases of graph
--- a/taskcluster/taskgraph/optimize.py +++ b/taskcluster/taskgraph/optimize.py @@ -2,16 +2,17 @@ # 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/. from __future__ import absolute_import, print_function, unicode_literals import logging import os import requests +from collections import defaultdict from .graph import Graph from . import files_changed from .taskgraph import TaskGraph from .util.seta import is_low_value_task from .util.taskcluster import find_task_id from .util.parameterization import resolve_task_references from slugid import nice as slugid @@ -40,85 +41,97 @@ def optimize_task_graph(target_task_grap named_links_dict=named_links_dict, label_to_taskid=label_to_taskid, existing_tasks=existing_tasks) return get_subgraph(target_task_graph, named_links_dict, label_to_taskid), label_to_taskid def optimize_task(task, params): """ - Optimize a single task by running its optimizations in order until one - succeeds. + Run the optimization for a given task """ - for opt in task.optimizations: - opt_type, args = opt[0], opt[1:] - opt_fn = _optimizations[opt_type] - opt_result = opt_fn(task, params, *args) - if opt_result: - return opt_result - - return False + if not task.optimization: + return False + opt_type, arg = task.optimization.items()[0] + opt_fn = _optimizations[opt_type] + return opt_fn(task, params, arg) def annotate_task_graph(target_task_graph, params, do_not_optimize, named_links_dict, label_to_taskid, existing_tasks): """ Annotate each task in the graph with .optimized (boolean) and .task_id (possibly None), following the rules for optimization and calling the task kinds' `optimize_task` method. As a side effect, label_to_taskid is updated with labels for all optimized tasks that are replaced with existing tasks. """ # set .optimized for all tasks, and .task_id for optimized tasks # with replacements + opt_counts = defaultdict(lambda: {'away': 0, 'replaced': 0}) for label in target_task_graph.graph.visit_postorder(): task = target_task_graph.tasks[label] named_task_dependencies = named_links_dict.get(label, {}) # check whether any dependencies have been optimized away dependencies = [target_task_graph.tasks[l] for l in named_task_dependencies.itervalues()] for t in dependencies: if t.optimized and not t.task_id: raise Exception( "task {} was optimized away, but {} depends on it".format( t.label, label)) # if this task is blacklisted, don't even consider optimizing replacement_task_id = None + opt_by = None if label in do_not_optimize: optimized = False # Let's check whether this task has been created before elif existing_tasks is not None and label in existing_tasks: optimized = True replacement_task_id = existing_tasks[label] + opt_by = "existing_tasks" # otherwise, examine the task itself (which may be an expensive operation) else: opt_result = optimize_task(task, params) # use opt_result to determine values for optimized, replacement_task_id optimized = bool(opt_result) - replacement_task_id = opt_result if opt_result and opt_result is not True else None + if optimized: + opt_by = task.optimization.keys()[0] + replacement_task_id = opt_result if opt_result is not True else None task.optimized = optimized task.task_id = replacement_task_id if replacement_task_id: label_to_taskid[label] = replacement_task_id if optimized: if replacement_task_id: + opt_counts[opt_by]['replaced'] += 1 logger.debug("optimizing `{}`, replacing with task `{}`" .format(label, replacement_task_id)) else: + opt_counts[opt_by]['away'] += 1 logger.debug("optimizing `{}` away".format(label)) # note: any dependent tasks will fail when they see this + + for opt_by in sorted(opt_counts): + counts = opt_counts[opt_by] + if counts['away'] and not counts['replaced']: + msg = "optimized away {} tasks for {}: ".format(counts['away'], opt_by) + elif counts['replaced'] and not counts['away']: + msg = "optimized {} tasks, replacing with other tasks, for {}: ".format( + counts['away'], opt_by) else: - if replacement_task_id: - raise Exception("{}: optimize_task returned False with a taskId".format(label)) + msg = "optimized {} tasks for {}, replacing {} and optimizing {} away".format( + sum(counts.values()), opt_by, counts['replaced'], counts['away']) + logger.info(msg) def get_subgraph(annotated_task_graph, named_links_dict, label_to_taskid): """ Return the subgraph of annotated_task_graph consisting only of non-optimized tasks and edges between them. To avoid losing track of taskIds for tasks optimized away, this method @@ -163,31 +176,32 @@ def optimization(name): if name in _optimizations: raise Exception("multiple optimizations with name {}".format(name)) _optimizations[name] = func return func return wrap @optimization('index-search') -def opt_index_search(task, params, index_path): - try: - task_id = find_task_id( - index_path, - use_proxy=bool(os.environ.get('TASK_ID'))) - - return task_id or True - except requests.exceptions.HTTPError: - pass +def opt_index_search(task, params, index_paths): + for index_path in index_paths: + try: + task_id = find_task_id( + index_path, + use_proxy=bool(os.environ.get('TASK_ID'))) + return task_id + except requests.exceptions.HTTPError: + # 404 will end up here and go on to the next index path + pass return False @optimization('seta') -def opt_seta(task, params): +def opt_seta(task, params, _): bbb_task = False # for bbb tasks we need to send in the buildbot buildername if task.task.get('provisionerId', '') == 'buildbot-bridge': label = task.task.get('payload').get('buildername') bbb_task = True else: label = task.label
--- a/taskcluster/taskgraph/task.py +++ b/taskcluster/taskgraph/task.py @@ -8,64 +8,64 @@ from __future__ import absolute_import, class Task(object): """ Representation of a task in a TaskGraph. Each Task has, at creation: - kind: the name of the task kind - label; the label for this task - attributes: a dictionary of attributes for this task (used for filtering) - task: the task definition (JSON-able dictionary) - - optimizations: optimizations to apply to the task (see taskgraph.optimize) + - optimization: optimization to apply to the task (see taskgraph.optimize) - dependencies: tasks this one depends on, in the form {name: label}, for example {'build': 'build-linux64/opt', 'docker-image': 'build-docker-image-desktop-test'} And later, as the task-graph processing proceeds: - task_id -- TaskCluster taskId under which this task will be created - optimized -- true if this task need not be performed This class is just a convenience wraper for the data type and managing display, comparison, serialization, etc. It has no functionality of its own. """ def __init__(self, kind, label, attributes, task, - optimizations=None, dependencies=None): + optimization=None, dependencies=None): self.kind = kind self.label = label self.attributes = attributes self.task = task self.task_id = None self.optimized = False self.attributes['kind'] = kind - self.optimizations = optimizations or [] + self.optimization = optimization self.dependencies = dependencies or {} def __eq__(self, other): return self.kind == other.kind and \ self.label == other.label and \ self.attributes == other.attributes and \ self.task == other.task and \ self.task_id == other.task_id and \ - self.optimizations == other.optimizations and \ + self.optimization == other.optimization and \ self.dependencies == other.dependencies def __repr__(self): return ('Task({kind!r}, {label!r}, {attributes!r}, {task!r}, ' - 'optimizations={optimizations!r}, ' + 'optimization={optimization!r}, ' 'dependencies={dependencies!r})'.format(**self.__dict__)) def to_json(self): rv = { 'kind': self.kind, 'label': self.label, 'attributes': self.attributes, 'dependencies': self.dependencies, - 'optimizations': self.optimizations, + 'optimization': self.optimization, 'task': self.task, } if self.task_id: rv['task_id'] = self.task_id return rv @classmethod def from_json(cls, task_dict): @@ -74,13 +74,13 @@ class Task(object): the original Task object. This is used to "resume" the task-graph generation process, for example in Action tasks. """ rv = cls( kind=task_dict['kind'], label=task_dict['label'], attributes=task_dict['attributes'], task=task_dict['task'], - optimizations=task_dict['optimizations'], + optimization=task_dict['optimization'], dependencies=task_dict.get('dependencies')) if 'task_id' in task_dict: rv.task_id = task_dict['task_id'] return rv
--- a/taskcluster/taskgraph/test/test_optimize.py +++ b/taskcluster/taskgraph/test/test_optimize.py @@ -56,28 +56,28 @@ class TestResolveTaskReferences(unittest class TestOptimize(unittest.TestCase): kind = None @classmethod def setUpClass(cls): # set up some simple optimization functions - optimization('no-optimize')(lambda self, params: False) - optimization('optimize-away')(lambda self, params: True) + optimization('no-optimize')(lambda self, params, arg: False) + optimization('optimize-away')(lambda self, params, arg: True) optimization('optimize-to-task')(lambda self, params, task: task) def make_task(self, label, optimization=None, task_def=None, optimized=None, task_id=None): task_def = task_def or {'sample': 'task-def'} task = Task(kind='test', label=label, attributes={}, task=task_def) task.optimized = optimized if optimization: - task.optimizations = [optimization] + task.optimization = optimization else: - task.optimizations = [] + task.optimization = None task.task_id = task_id return task def make_graph(self, *tasks_and_edges): tasks = {t.label: t for t in tasks_and_edges if isinstance(t, Task)} edges = {e for e in tasks_and_edges if not isinstance(e, Task)} return TaskGraph(tasks, graph.Graph(set(tasks), edges)) @@ -87,65 +87,65 @@ class TestOptimize(unittest.TestCase): got_annotations = { t.label: repl(t.task_id) or t.optimized for t in graph.tasks.itervalues() } self.assertEqual(got_annotations, annotations) def test_annotate_task_graph_no_optimize(self): "annotating marks everything as un-optimized if the kind returns that" graph = self.make_graph( - self.make_task('task1', ['no-optimize']), - self.make_task('task2', ['no-optimize']), - self.make_task('task3', ['no-optimize']), + self.make_task('task1', {'no-optimize': []}), + self.make_task('task2', {'no-optimize': []}), + self.make_task('task3', {'no-optimize': []}), ('task2', 'task1', 'build'), ('task2', 'task3', 'image'), ) annotate_task_graph(graph, {}, set(), graph.graph.named_links_dict(), {}, None) self.assert_annotations( graph, task1=False, task2=False, task3=False ) def test_annotate_task_graph_optimize_away_dependency(self): "raises exception if kind optimizes away a task on which another depends" graph = self.make_graph( - self.make_task('task1', ['optimize-away']), - self.make_task('task2', ['no-optimize']), + self.make_task('task1', {'optimize-away': []}), + self.make_task('task2', {'no-optimize': []}), ('task2', 'task1', 'build'), ) self.assertRaises( Exception, lambda: annotate_task_graph(graph, {}, set(), graph.graph.named_links_dict(), {}, None) ) def test_annotate_task_graph_do_not_optimize(self): "annotating marks everything as un-optimized if in do_not_optimize" graph = self.make_graph( - self.make_task('task1', ['optimize-away']), - self.make_task('task2', ['optimize-away']), + self.make_task('task1', {'optimize-away': True}), + self.make_task('task2', {'optimize-away': True}), ('task2', 'task1', 'build'), ) label_to_taskid = {} annotate_task_graph(graph, {}, {'task1', 'task2'}, graph.graph.named_links_dict(), label_to_taskid, None) self.assert_annotations( graph, task1=False, task2=False ) self.assertEqual def test_annotate_task_graph_nos_do_not_propagate(self): "a task with a non-optimized dependency can be optimized" graph = self.make_graph( - self.make_task('task1', ['no-optimize']), - self.make_task('task2', ['optimize-to-task', 'taskid']), - self.make_task('task3', ['optimize-to-task', 'taskid']), + self.make_task('task1', {'no-optimize': []}), + self.make_task('task2', {'optimize-to-task': 'taskid'}), + self.make_task('task3', {'optimize-to-task': 'taskid'}), ('task2', 'task1', 'build'), ('task2', 'task3', 'image'), ) annotate_task_graph(graph, {}, set(), graph.graph.named_links_dict(), {}, None) self.assert_annotations( graph, task1=False, @@ -228,19 +228,19 @@ class TestOptimize(unittest.TestCase): self.assertEqual(sub.tasks[task2].task_id, task2) self.assertEqual(sorted(sub.tasks[task2].task['dependencies']), sorted([task3, 'dep1'])) self.assertEqual(sub.tasks[task2].task['payload'], 'http://dep1/' + task3) self.assertEqual(sub.tasks[task3].task_id, task3) def test_optimize(self): "optimize_task_graph annotates and extracts the subgraph from a simple graph" input = self.make_graph( - self.make_task('task1', ['optimize-to-task', 'dep1']), - self.make_task('task2', ['no-optimize']), - self.make_task('task3', ['no-optimize']), + self.make_task('task1', {'optimize-to-task': 'dep1'}), + self.make_task('task2', {'no-optimize': []}), + self.make_task('task3', {'no-optimize': []}), ('task2', 'task1', 'build'), ('task2', 'task3', 'image'), ) opt, label_to_taskid = optimize_task_graph(input, {}, set()) self.assertEqual(opt.graph, graph.Graph( {label_to_taskid['task2'], label_to_taskid['task3']}, {(label_to_taskid['task2'], label_to_taskid['task3'], 'image')}))
--- a/taskcluster/taskgraph/test/test_taskgraph.py +++ b/taskcluster/taskgraph/test/test_taskgraph.py @@ -19,60 +19,60 @@ class TestTaskGraph(unittest.TestCase): def test_taskgraph_to_json(self): tasks = { 'a': Task(kind='test', label='a', attributes={'attr': 'a-task'}, task={'taskdef': True}), 'b': Task(kind='test', label='b', attributes={}, task={'task': 'def'}, - optimizations=[['seta']], + optimization={'seta': None}, # note that this dep is ignored, superseded by that # from the taskgraph's edges dependencies={'first': 'a'}), } graph = Graph(nodes=set('ab'), edges={('a', 'b', 'edgelabel')}) taskgraph = TaskGraph(tasks, graph) res = taskgraph.to_json() self.assertEqual(res, { 'a': { 'kind': 'test', 'label': 'a', 'attributes': {'attr': 'a-task', 'kind': 'test'}, 'task': {'taskdef': True}, 'dependencies': {'edgelabel': 'b'}, - 'optimizations': [], + 'optimization': None, }, 'b': { 'kind': 'test', 'label': 'b', 'attributes': {'kind': 'test'}, 'task': {'task': 'def'}, 'dependencies': {}, - 'optimizations': [['seta']], + 'optimization': {'seta': None}, } }) def test_round_trip(self): graph = TaskGraph(tasks={ 'a': Task( kind='fancy', label='a', attributes={}, dependencies={'prereq': 'b'}, # must match edges, below - optimizations=[['seta']], + optimization={'seta': None}, task={'task': 'def'}), 'b': Task( kind='pre', label='b', attributes={}, dependencies={}, - optimizations=[['seta']], + optimization={'seta': None}, task={'task': 'def2'}), }, graph=Graph(nodes={'a', 'b'}, edges={('a', 'b', 'prereq')})) tasks, new_graph = TaskGraph.from_json(graph.to_json()) self.assertEqual(graph, new_graph) if __name__ == '__main__':
--- a/taskcluster/taskgraph/transforms/docker_image.py +++ b/taskcluster/taskgraph/transforms/docker_image.py @@ -56,36 +56,36 @@ def fill_template(config, tasks): context_hash=context_hash, )) # As an optimization, if the context hash exists for a high level, that image # task ID will be used. The reasoning behind this is that eventually everything ends # up on level 3 at some point if most tasks use this as a common image # for a given context hash, a worker within Taskcluster does not need to contain # the same image per branch. - optimizations = [['index-search', '{}.level-{}.{}.hash.{}'.format( - INDEX_PREFIX, level, image_name, context_hash)] - for level in reversed(range(int(config.params['level']), 4))] + optimization = {'index-search': ['{}.level-{}.{}.hash.{}'.format( + INDEX_PREFIX, level, image_name, context_hash) + for level in reversed(range(int(config.params['level']), 4))]} # Adjust the zstandard compression level based on the execution level. # We use faster compression for level 1 because we care more about # end-to-end times. We use slower/better compression for other levels # because images are read more often and it is worth the trade-off to # burn more CPU once to reduce image size. zstd_level = '3' if int(config.params['level']) == 1 else '10' # include some information that is useful in reconstructing this task # from JSON taskdesc = { 'label': 'build-docker-image-' + image_name, 'description': description, 'attributes': {'image_name': image_name}, 'expires-after': '1 year', 'routes': routes, - 'optimizations': optimizations, + 'optimization': optimization, 'scopes': ['secrets:get:project/taskcluster/gecko/hgfingerprint'], 'treeherder': { 'symbol': job_symbol, 'platform': 'taskcluster-images/opt', 'kind': 'other', 'tier': 1, }, 'run-on-projects': [],
--- a/taskcluster/taskgraph/transforms/job/__init__.py +++ b/taskcluster/taskgraph/transforms/job/__init__.py @@ -22,16 +22,17 @@ from taskgraph.util.schema import ( ) from taskgraph.util.workertypes import worker_type_implementation from taskgraph.transforms.task import task_description_schema from voluptuous import ( Any, Extra, Optional, Required, + Exclusive, ) logger = logging.getLogger(__name__) # Voluptuous uses marker objects as dictionary *keys*, but they are not # comparable, so we cast all of the keys back to regular strings task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()} @@ -54,23 +55,24 @@ job_description_schema = Schema({ Optional('routes'): task_description_schema['routes'], Optional('scopes'): task_description_schema['scopes'], Optional('tags'): task_description_schema['tags'], Optional('extra'): task_description_schema['extra'], Optional('treeherder'): task_description_schema['treeherder'], Optional('index'): task_description_schema['index'], Optional('run-on-projects'): task_description_schema['run-on-projects'], Optional('coalesce'): task_description_schema['coalesce'], - Optional('optimizations'): task_description_schema['optimizations'], + Exclusive('optimization', 'optimization'): task_description_schema['optimization'], Optional('needs-sccache'): task_description_schema['needs-sccache'], - # The "when" section contains descriptions of the circumstances - # under which this task should be included in the task graph. This - # will be converted into an element in the `optimizations` list. - Optional('when'): Any({ + # The "when" section contains descriptions of the circumstances under which + # this task should be included in the task graph. This will be converted + # into an optimization, so it cannot be specified in a job description that + # also gives 'optimization'. + Exclusive('when', 'optimization'): Any({ # This task only needs to be run if a file matching one of the given # patterns has changed in the push. The patterns use the mozpack # match function (python/mozbuild/mozpack/path.py). Optional('files-changed'): [basestring], }), # A description of how to run this job. 'run': { @@ -98,32 +100,33 @@ def validate(config, jobs): yield validate_schema(job_description_schema, job, "In job {!r}:".format(job.get('name', job.get('label')))) @transforms.add def rewrite_when_to_optimization(config, jobs): for job in jobs: when = job.pop('when', {}) - files_changed = when.get('files-changed') - if not files_changed: + if not when: yield job continue + files_changed = when.get('files-changed') + # add some common files files_changed.extend([ '{}/**'.format(config.path), 'taskcluster/taskgraph/**', ]) if 'in-tree' in job.get('worker', {}).get('docker-image', {}): files_changed.append('taskcluster/docker/{}/**'.format( job['worker']['docker-image']['in-tree'])) # "only when files changed" implies "skip if files have not changed" - job.setdefault('optimizations', []).append(['skip-unless-changed', files_changed]) + job['optimization'] = {'skip-unless-changed': files_changed} assert 'when' not in job yield job @transforms.add def make_task_description(config, jobs): """Given a build description, create a task description"""
--- a/taskcluster/taskgraph/transforms/job/toolchain.py +++ b/taskcluster/taskgraph/transforms/job/toolchain.py @@ -48,17 +48,17 @@ toolchain_run_schema = Schema({ Required('toolchain-artifact'): basestring, # An alias that can be used instead of the real toolchain job name in # the toolchains list for build jobs. Optional('toolchain-alias'): basestring, }) -def add_optimizations(config, run, taskdesc): +def add_optimization(config, run, taskdesc): files = list(run.get('resources', [])) # This file files.append('taskcluster/taskgraph/transforms/job/toolchain.py') # The script files.append('taskcluster/scripts/misc/{}'.format(run['script'])) # Tooltool manifest if any is defined: tooltool_manifest = taskdesc['worker']['env'].get('TOOLTOOL_MANIFEST') if tooltool_manifest: @@ -76,23 +76,23 @@ def add_optimizations(config, run, taskd digest = hashlib.sha256('\n'.join(data)).hexdigest() label = taskdesc['label'] subs = { 'name': label.replace('%s-' % config.kind, ''), 'digest': digest, } - optimizations = taskdesc.setdefault('optimizations', []) - # We'll try to find a cached version of the toolchain at levels above # and including the current level, starting at the highest level. + index_routes = [] for level in reversed(range(int(config.params['level']), 4)): subs['level'] = level - optimizations.append(['index-search', TOOLCHAIN_INDEX.format(**subs)]) + index_routes.append(TOOLCHAIN_INDEX.format(**subs)) + taskdesc['optimization'] = {'index-search': index_routes} # ... and cache at the lowest level. taskdesc.setdefault('routes', []).append( 'index.{}'.format(TOOLCHAIN_INDEX.format(**subs))) @run_job_using("docker-worker", "toolchain-script", schema=toolchain_run_schema) def docker_worker_toolchain(config, job, taskdesc): @@ -131,17 +131,17 @@ def docker_worker_toolchain(config, job, run['script']) ] attributes = taskdesc.setdefault('attributes', {}) attributes['toolchain-artifact'] = run['toolchain-artifact'] if 'toolchain-alias' in run: attributes['toolchain-alias'] = run['toolchain-alias'] - add_optimizations(config, run, taskdesc) + add_optimization(config, run, taskdesc) @run_job_using("generic-worker", "toolchain-script", schema=toolchain_run_schema) def windows_toolchain(config, job, taskdesc): run = job['run'] taskdesc['run-on-projects'] = ['trunk', 'try'] worker = taskdesc['worker'] @@ -178,9 +178,9 @@ def windows_toolchain(config, job, taskd r'{} -c ./build/src/taskcluster/scripts/misc/{}'.format(bash, run['script']) ] attributes = taskdesc.setdefault('attributes', {}) attributes['toolchain-artifact'] = run['toolchain-artifact'] if 'toolchain-alias' in run: attributes['toolchain-alias'] = run['toolchain-alias'] - add_optimizations(config, run, taskdesc) + add_optimization(config, run, taskdesc)
--- a/taskcluster/taskgraph/transforms/task.py +++ b/taskcluster/taskgraph/transforms/task.py @@ -162,27 +162,29 @@ task_description_schema = Schema({ # tasks. 'age': int, # The minimum number of backlogged tasks with the same coalescing key, # before the coalescing service will return tasks. 'size': int, }, - # Optimizations to perform on this task during the optimization phase, - # specified in order. These optimizations are defined in - # taskcluster/taskgraph/optimize.py. - Optional('optimizations'): [Any( - # search the index for the given index namespace, and replace this task if found - ['index-search', basestring], + # Optimization to perform on this task during the optimization phase. + # Optimizations are defined in taskcluster/taskgraph/optimize.py. + Required('optimization', default=None): Any( + # always run this task (default) + None, + # search the index for the given index namespaces, and replace this task if found + # the search occurs in order, with the first match winning + {'index-search': [basestring]}, # consult SETA and skip this task if it is low-value - ['seta'], + {'seta': None}, # skip this task if none of the given file patterns match - ['skip-unless-changed', [basestring]], - )], + {'skip-unless-changed': [basestring]}, + ), # the provisioner-id/worker-type for the task. The following parameters will # be substituted in this string: # {level} -- the scm level of this push 'worker-type': basestring, # Whether the job should use sccache compiler caching. Required('needs-sccache', default=False): bool, @@ -1227,17 +1229,17 @@ def build_task(config, tasks): env = payload.setdefault('env', {}) env['MOZ_AUTOMATION'] = '1' yield { 'label': task['label'], 'task': task_def, 'dependencies': task.get('dependencies', {}), 'attributes': attributes, - 'optimizations': task.get('optimizations', []), + 'optimization': task.get('optimization', None), } def check_caches_are_volumes(task): """Ensures that all cache paths are defined as volumes. Caches and volumes are the only filesystem locations whose content isn't defined by the Docker image itself. Some caches are optional
--- a/taskcluster/taskgraph/transforms/tests.py +++ b/taskcluster/taskgraph/transforms/tests.py @@ -896,17 +896,16 @@ def make_job_description(config, tests): attr_try_name: try_name, }) jobdesc = {} name = '{}-{}'.format(test['test-platform'], test['test-name']) jobdesc['name'] = name jobdesc['label'] = label jobdesc['description'] = test['description'] - jobdesc['when'] = test.get('when', {}) jobdesc['attributes'] = attributes jobdesc['dependencies'] = {'build': build_label} if test['mozharness']['requires-signed-builds'] is True: jobdesc['dependencies']['build-signing'] = test['build-signing-label'] jobdesc['expires-after'] = test['expires-after'] jobdesc['routes'] = [] @@ -926,19 +925,21 @@ def make_job_description(config, tests): jobdesc['treeherder'] = { 'symbol': test['treeherder-symbol'], 'kind': 'test', 'tier': test['tier'], 'platform': test.get('treeherder-machine-platform', test['build-platform']), } # run SETA unless this is a try push - jobdesc['optimizations'] = optimizations = [] - if config.params['project'] != 'try': - optimizations.append(['seta']) + if config.params['project'] == 'try': + jobdesc['when'] = test.get('when', {}) + else: + # when SETA is enabled, the 'when' does not apply (optimizations don't mix) + jobdesc['optimization'] = {'seta': None} run = jobdesc['run'] = {} run['using'] = 'mozharness-test' run['test'] = test jobdesc['worker-type'] = test.pop('worker-type') yield jobdesc