Backed out changeset e947694d0ed7 (bug 1507898) for Linting failure. CLOSED TREE
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Wed, 21 Nov 2018 00:29:17 +0200
changeset 506598 3b818a926ee427b28b7898d24afec9cc864cda7f
parent 506597 df86262bfccdd4c4f534ec45e0549cd29474c58c
child 506599 50810256f2804f772899cfa1376e48124ab98eb6
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1507898
milestone65.0a1
backs oute947694d0ed75030968484df36f08a222cdd79b5
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
Backed out changeset e947694d0ed7 (bug 1507898) for Linting failure. CLOSED TREE
taskcluster/taskgraph/transforms/balrog_submit.py
taskcluster/taskgraph/transforms/base.py
taskcluster/taskgraph/transforms/beetmover.py
taskcluster/taskgraph/transforms/beetmover_checksums.py
taskcluster/taskgraph/transforms/beetmover_emefree_checksums.py
taskcluster/taskgraph/transforms/beetmover_geckoview.py
taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py
taskcluster/taskgraph/transforms/beetmover_push_to_release.py
taskcluster/taskgraph/transforms/beetmover_repackage.py
taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
taskcluster/taskgraph/transforms/beetmover_source_checksums.py
taskcluster/taskgraph/transforms/checksums_signing.py
taskcluster/taskgraph/transforms/diffoscope.py
taskcluster/taskgraph/transforms/docker_image.py
taskcluster/taskgraph/transforms/fetch.py
taskcluster/taskgraph/transforms/google_play_strings.py
taskcluster/taskgraph/transforms/job/__init__.py
taskcluster/taskgraph/transforms/l10n.py
taskcluster/taskgraph/transforms/push_apk.py
taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py
taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
taskcluster/taskgraph/transforms/release_snap_push.py
taskcluster/taskgraph/transforms/repackage.py
taskcluster/taskgraph/transforms/repackage_partner.py
taskcluster/taskgraph/transforms/repackage_routes.py
taskcluster/taskgraph/transforms/repackage_signing.py
taskcluster/taskgraph/transforms/repackage_signing_partner.py
taskcluster/taskgraph/transforms/signing.py
taskcluster/taskgraph/transforms/source_checksums_signing.py
taskcluster/taskgraph/transforms/source_test.py
taskcluster/taskgraph/transforms/tests.py
--- a/taskcluster/taskgraph/transforms/balrog_submit.py
+++ b/taskcluster/taskgraph/transforms/balrog_submit.py
@@ -5,27 +5,30 @@
 Transform the per-locale balrog task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     get_balrog_server_scope, get_worker_type_for_scope
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 
 # 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()}
 
+transforms = TransformSequence()
+
 # shortcut for a string where task references are allowed
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 balrog_description_schema = schema.extend({
     # unique label to describe this balrog task, defaults to balrog-{dep.label}
     Optional('label'): basestring,
@@ -36,18 +39,25 @@ balrog_description_schema = schema.exten
     Optional('treeherder'): task_description_schema['treeherder'],
 
     # Shipping product / phase
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 
-transforms = TransformSequence()
-transforms.add_validate(balrog_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            balrog_description_schema, job,
+            "In balrog ({!r} kind) task for {!r}:".format(config.kind, label))
+
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
 
         treeherder = job.get('treeherder', {})
--- a/taskcluster/taskgraph/transforms/base.py
+++ b/taskcluster/taskgraph/transforms/base.py
@@ -4,20 +4,16 @@
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import attr
 
 from ..parameters import Parameters
 from ..config import GraphConfig
 
-import attr
-
-from taskgraph.util.schema import Schema, validate_schema
-
 
 @attr.s(frozen=True)
 class TransformConfig(object):
     """
     A container for configuration affecting transforms.  The `config` argument
     to transforms is an instance of this class.
     """
 
@@ -60,31 +56,8 @@ class TransformSequence(object):
             items = xform(config, items)
             if items is None:
                 raise Exception("Transform {} is not a generator".format(xform))
         return items
 
     def add(self, func):
         self._transforms.append(func)
         return func
-
-    def add_validate(self, schema):
-        self.add(ValidateSchema(schema))
-
-
-@attr.s
-class ValidateSchema(object):
-    schema = attr.ib(type=Schema)
-
-    def __call__(self, config, tasks):
-        for task in tasks:
-            if 'name' in task:
-                error = "In {kind} kind task {name!r}:".format(
-                    kind=config.kind, name=task['name'])
-            elif 'label' in task:
-                error = "In job {label!r}:".format(label=task['label'])
-            elif 'primary-dependency' in task:
-                error = "In {kind} kind task for {dependency!r}:".format(
-                    kind=config.kind, dependency=task['primary-dependency'].label)
-            else:
-                error = "In unknown task:"
-            validate_schema(self.schema, task, error)
-            yield task
--- a/taskcluster/taskgraph/transforms/beetmover.py
+++ b/taskcluster/taskgraph/transforms/beetmover.py
@@ -5,16 +5,17 @@
 Transform the beetmover task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          get_worker_type_for_scope)
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 
@@ -136,17 +137,24 @@ beetmover_description_schema = schema.ex
     # locale is passed only for l10n beetmoving
     Optional('locale'): basestring,
 
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
-transforms.add_validate(beetmover_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_description_schema, job,
+            "In beetmover ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/beetmover_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_checksums.py
@@ -6,41 +6,51 @@ Transform the checksums signing task int
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          get_worker_type_for_scope)
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_checksums_description_schema = schema.extend({
     Required('depname', default='build'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('locale'): basestring,
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_checksums_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_checksums_description_schema, job,
+            "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_checksums_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/beetmover_emefree_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_emefree_checksums.py
@@ -6,38 +6,47 @@ Transform release-beetmover-source-check
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_checksums_description_schema = schema.extend({
     Required('depname', default='build'): basestring,
     Optional('label'): basestring,
     Optional('extra'): object,
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_checksums_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_checksums_description_schema, job,
+            "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_checksums_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
         build_platform = attributes.get("build_platform")
--- a/taskcluster/taskgraph/transforms/beetmover_geckoview.py
+++ b/taskcluster/taskgraph/transforms/beetmover_geckoview.py
@@ -7,17 +7,17 @@ Transform the beetmover task into an act
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import \
     craft_release_properties as beetmover_craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
-from taskgraph.util.schema import resolve_keyed_by, optionally_keyed_by
+from taskgraph.util.schema import validate_schema, resolve_keyed_by, optionally_keyed_by
 from taskgraph.util.scriptworker import get_worker_type_for_scope
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 
 _ARTIFACT_ID_PER_PLATFORM = {
     'android-aarch64': 'geckoview{update_channel}-arm64-v8a',
     'android-api-16': 'geckoview{update_channel}-armeabi-v7a',
@@ -30,33 +30,42 @@ from voluptuous import Required, Optiona
     'mozilla-beta': '-beta',
     'mozilla-central': '-nightly',
     'try': '-nightly-try',
     'maple': '-nightly-maple',
 }
 
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
+transforms = TransformSequence()
+
 beetmover_description_schema = schema.extend({
     Required('depname', default='build'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
 
     Required('run-on-projects'): task_description_schema['run-on-projects'],
     Required('run-on-hg-branches'): task_description_schema['run-on-hg-branches'],
 
     Optional('bucket-scope'): optionally_keyed_by('release-level', basestring),
     Optional('shipping-phase'): optionally_keyed_by(
         'project', task_description_schema['shipping-phase']
     ),
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_description_schema, job,
+            "In beetmover-geckoview ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def resolve_keys(config, jobs):
     for job in jobs:
         resolve_keyed_by(
             job, 'run-on-hg-branches', item_name=job['label'], project=config.params['project']
         )
--- a/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_langpack_checksums.py
@@ -6,41 +6,51 @@ Transform release-beetmover-langpack-che
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          get_worker_type_for_scope)
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_checksums_description_schema = schema.extend({
     Required('depname', default='build'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('locale'): basestring,
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_checksums_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_checksums_description_schema, job,
+            "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_checksums_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/beetmover_push_to_release.py
+++ b/taskcluster/taskgraph/transforms/beetmover_push_to_release.py
@@ -3,30 +3,33 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 Transform the beetmover-push-to-release task into a task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
-from taskgraph.util.schema import Schema
+from taskgraph.util.schema import (
+     validate_schema, Schema,
+)
 from taskgraph.util.scriptworker import (
     get_beetmover_bucket_scope, add_scope_prefix,
     get_worker_type_for_scope,
 )
 from taskgraph.transforms.job import job_description_schema
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}
 
+transforms = TransformSequence()
 
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_push_to_release_description_schema = Schema({
     Required('name'): basestring,
     Required('product'): basestring,
@@ -39,18 +42,24 @@ beetmover_push_to_release_description_sc
     Optional('index'): {basestring: basestring},
     Optional('routes'): [basestring],
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Required('shipping-product'): task_description_schema['shipping-product'],
     Optional('extra'): task_description_schema['extra'],
 })
 
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_push_to_release_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job['name']
+        validate_schema(
+            beetmover_push_to_release_description_schema, job,
+            "In beetmover-push-to-release ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_push_to_release_description(config, jobs):
     for job in jobs:
         treeherder = job.get('treeherder', {})
         treeherder.setdefault('symbol', 'Rel(BM-C)')
         treeherder.setdefault('tier', 1)
--- a/taskcluster/taskgraph/transforms/beetmover_repackage.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage.py
@@ -9,16 +9,17 @@ from __future__ import absolute_import, 
 
 from taskgraph.loader.multi_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.partials import (get_balrog_platform_name,
                                      get_partials_artifacts,
                                      get_partials_artifact_map)
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          get_worker_type_for_scope)
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 import logging
@@ -138,16 +139,18 @@ UPSTREAM_ARTIFACT_SIGNED_REPACKAGE_PATHS
 UPSTREAM_ARTIFACT_SIGNED_MSI_PATHS = [
     'target.installer.msi',
 ]
 
 # 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()}
 
+transforms = TransformSequence()
+
 # shortcut for a string where task references are allowed
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_description_schema = schema.extend({
     # depname is used in taskref's to identify the taskID of the unsigned things
     Required('depname', default='build'): basestring,
@@ -162,18 +165,25 @@ beetmover_description_schema = schema.ex
 
     # locale is passed only for l10n beetmoving
     Optional('locale'): basestring,
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     # Optional until we fix asan (run_on_projects?)
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_description_schema, job,
+            "In beetmover ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
+++ b/taskcluster/taskgraph/transforms/beetmover_repackage_partner.py
@@ -14,16 +14,17 @@ from taskgraph.util.attributes import co
 from taskgraph.util.partners import (
     check_if_partners_enabled,
     get_ftp_platform,
     get_partner_config_by_kind,
 )
 from taskgraph.util.schema import (
     optionally_keyed_by,
     resolve_keyed_by,
+    validate_schema,
 )
 from taskgraph.util.scriptworker import (
     add_scope_prefix,
     get_beetmover_bucket_scope,
     get_worker_type_for_scope,
 )
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.task import task_description_schema
@@ -34,16 +35,18 @@ import logging
 
 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()}
 
+transforms = TransformSequence()
+
 # shortcut for a string where task references are allowed
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_description_schema = schema.extend({
     # depname is used in taskref's to identify the taskID of the unsigned things
     Required('depname', default='build'): basestring,
@@ -55,19 +58,27 @@ beetmover_description_schema = schema.ex
     Required('partner-public-path'): Any(None, basestring),
     Required('partner-private-path'): Any(None, basestring),
 
     Optional('extra'): object,
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
 transforms.add(check_if_partners_enabled)
-transforms.add_validate(beetmover_description_schema)
+
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_description_schema, job,
+            "In beetmover ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def resolve_keys(config, jobs):
     for job in jobs:
         resolve_keyed_by(
             job, 'partner-bucket-scope', item_name=job['label'],
             **{'release-level': config.params.release_level()}
--- a/taskcluster/taskgraph/transforms/beetmover_source_checksums.py
+++ b/taskcluster/taskgraph/transforms/beetmover_source_checksums.py
@@ -6,41 +6,51 @@ Transform release-beetmover-source-check
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          get_worker_type_for_scope)
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 beetmover_checksums_description_schema = schema.extend({
     Required('depname', default='build'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('locale'): basestring,
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(beetmover_checksums_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            beetmover_checksums_description_schema, job,
+            "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_beetmover_checksums_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/checksums_signing.py
@@ -5,42 +5,52 @@
 Transform the checksums signing task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
     add_scope_prefix,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 checksums_signing_description_schema = schema.extend({
     Required('depname', default='beetmover'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(checksums_signing_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            checksums_signing_description_schema, job,
+            "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_checksums_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/diffoscope.py
+++ b/taskcluster/taskgraph/transforms/diffoscope.py
@@ -6,24 +6,27 @@ This transform construct tasks to perfor
 defined in kind.yml
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.schema import (
     Schema,
+    validate_schema,
 )
 from taskgraph.util.taskcluster import get_artifact_path, get_artifact_url
 from voluptuous import (
     Any,
     Optional,
     Required,
 )
 
+transforms = TransformSequence()
+
 index_or_string = Any(
     basestring,
     {Required('index-search'): basestring},
 )
 
 diff_description_schema = Schema({
     # Name of the diff task.
     Required('name'): basestring,
@@ -41,18 +44,24 @@ diff_description_schema = Schema({
     # Arguments to pass to diffoscope, used for job-defaults in
     # taskcluster/ci/diffoscope/kind.yml
     Optional('args'): basestring,
 
     # Extra arguments to pass to diffoscope, that can be set per job.
     Optional('extra-args'): basestring,
 })
 
-transforms = TransformSequence()
-transforms.add_validate(diff_description_schema)
+
+@transforms.add
+def validate(config, tasks):
+    for task in tasks:
+        validate_schema(
+            diff_description_schema, task,
+            "In diff task {!r}:".format(task.get('name', 'unknown')))
+        yield task
 
 
 @transforms.add
 def fill_template(config, tasks):
     dummy_tasks = {}
 
     for task in tasks:
         name = task['name']
--- a/taskcluster/taskgraph/transforms/docker_image.py
+++ b/taskcluster/taskgraph/transforms/docker_image.py
@@ -12,16 +12,17 @@ from taskgraph.transforms.base import Tr
 from taskgraph.transforms.task import _run_task_suffix
 from .. import GECKO
 from taskgraph.util.docker import (
     generate_context_hash,
 )
 from taskgraph.util.cached_tasks import add_optimization
 from taskgraph.util.schema import (
     Schema,
+    validate_schema,
 )
 from voluptuous import (
     Optional,
     Required,
 )
 
 DIGEST_RE = re.compile('^[0-9a-f]{64}$')
 
@@ -48,17 +49,23 @@ docker_image_schema = Schema({
     # different from the docker image name.
     Optional('definition'): basestring,
 
     # List of package tasks this docker image depends on.
     Optional('packages'): [basestring],
 })
 
 
-transforms.add_validate(docker_image_schema)
+@transforms.add
+def validate(config, tasks):
+    for task in tasks:
+        validate_schema(
+            docker_image_schema, task,
+            "In docker image {!r}:".format(task.get('name', 'unknown')))
+        yield task
 
 
 def order_image_tasks(config, tasks):
     """Iterate image tasks in an order where parent images come first."""
     pending = deque(tasks)
     task_names = {task['name'] for task in pending}
     emitted = set()
     while True:
--- a/taskcluster/taskgraph/transforms/fetch.py
+++ b/taskcluster/taskgraph/transforms/fetch.py
@@ -19,24 +19,27 @@ import taskgraph
 from .base import (
     TransformSequence,
 )
 from ..util.cached_tasks import (
     add_optimization,
 )
 from ..util.schema import (
     Schema,
+    validate_schema,
 )
 from ..util.treeherder import (
     join_symbol,
 )
 
 
 CACHE_TYPE = 'content.v1'
 
+transforms = TransformSequence()
+
 FETCH_SCHEMA = Schema({
     # Name of the task.
     Required('name'): basestring,
 
     # Relative path (from config.path) to the file the task was defined
     # in.
     Optional('job-from'): basestring,
 
@@ -69,18 +72,25 @@ FETCH_SCHEMA = Schema({
         # The name to give to the generated artifact.
         Optional('artifact-name'): basestring,
 
         # IMPORTANT: when adding anything that changes the behavior of the task,
         # it is important to update the digest data used to compute cache hits.
     },
 })
 
-transforms = TransformSequence()
-transforms.add_validate(FETCH_SCHEMA)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(
+            FETCH_SCHEMA, job,
+            'In fetch task {!r}:'.format(job.get('name', 'unknown')))
+
+        yield job
 
 
 @transforms.add
 def process_fetch_job(config, jobs):
     # Converts fetch-url entries to the job schema.
     for job in jobs:
         if 'fetch' not in job:
             continue
--- a/taskcluster/taskgraph/transforms/google_play_strings.py
+++ b/taskcluster/taskgraph/transforms/google_play_strings.py
@@ -4,45 +4,50 @@
 """
 Transform the push-apk kind into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.task import task_description_schema
-from taskgraph.util.schema import resolve_keyed_by, Schema
+from taskgraph.util.schema import resolve_keyed_by, Schema, validate_schema
 
 from voluptuous import Required
 
+
+transforms = TransformSequence()
+
 # 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()}
 
 google_play_description_schema = Schema({
     Required('name'): basestring,
+    Required('label'): task_description_schema['label'],
     Required('description'): task_description_schema['description'],
     Required('job-from'): task_description_schema['job-from'],
     Required('attributes'): task_description_schema['attributes'],
     Required('treeherder'): task_description_schema['treeherder'],
     Required('run-on-projects'): task_description_schema['run-on-projects'],
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Required('shipping-product'): task_description_schema['shipping-product'],
     Required('worker-type'): task_description_schema['worker-type'],
     Required('worker'): object,
 })
 
-transforms = TransformSequence()
-transforms.add_validate(google_play_description_schema)
-
 
 @transforms.add
-def set_label(config, jobs):
+def validate_jobs_schema(config, jobs):
     for job in jobs:
         job['label'] = job['name']
+        validate_schema(
+            google_play_description_schema, job,
+            "In GooglePlayStrings ({!r} kind) task for {!r}:".format(config.kind, job['label'])
+        )
         yield job
 
 
 @transforms.add
 def set_worker_data(config, jobs):
     for job in jobs:
         worker = job['worker']
 
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -104,17 +104,24 @@ job_description_schema = Schema({
     Required('worker-type'): task_description_schema['worker-type'],
 
     # This object will be passed through to the task description, with additions
     # provided by the job's run-using function
     Optional('worker'): dict,
 })
 
 transforms = TransformSequence()
-transforms.add_validate(job_description_schema)
+
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(job_description_schema, job,
+                        "In job {!r}:".format(job.get('name', job.get('label'))))
+        yield job
 
 
 @transforms.add
 def rewrite_when_to_optimization(config, jobs):
     for job in jobs:
         when = job.pop('when', {})
         if not when:
             yield job
--- a/taskcluster/taskgraph/transforms/l10n.py
+++ b/taskcluster/taskgraph/transforms/l10n.py
@@ -11,16 +11,17 @@ import copy
 import json
 
 from mozbuild.chunkify import chunkify
 from taskgraph.loader.multi_dep import schema
 from taskgraph.transforms.base import (
     TransformSequence,
 )
 from taskgraph.util.schema import (
+    validate_schema,
     optionally_keyed_by,
     resolve_keyed_by,
 )
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.util.treeherder import add_suffix
 from taskgraph.transforms.job import job_description_schema
 from taskgraph.transforms.task import task_description_schema
@@ -208,17 +209,22 @@ def copy_in_useful_magic(config, jobs):
         attributes = copy_attributes_from_dependent_job(dep)
         attributes.update(job.get('attributes', {}))
         # build-platform is needed on `job` for by-build-platform
         job['build-platform'] = attributes.get("build_platform")
         job['attributes'] = attributes
         yield job
 
 
-transforms.add_validate(l10n_description_schema)
+@transforms.add
+def validate_early(config, jobs):
+    for job in jobs:
+        validate_schema(l10n_description_schema, job,
+                        "In job {!r}:".format(job.get('name', 'unknown')))
+        yield job
 
 
 @transforms.add
 def setup_nightly_dependency(config, jobs):
     """ Sets up a task dependency to the signing job this relates to """
     for job in jobs:
         job['dependencies'] = {'build': job['dependent-tasks']['build'].label}
         if job['attributes']['build_platform'].startswith('win') or \
@@ -364,17 +370,22 @@ def mh_options_replace_project(config, j
     for job in jobs:
         job['mozharness']['options'] = map(
             lambda x: x.format(project=config.params['project']),
             job['mozharness']['options']
             )
         yield job
 
 
-transforms.add_validate(l10n_description_schema)
+@transforms.add
+def validate_again(config, jobs):
+    for job in jobs:
+        validate_schema(l10n_description_schema, job,
+                        "In job {!r}:".format(job.get('name', 'unknown')))
+        yield job
 
 
 @transforms.add
 def stub_installer(config, jobs):
     for job in jobs:
         job.setdefault('attributes', {})
         job.setdefault('env', {})
         if job["attributes"].get('stub-installer'):
--- a/taskcluster/taskgraph/transforms/push_apk.py
+++ b/taskcluster/taskgraph/transforms/push_apk.py
@@ -6,22 +6,25 @@ Transform the push-apk kind into an actu
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import re
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.task import task_description_schema
-from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by, Schema
+from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by, Schema, validate_schema
 from taskgraph.util.scriptworker import get_push_apk_scope
 from taskgraph.util.taskcluster import get_artifact_prefix
 
 from voluptuous import Optional, Required
 
+
+transforms = TransformSequence()
+
 # 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()}
 
 
 push_apk_description_schema = Schema({
     Required('dependent-tasks'): object,
     Required('name'): basestring,
@@ -43,18 +46,26 @@ push_apk_description_schema = Schema({
 
 
 REQUIRED_ARCHITECTURES = {
     'android-x86-nightly',
     'android-api-16-nightly',
 }
 PLATFORM_REGEX = re.compile(r'build-signing-android-(\S+)-nightly')
 
-transforms = TransformSequence()
-transforms.add_validate(push_apk_description_schema)
+
+@transforms.add
+def validate_jobs_schema_transform_partial(config, jobs):
+    for job in jobs:
+        label = job.get('label', '?no-label?')
+        validate_schema(
+            push_apk_description_schema, job,
+            "In PushApk ({!r} kind) task for {!r}:".format(config.kind, label)
+        )
+        yield job
 
 
 @transforms.add
 def validate_dependent_tasks(_, jobs):
     for job in jobs:
         check_every_architecture_is_present_in_dependent_tasks(job['dependent-tasks'])
         yield job
 
--- a/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py
+++ b/taskcluster/taskgraph/transforms/release_beetmover_signed_addons.py
@@ -6,17 +6,17 @@ Transform the beetmover task into an act
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
-from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by
+from taskgraph.util.schema import validate_schema, optionally_keyed_by, resolve_keyed_by
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope)
 from taskgraph.transforms.task import task_description_schema
 from taskgraph.transforms.release_sign_and_push_langpacks import get_upstream_task_ref
 from voluptuous import Required, Optional
 
 import logging
 import copy
@@ -58,17 +58,24 @@ def set_label(config, jobs):
     for job in jobs:
         job['label'] = job['primary-dependency'].label.replace(
             'sign-and-push-langpacks', 'beetmover-signed-langpacks'
         )
 
         yield job
 
 
-transforms.add_validate(beetmover_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(
+            beetmover_description_schema, job,
+            "In beetmover ({!r} kind) task for {!r}:".format(config.kind, job['label'])
+        )
+        yield job
 
 
 @transforms.add
 def resolve_keys(config, jobs):
     for job in jobs:
         resolve_keyed_by(
             job, 'worker-type', item_name=job['label'],
             **{'release-level': config.params.release_level()}
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_beetmover.py
@@ -4,16 +4,17 @@
 """
 Transform the `release-generate-checksums-beetmover` task to also append `build` as dependency
 """
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (get_beetmover_bucket_scope,
                                          get_beetmover_action_scope,
                                          get_worker_type_for_scope,
                                          )
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.transforms.beetmover import craft_release_properties
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
@@ -33,34 +34,43 @@ CHECKSUMS_SIGNING_ARTIFACTS = [
     "SHA512SUMS.asc"
 ]
 
 
 # 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()}
 
+transforms = TransformSequence()
+
 release_generate_checksums_beetmover_schema = schema.extend({
     # depname is used in taskref's to identify the taskID of the unsigned things
     Required('depname', default='build'): basestring,
 
     # unique label to describe this beetmover task, defaults to {dep.label}-beetmover
     Optional('label'): basestring,
 
     # treeherder is allowed here to override any defaults we use for beetmover.  See
     # taskcluster/taskgraph/transforms/task.py for the schema details, and the
     # below transforms for defaults of various values.
     Optional('treeherder'): task_description_schema['treeherder'],
 
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(release_generate_checksums_beetmover_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            release_generate_checksums_beetmover_schema, job,
+            "In ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = copy_attributes_from_dependent_job(dep_job)
 
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
@@ -5,39 +5,49 @@
 Transform the release-generate-checksums-signing task into task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
     add_scope_prefix,
 )
 from taskgraph.util.taskcluster import get_artifact_path
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 release_generate_checksums_signing_schema = schema.extend({
     Required('depname', default='release-generate-checksums'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(release_generate_checksums_signing_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            release_generate_checksums_signing_schema, job,
+            "In ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_release_generate_checksums_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = copy_attributes_from_dependent_job(dep_job)
 
--- a/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
+++ b/taskcluster/taskgraph/transforms/release_sign_and_push_langpacks.py
@@ -5,24 +5,29 @@
 Transform the release-sign-and-push task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
-from taskgraph.util.schema import resolve_keyed_by, optionally_keyed_by
+from taskgraph.util.schema import validate_schema, resolve_keyed_by, optionally_keyed_by
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required
 
+
+transforms = TransformSequence()
+
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
+
 transforms = TransformSequence()
 
+
 langpack_sign_push_description_schema = schema.extend({
     Required('label'): basestring,
     Required('description'): basestring,
     Required('worker-type'): optionally_keyed_by('release-level', basestring),
     Required('worker'): {
         Required('implementation'): 'sign-and-push-addons',
         Required('channel'): optionally_keyed_by(
             'project',
@@ -41,17 +46,24 @@ langpack_sign_push_description_schema = 
 def set_label(config, jobs):
     for job in jobs:
         label = 'sign-and-push-langpacks-{}'.format(job['primary-dependency'].label)
         job['label'] = label
 
         yield job
 
 
-transforms.add_validate(langpack_sign_push_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(
+            langpack_sign_push_description_schema, job,
+            'In sign-and-push-langpacks ({} kind) task for {}:'.format(config.kind, job['label'])
+        )
+        yield job
 
 
 @transforms.add
 def resolve_keys(config, jobs):
     for job in jobs:
         resolve_keyed_by(
             job, 'worker-type', item_name=job['label'],
             **{'release-level': config.params.release_level()}
--- a/taskcluster/taskgraph/transforms/release_snap_push.py
+++ b/taskcluster/taskgraph/transforms/release_snap_push.py
@@ -4,41 +4,53 @@
 """
 Transform the release-snap-push kind into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.task import task_description_schema
-from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by, Schema
+from taskgraph.util.schema import optionally_keyed_by, resolve_keyed_by, Schema, validate_schema
 
 from voluptuous import Optional, Required
 
+
+transforms = TransformSequence()
+
 # 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()}
 
+
 push_snap_description_schema = Schema({
     Required('name'): basestring,
     Required('job-from'): task_description_schema['job-from'],
     Required('dependencies'): task_description_schema['dependencies'],
     Required('description'): task_description_schema['description'],
     Required('treeherder'): task_description_schema['treeherder'],
     Required('run-on-projects'): task_description_schema['run-on-projects'],
     Required('worker-type'): optionally_keyed_by('release-level', basestring),
     Required('worker'): object,
     Required('scopes'): optionally_keyed_by('project', [basestring]),
     Required('shipping-phase'): task_description_schema['shipping-phase'],
     Required('shipping-product'): task_description_schema['shipping-product'],
     Optional('extra'): task_description_schema['extra'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(push_snap_description_schema)
+
+@transforms.add
+def validate_jobs_schema_transform(config, jobs):
+    for job in jobs:
+        label = job.get('label', '?no-label?')
+        validate_schema(
+            push_snap_description_schema, job,
+            "In release_snap_push ({!r} kind) task for {!r}:".format(config.kind, label)
+        )
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         if len(job['dependencies']) != 1:
             raise Exception('Exactly 1 dependency is required')
 
--- a/taskcluster/taskgraph/transforms/repackage.py
+++ b/taskcluster/taskgraph/transforms/repackage.py
@@ -8,25 +8,28 @@ Transform the repackage task into an act
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import (
+    validate_schema,
     optionally_keyed_by,
     resolve_keyed_by,
 )
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.util.platforms import archive_format, executable_extension
 from taskgraph.util.workertypes import worker_type_implementation
 from taskgraph.transforms.job import job_description_schema
 from voluptuous import Any, Required, Optional
 
+transforms = TransformSequence()
+
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}
 
 
 # shortcut for a string where task references are allowed
 taskref_or_string = Any(
     basestring,
@@ -148,18 +151,25 @@ PACKAGE_FORMATS = {
         'output': 'target.stub-installer.exe',
     },
 }
 MOZHARNESS_EXPANSIONS = [
     'package-name', 'installer-tag', 'fetch-dir',
     'stub-installer-tag', 'sfx-stub', 'wsx-stub',
 ]
 
-transforms = TransformSequence()
-transforms.add_validate(packaging_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            packaging_description_schema, job,
+            "In packaging ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def copy_in_useful_magic(config, jobs):
     """Copy attributes from upstream task to be used for keyed configuration."""
     for job in jobs:
         dep = job['primary-dependency']
         job['build-platform'] = dep.attributes.get("build_platform")
--- a/taskcluster/taskgraph/transforms/repackage_partner.py
+++ b/taskcluster/taskgraph/transforms/repackage_partner.py
@@ -8,27 +8,30 @@ Transform the repackage task into an act
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import (
+    validate_schema,
     optionally_keyed_by,
     resolve_keyed_by,
 )
 from taskgraph.util.taskcluster import get_artifact_prefix
 from taskgraph.util.partners import check_if_partners_enabled
 from taskgraph.util.platforms import archive_format, executable_extension
 from taskgraph.util.workertypes import worker_type_implementation
 from taskgraph.transforms.task import task_description_schema
 from taskgraph.transforms.repackage import PACKAGE_FORMATS
 from voluptuous import Any, Required, Optional
 
+transforms = TransformSequence()
+
 # 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()}
 
 
 def _by_platform(arg):
     return optionally_keyed_by('build-platform', arg)
 
@@ -67,19 +70,27 @@ packaging_description_schema = schema.ex
         Optional('config-paths'): [basestring],
 
         # if true, perform a checkout of a comm-central based branch inside the
         # gecko checkout
         Required('comm-checkout', default=False): bool,
     }
 })
 
-transforms = TransformSequence()
 transforms.add(check_if_partners_enabled)
-transforms.add_validate(packaging_description_schema)
+
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            packaging_description_schema, job,
+            "In packaging ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def copy_in_useful_magic(config, jobs):
     """Copy attributes from upstream task to be used for keyed configuration."""
     for job in jobs:
         dep = job['primary-dependency']
         job['build-platform'] = dep.attributes.get("build_platform")
--- a/taskcluster/taskgraph/transforms/repackage_routes.py
+++ b/taskcluster/taskgraph/transforms/repackage_routes.py
@@ -3,21 +3,33 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
 Add indexes to repackage kinds
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
+from taskgraph.util.schema import validate_schema
+from taskgraph.transforms.job import job_description_schema
 
 transforms = TransformSequence()
 
 
 @transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job['label']
+        validate_schema(
+            job_description_schema, job,
+            "In repackage-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
+
+
+@transforms.add
 def add_indexes(config, jobs):
     for job in jobs:
         repackage_type = job['attributes'].get('repackage_type')
         if repackage_type:
             build_platform = job['attributes']['build_platform']
             job_name = '{}-{}'.format(build_platform, repackage_type)
             product = job.get('index', {}).get('product', 'firefox')
             index_type = 'generic'
--- a/taskcluster/taskgraph/transforms/repackage_signing.py
+++ b/taskcluster/taskgraph/transforms/repackage_signing.py
@@ -7,46 +7,56 @@ Transform the repackage signing task int
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import os
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     add_scope_prefix,
     get_signing_cert_scope_per_platform,
     get_worker_type_for_scope,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 repackage_signing_description_schema = schema.extend({
     Required('depname', default='repackage'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 SIGNING_FORMATS = {
     'target.complete.mar': ["autograph_hash_only_mar384"],
     'target.bz2.complete.mar': ["mar"],
     "target.installer.exe": ["sha2signcode"],
     "target.stub-installer.exe": ["sha2signcodestub"],
     "target.installer.msi": ["sha2signcode"],
 }
 
-transforms = TransformSequence()
-transforms.add_validate(repackage_signing_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            repackage_signing_description_schema, job,
+            "In repackage-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_repackage_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = copy_attributes_from_dependent_job(dep_job)
         locale = attributes.get('locale', dep_job.attributes.get('locale'))
--- a/taskcluster/taskgraph/transforms/repackage_signing_partner.py
+++ b/taskcluster/taskgraph/transforms/repackage_signing_partner.py
@@ -6,16 +6,17 @@ Transform the repackage signing task int
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.partners import check_if_partners_enabled
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     add_scope_prefix,
     get_signing_cert_scope_per_platform,
 )
 from taskgraph.util.taskcluster import get_artifact_path
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
@@ -29,17 +30,26 @@ repackage_signing_description_schema = s
     Required('depname', default='repackage'): basestring,
     Optional('label'): basestring,
     Optional('extra'): object,
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
 transforms.add(check_if_partners_enabled)
-transforms.add_validate(repackage_signing_description_schema)
+
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            repackage_signing_description_schema, job,
+            "In repackage-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_repackage_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         repack_id = dep_job.task['extra']['repack_id']
         attributes = dep_job.attributes
--- a/taskcluster/taskgraph/transforms/signing.py
+++ b/taskcluster/taskgraph/transforms/signing.py
@@ -5,16 +5,17 @@
 Transform the signing task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     add_scope_prefix,
     get_signing_cert_scope_per_platform,
     get_worker_type_for_scope,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
@@ -72,17 +73,24 @@ signing_description_schema = schema.exte
 
 @transforms.add
 def set_defaults(config, jobs):
     for job in jobs:
         job.setdefault('depname', 'build')
         yield job
 
 
-transforms.add_validate(signing_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            signing_description_schema, job,
+            "In signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_task_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/source_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/source_checksums_signing.py
@@ -5,42 +5,52 @@
 Transform the checksums signing task into an actual task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.loader.single_dep import schema
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
+from taskgraph.util.schema import validate_schema
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
     add_scope_prefix,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # 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()}
 
+transforms = TransformSequence()
+
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
 checksums_signing_description_schema = schema.extend({
     Required('depname', default='beetmover'): basestring,
     Optional('label'): basestring,
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 })
 
-transforms = TransformSequence()
-transforms.add_validate(checksums_signing_description_schema)
+
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        label = job.get('primary-dependency', object).__dict__.get('label', '?no-label?')
+        validate_schema(
+            checksums_signing_description_schema, job,
+            "In checksums-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+        yield job
 
 
 @transforms.add
 def make_checksums_signing_description(config, jobs):
     for job in jobs:
         dep_job = job['primary-dependency']
         attributes = dep_job.attributes
 
--- a/taskcluster/taskgraph/transforms/source_test.py
+++ b/taskcluster/taskgraph/transforms/source_test.py
@@ -1,8 +1,9 @@
+# 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/.
 """
 Source-test jobs can run on multiple platforms.  These transforms allow jobs
 with either `platform` or a list of `platforms`, and set the appropriate
 treeherder configuration and attributes for that platform.
 """
 
@@ -10,16 +11,17 @@ from __future__ import absolute_import, 
 
 import copy
 import os
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.job import job_description_schema
 from taskgraph.util.attributes import keymatch
 from taskgraph.util.schema import (
+    validate_schema,
     resolve_keyed_by,
 )
 from taskgraph.util.treeherder import join_symbol, split_symbol
 
 from voluptuous import (
     Any,
     Extra,
     Optional,
@@ -65,17 +67,22 @@ transforms = TransformSequence()
 
 @transforms.add
 def set_defaults(config, jobs):
     for job in jobs:
         job.setdefault('require-build', False)
         yield job
 
 
-transforms.add_validate(source_test_description_schema)
+@transforms.add
+def validate(config, jobs):
+    for job in jobs:
+        validate_schema(source_test_description_schema, job,
+                        "In job {!r}:".format(job['name']))
+        yield job
 
 
 @transforms.add
 def set_job_name(config, jobs):
     for job in jobs:
         if 'job-from' in job and job['job-from'] != 'kind.yml':
             from_name = os.path.splitext(job['job-from'])[0]
             job['name'] = '{}-{}'.format(from_name, job['name'])
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -20,16 +20,17 @@ for example - use `all_tests.py` instead
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import match_run_on_projects
 from taskgraph.util.schema import resolve_keyed_by, OptimizationSchema
 from taskgraph.util.treeherder import split_symbol, join_symbol, add_suffix
 from taskgraph.util.platforms import platform_family
 from taskgraph.util.schema import (
+    validate_schema,
     optionally_keyed_by,
     Schema,
 )
 from taskgraph.util.taskcluster import get_artifact_path
 from mozbuild.schedules import INCLUSIVE_COMPONENTS
 
 from taskgraph.util.perfile import perfile_number_of_chunks
 
@@ -475,17 +476,22 @@ def set_defaults(config, tests):
         test['mozharness'].setdefault('requires-signed-builds', False)
         test['mozharness'].setdefault('tooltool-downloads', False)
         test['mozharness'].setdefault('set-moz-node-path', False)
         test['mozharness'].setdefault('chunked', False)
         test['mozharness'].setdefault('chunking-args', 'this-chunk')
         yield test
 
 
-transforms.add_validate(test_description_schema)
+@transforms.add
+def validate(config, tests):
+    for test in tests:
+        validate_schema(test_description_schema, test,
+                        "In test {!r}:".format(test['test-name']))
+        yield test
 
 
 @transforms.add
 def resolve_keys(config, tests):
     for test in tests:
         resolve_keyed_by(
             test, 'requite-signed-extensions',
             item_name=test['test-name'],