Bug 1383880: WIP draft
authorDustin J. Mitchell <dustin@mozilla.com>
Tue, 25 Jul 2017 01:15:59 +0000
changeset 615392 469fd4e4914345854396c186079bfa57c7d65afb
parent 614544 462d7561089c98e33382384896434861ad7bc491
child 639160 dc8d56685eea130ad44b7208a870d027adf5133b
push id70339
push userdmitchell@mozilla.com
push dateTue, 25 Jul 2017 21:56:47 +0000
bugs1383880
milestone56.0a1
Bug 1383880: WIP MozReview-Commit-ID: 5UCS6JaNn3d
mobile/android/moz.build
moz.build
python/mozbuild/mozbuild/affects.py
python/mozbuild/mozbuild/frontend/context.py
python/mozbuild/mozbuild/frontend/mach_commands.py
python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build
taskcluster/ci/build/android.yml
taskcluster/ci/build/linux.yml
taskcluster/ci/build/macosx.yml
taskcluster/ci/build/windows.yml
taskcluster/taskgraph/optimize.py
taskcluster/taskgraph/transforms/task.py
--- a/mobile/android/moz.build
+++ b/mobile/android/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox for Android', 'Build Config & IDE Support')
+    AFFECTS_ONLY.platform_family = ['android']
 
 with Files('bouncer/**'):
     BUG_COMPONENT = ('Firefox for Android', 'Distributions')
 
 with Files('branding/**'):
     BUG_COMPONENT = ('Firefox for Android', 'General')
 
 with Files('build/**'):
--- a/moz.build
+++ b/moz.build
@@ -34,16 +34,17 @@ with Files('mach'):
 with Files('*moz*'):
     BUG_COMPONENT = ('Core', 'Build Config')
 
 with Files('GNUmakefile'):
     BUG_COMPONENT = ('Core', 'Build Config')
 
 with Files('*gradle*'):
     BUG_COMPONENT = ('Firefox for Android', 'Build Config & IDE Support')
+    AFFECTS_ONLY.platform_family = ['android']
 
 with Files('**/l10n.toml'):
     BUG_COMPONENT = ('Core', 'Localization')
     FINAL = True
 
 with Files('README.txt'):
     BUG_COMPONENT = ('Core', 'General')
 
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/affects.py
@@ -0,0 +1,42 @@
+# 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/.
+
+r"""
+This module contains the configuration for the AFFECTS_ONLY mozbuild variable.
+
+TODO: doc affects in general here
+"""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+import os
+
+from .testing import all_test_flavors
+
+# Enumeration of class names and allowed instances of that class
+AFFECTS_ONLY_CLASSES = {
+    'test_flavor': set(all_test_flavors()),
+    'platform_family': {'android', 'linux', 'macosx', 'windows'},
+}
+
+TOPSRCDIR = os.path.abspath(os.path.join(__file__, '../../../../'))
+
+def affected_by_paths(paths):
+    """Calculate what is affected by the combined effect of these paths, based
+    on AFFECTS_ONLY variables in moz.build files.  Returns a set of
+    "class:instance" strings indicating what might be affected."""
+    from .frontend import reader
+    affects = {}
+
+    config = reader.EmptyConfig(TOPSRCDIR)
+    reader = reader.BuildReader(config)
+    for p, m in reader.files_info(paths).items():
+        affects_only = m['AFFECTS_ONLY']
+        for class_name, full_set in AFFECTS_ONLY_CLASSES.items():
+            # not specifying AFFECTS_ONLY means it affects all instances in the class
+            instances = set(affects_only[class_name]) or full_set
+            class_instances = affects.setdefault(class_name, set())
+            class_instances |= instances
+
+    return affects
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -32,16 +32,17 @@ from mozbuild.util import (
     ReadOnlyKeyedDefaultDict,
     StrictOrderingOnAppendList,
     StrictOrderingOnAppendListWithAction,
     StrictOrderingOnAppendListWithFlagsFactory,
     TypedList,
     TypedNamedTuple,
 )
 
+from ..affects import AFFECTS_ONLY_CLASSES
 from ..testing import (
     all_test_flavors,
     read_manifestparser_manifest,
     read_reftest_manifest,
     read_wpt_manifest,
 )
 
 import mozpack.path as mozpath
@@ -545,16 +546,21 @@ def ContextDerivedTypedRecord(*fields):
 
         def __init__(self, context):
             for fname, ftype in self._fields.items():
                 if issubclass(ftype, ContextDerivedValue):
                     setattr(self, fname, self._fields[fname](context))
                 else:
                     setattr(self, fname, self._fields[fname]())
 
+        def __getitem__(self, name):
+            if name in self._fields:
+                return getattr(self, name)
+            raise KeyError(name)
+
         def __setattr__(self, name, value):
             if name in self._fields and not isinstance(value, self._fields[name]):
                 value = self._fields[name](value)
             object.__setattr__(self, name, value)
 
     _TypedRecord._fields = dict(fields)
     return _TypedRecord
 
@@ -618,22 +624,28 @@ WebPlatformTestManifest = TypedNamedTupl
                                            ("test_root", unicode)])
 ManifestparserManifestList = OrderedListWithAction(read_manifestparser_manifest)
 ReftestManifestList = OrderedListWithAction(read_reftest_manifest)
 WptManifestList = TypedListWithAction(WebPlatformTestManifest, read_wpt_manifest)
 
 OrderedSourceList = ContextDerivedTypedList(SourcePath, StrictOrderingOnAppendList)
 OrderedTestFlavorList = TypedList(Enum(*all_test_flavors()),
                                   StrictOrderingOnAppendList)
+OrderedPlatformFamilyList = TypedList(Enum('android', 'macosx', 'windows', 'linux'),
+                                      StrictOrderingOnAppendList)
 OrderedStringList = TypedList(unicode, StrictOrderingOnAppendList)
 DependentTestsEntry = ContextDerivedTypedRecord(('files', OrderedSourceList),
                                                 ('tags', OrderedStringList),
                                                 ('flavors', OrderedTestFlavorList))
 BugzillaComponent = TypedNamedTuple('BugzillaComponent',
                         [('product', unicode), ('component', unicode)])
+AffectsOnlyEntry = ContextDerivedTypedRecord(*sorted(
+    (class_name, TypedList(Enum(*class_instances)))
+    for class_name, class_instances in AFFECTS_ONLY_CLASSES.items()
+    ))
 
 
 class Files(SubContext):
     """Metadata attached to files.
 
     It is common to want to annotate files with metadata, such as which
     Bugzilla component tracks issues with certain files. This sub-context is
     where we stick that metadata.
@@ -742,16 +754,34 @@ class Files(SubContext):
             with Files('dom/base/nsGlobalWindow.cpp'):
                 IMPACTED_TESTS.flavors += [
                     'mochitest',
                 ]
 
             Would suggest that nsGlobalWindow.cpp is potentially relevant to
             any plain mochitest.
             """),
+        'AFFECTS_ONLY': (AffectsOnlyEntry, list,
+            """Maps source files to the limited sets of CI tasks whose outcome
+            they could possibly affect.
+
+            The sets of CI tasks are grouped into classes. For example,
+            `android` and `linux` are both in the `pltaform_family` class.
+            Each class represents a topical labeling of CI tasks, where a task
+            may have zero or one labels in each class.
+
+            A file annotated with `AFFECTS_ONLY.platform_family = ['android']`
+            is treated as affecting only android-specific tasks. Thus if this
+            is the only file modified in a push, then any task not labeled with
+            `platform_family` `android' can be safely skipped.
+
+            A file that is not annotated is assumed to belong to all sets in
+            the class. For example, `dom/moz.build` belongs to all platform
+            families.
+            """),
     }
 
     def __init__(self, parent, pattern=None):
         super(Files, self).__init__(parent)
         self.pattern = pattern
         self.finalized = set()
         self.test_files = set()
         self.test_tags = set()
--- a/python/mozbuild/mozbuild/frontend/mach_commands.py
+++ b/python/mozbuild/mozbuild/frontend/mach_commands.py
@@ -216,8 +216,28 @@ class MozbuildFileCommands(MachCommandBa
 
             for path, f in finder.find(p):
                 if path not in all_paths_set:
                     all_paths_set.add(path)
                     allpaths.append(path)
 
         reader = self._get_reader(finder=reader_finder)
         return reader.files_info(allpaths)
+
+
+    @SubCommand('file-info', 'affects',
+                'Show what is affected by the files listed.')
+    @CommandArgument('paths', nargs='+',
+                     help='Paths whose data to query')
+    def file_info_affects(self, paths):
+        """Show what is affected by the given files.
+
+        Given a requested set of files (which can be specified using
+        wildcards), print the total set of affected things.
+        """
+        from mozbuild.affects import affected_by_paths, AFFECTS_ONLY_CLASSES
+        affected = affected_by_paths(paths)
+        width = max(len(c) for c in affected)
+        for class_name in sorted(affected):
+            class_instances = affected[class_name]
+            if class_instances == AFFECTS_ONLY_CLASSES[class_name]:
+                class_instances = ['ALL']
+            print("{:{width}} -- {}".format(class_name, ", ".join(class_instances), width=width))
--- a/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build
+++ b/python/mozbuild/mozbuild/test/frontend/data/files-test-metadata/tagged/moz.build
@@ -5,11 +5,12 @@ with Files('src/submodule/**'):
 
 with Files('src/bar.jsm'):
     IMPACTED_TESTS.flavors += [
         'browser-chrome',
     ]
     IMPACTED_TESTS.files += [
         '**.js',
     ]
+    AFFECTS_ONLY.test_flavor = ['browser-chrome']
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
--- a/taskcluster/ci/build/android.yml
+++ b/taskcluster/ci/build/android.yml
@@ -6,16 +6,18 @@ android-api-15/debug:
     treeherder:
         platform: android-4-0-armv7-api15/debug
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -31,16 +33,18 @@ android-x86/opt:
     treeherder:
         platform: android-4-2-x86/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -59,16 +63,18 @@ android-x86-nightly/opt:
     treeherder:
         platform: android-4-2-x86/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
@@ -85,16 +91,18 @@ android-api-15/opt:
     treeherder:
         platform: android-4-0-armv7-api15/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -113,16 +121,18 @@ android-api-15-nightly/opt:
     treeherder:
         platform: android-4-0-armv7-api15/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
@@ -139,16 +149,18 @@ android-x86-old-id/opt:
     treeherder:
         platform: android-4-2-x86-old-id/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -168,16 +180,18 @@ android-x86-old-id-nightly/opt:
     treeherder:
         platform: android-4-2-x86-old-id/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android-x86/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
@@ -195,16 +209,18 @@ android-api-15-old-id/opt:
     treeherder:
         platform: android-4-0-armv7-api15-old-id/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -224,16 +240,18 @@ android-api-15-old-id-nightly/opt:
     treeherder:
         platform: android-4-0-armv7-api15-old-id/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
@@ -264,16 +282,18 @@ android-api-15-gradle/opt:
             path: /home/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
             type: directory
           - name: public/build/geckoview_example.apk
             path: /home/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
             type: file
           - name: public/build
             path: /home/worker/artifacts/
             type: directory
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -289,16 +309,18 @@ android-aarch64/opt:
     treeherder:
         platform: android-5-0-aarch64/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         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"
@@ -317,16 +339,18 @@ android-aarch64-nightly/opt:
     treeherder:
         platform: android-5-0-aarch64/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-android
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "mobile/android/config/tooltool-manifests/android/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:android"]
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
             - builds/releng_base_android_64_builds.py
             - disable_signing.py
             - platform_supports_post_upload_to_latest.py
             - taskcluster_nightly.py
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -6,16 +6,18 @@ linux64/opt:
     treeherder:
         platform: linux64/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -31,16 +33,18 @@ linux64-dmd/opt:
         platform: linux64-dmd/opt
         symbol: tc(Bdmd)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
         script: "mozharness/scripts/fx_desktop_build.py"
         custom-build-variant-cfg: dmd
         secrets: true
@@ -57,16 +61,18 @@ linux64/pgo:
         platform: linux64/pgo
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
     coalesce-name: linux64-pgo
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         options: [enable-pgo]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
@@ -82,16 +88,18 @@ linux64/debug:
     treeherder:
         platform: linux64/debug
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -110,16 +118,18 @@ linux64-devedition-nightly/opt:
     treeherder:
         platform: linux64-devedition/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - disable_signing.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
@@ -138,16 +148,18 @@ linux64-base-toolchains/opt:
         platform: linux64/opt
         symbol: tc(Bb)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             PERFHERDER_EXTRA_OPTIONS: base-toolchains
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/base-toolchains.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -163,16 +175,18 @@ linux64-base-toolchains/debug:
         platform: linux64/debug
         symbol: tc(Bb)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             PERFHERDER_EXTRA_OPTIONS: base-toolchains
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/base-toolchains.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -189,16 +203,18 @@ linux/opt:
         platform: linux32/opt
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux32/releng.manifest"
     coalesce-name: opt_linux32
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_32_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -214,16 +230,18 @@ linux/debug:
         platform: linux32/debug
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux32/releng.manifest"
     coalesce-name: dbg_linux32
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_32_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -240,16 +258,18 @@ linux/pgo:
         platform: linux32/pgo
         symbol: tc(B)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux32/releng.manifest"
     coalesce-name: linux32-pgo
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         options: [enable-pgo]
         config:
             - builds/releng_base_linux_32_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
@@ -268,16 +288,18 @@ linux-devedition-nightly/opt:
     treeherder:
         platform: linux32-devedition/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_32_builds.py
             - disable_signing.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
@@ -298,16 +320,18 @@ linux-nightly/opt:
     treeherder:
         platform: linux32/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_32_builds.py
             - disable_signing.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
@@ -325,16 +349,18 @@ linux64-asan/opt:
         platform: linux64/asan
         symbol: tc(Bo)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         env:
             PERFHERDER_EXTRA_OPTIONS: "opt asan"
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/asan.manifest"
         max-run-time: 36000
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -352,16 +378,18 @@ linux64-asan-fuzzing/opt:
         platform: linux64/asan
         symbol: tc(Bof)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         env:
             PERFHERDER_EXTRA_OPTIONS: asan-fuzzing
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/fuzzing.manifest"
         max-run-time: 36000
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -378,16 +406,18 @@ linux64-asan/debug:
         platform: linux64/asan
         symbol: tc(Bd)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         env:
             PERFHERDER_EXTRA_OPTIONS: "debug asan"
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/asan.manifest"
         max-run-time: 36000
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -406,16 +436,18 @@ linux64-nightly/opt:
     treeherder:
         platform: linux64/opt
         symbol: tc(N)
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - disable_signing.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
@@ -432,16 +464,18 @@ linux64-noopt/debug:
         platform: linux64-noopt/debug
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 3600
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -461,16 +495,18 @@ linux64-jsdcov/opt:
         symbol: tc(B)
         tier: 2
     run-on-projects: [ ]
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -488,16 +524,18 @@ linux64-ccov/opt:
         symbol: tc(B)
         tier: 2
     run-on-projects: [ ]
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -514,16 +552,18 @@ linux64-add-on-devel/opt:
         platform: linux64-add-on-devel/opt
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/linux64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:linux"]
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_linux_64_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
--- a/taskcluster/ci/build/macosx.yml
+++ b/taskcluster/ci/build/macosx.yml
@@ -7,16 +7,18 @@ macosx64/debug:
         platform: osx-cross/debug
         symbol: tc(B)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_mac_64_cross_builds.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         custom-build-variant-cfg: cross-debug
@@ -31,16 +33,18 @@ macosx64/opt:
         platform: osx-cross/opt
         symbol: tc(B)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_mac_64_cross_builds.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         tooltool-downloads: internal
@@ -54,16 +58,18 @@ macosx64-dmd/opt:
         platform: osx-10-10-dmd/opt
         symbol: tc(Bdmd)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_mac_64_cross_builds.py
         script: "mozharness/scripts/fx_desktop_build.py"
         custom-build-variant-cfg: dmd
         secrets: true
@@ -79,16 +85,18 @@ macosx64-devedition/opt:
         platform: osx-cross-devedition/opt
         symbol: tc(B)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_mac_64_cross_builds.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
         tooltool-downloads: internal
@@ -104,16 +112,18 @@ macosx64-noopt/debug:
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         docker-image: {in-tree: desktop-build}
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_mac_64_cross_builds.py
             - balrog/production.py
         script: "mozharness/scripts/fx_desktop_build.py"
         secrets: true
@@ -131,16 +141,18 @@ macosx64-add-on-devel/opt:
         platform: osx-cross-add-on-devel/opt
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
        using: mozharness
        actions: [get-secrets build update]
        config:
             - builds/releng_base_mac_64_cross_builds.py
             - balrog/production.py
        script: "mozharness/scripts/fx_desktop_build.py"
        secrets: true
@@ -160,16 +172,18 @@ macosx64-nightly/opt:
         platform: osx-cross/opt
         symbol: tc(N)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-macosx64
     worker:
         max-run-time: 36000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/macosx64/cross-releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:macosx"]
     run:
         using: mozharness
         actions: [get-secrets build update]
         config:
             - builds/releng_base_mac_64_cross_builds.py
             - disable_signing.py
             - taskcluster_nightly.py
         script: "mozharness/scripts/fx_desktop_build.py"
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -7,16 +7,18 @@ win32/debug:
         platform: windows2012-32/debug
         symbol: tc(B)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_debug.py
 
 win32/opt:
     description: "Win32 Opt"
@@ -27,16 +29,18 @@ win32/opt:
         platform: windows2012-32/opt
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_opt.py
 
 win32-dmd/opt:
     description: "Win32 DMD Opt"
@@ -47,16 +51,18 @@ win32-dmd/opt:
         platform: windows2012-32-dmd/opt
         symbol: tc(Bdmd)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_opt.py
         custom-build-variant-cfg: dmd
     run-on-projects: [ ]
 
@@ -69,16 +75,18 @@ win32/pgo:
         platform: windows2012-32/pgo
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 9000
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         options: [enable-pgo]
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_opt.py
 
 win64/debug:
@@ -90,16 +98,18 @@ win64/debug:
         platform: windows2012-64/debug
         symbol: tc(B)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_64_debug.py
 
 win64/opt:
     description: "Win64 Opt"
@@ -110,16 +120,18 @@ win64/opt:
         platform: windows2012-64/opt
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_64_opt.py
 
 win64-dmd/opt:
     description: "Win64 DMD Opt"
@@ -130,16 +142,18 @@ win64-dmd/opt:
         platform: windows2012-64-dmd/opt
         symbol: tc(Bdmd)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_64_opt.py
         custom-build-variant-cfg: dmd
     run-on-projects: [ ]
 
@@ -155,16 +169,18 @@ win32-nightly/opt:
         platform: windows2012-32/opt
         symbol: tc(N)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         actions: [clone-tools, build, check-test, update]
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_32_opt.py
             - disable_signing.py
             - taskcluster_nightly.py
@@ -181,16 +197,18 @@ win64-nightly/opt:
         platform: windows2012-64/opt
         symbol: tc(N)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         actions: [clone-tools, build, check-test, update]
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_64_opt.py
             - disable_signing.py
             - taskcluster_nightly.py
@@ -204,16 +222,18 @@ win64/pgo:
         platform: windows2012-64/pgo
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 10800
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         options: [enable-pgo]
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_windows_64_opt.py
 
 win32-add-on-devel/opt:
@@ -225,16 +245,18 @@ win32-add-on-devel/opt:
         platform: windows2012-32-add-on-devel/opt
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 10800
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: "mozharness/scripts/fx_desktop_build.py"
         config:
             - builds/taskcluster_firefox_windows_32_addondevel.py
             - balrog/production.py
     run-on-projects: [ 'mozilla-beta', 'mozilla-release', 'mozilla-esr45' ]
 
@@ -247,16 +269,18 @@ win64-add-on-devel/opt:
         platform: windows2012-64-add-on-devel/opt
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 10800
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: "mozharness/scripts/fx_desktop_build.py"
         config:
             - builds/taskcluster_firefox_windows_64_addondevel.py
             - balrog/production.py
     run-on-projects: [ 'mozilla-beta', 'mozilla-release', 'mozilla-esr45' ]
 
@@ -269,16 +293,18 @@ win64-noopt/debug:
         platform: windows2012-64-noopt/debug
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_win64_noopt_debug.py
     run-on-projects: [ 'trunk', 'try' ]
 
 win32-noopt/debug:
@@ -290,16 +316,18 @@ win32-noopt/debug:
         platform: windows2012-32-noopt/debug
         symbol: tc(B)
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win32/releng.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_win32_noopt_debug.py
     run-on-projects: [ 'trunk', 'try' ]
 
 win64-asan/debug:
@@ -311,16 +339,18 @@ win64-asan/debug:
         platform: windows2012-64/asan
         symbol: tc(Bd)
         tier: 3
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/clang.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_win64_asan_debug.py
     run-on-projects: []
 
 win64-asan/opt:
@@ -332,14 +362,16 @@ win64-asan/opt:
         platform: windows2012-64/asan
         symbol: tc(Bo)
         tier: 3
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         max-run-time: 7200
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/clang.manifest"
+    optimizations:
+      - [skip-unless-affects, "platform_family:windows"]
     run:
         using: mozharness
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/taskcluster_firefox_win64_asan_opt.py
     run-on-projects: []
--- a/taskcluster/taskgraph/optimize.py
+++ b/taskcluster/taskgraph/optimize.py
@@ -10,16 +10,18 @@ import os
 import requests
 
 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 slugid import nice as slugid
+from mozbuild.util import memoize
+from mozbuild.affects import affected_by_paths
 
 logger = logging.getLogger(__name__)
 TASK_REFERENCE_PATTERN = re.compile('<([^>]+)>')
 
 _optimizations = {}
 
 
 def optimize_task_graph(target_task_graph, params, do_not_optimize, existing_tasks=None):
@@ -155,33 +157,33 @@ def get_subgraph(annotated_task_graph, n
     # resolve labels to taskIds and populate task['dependencies']
     tasks_by_taskid = {}
     for label in annotated_task_graph.graph.visit_postorder():
         task = annotated_task_graph.tasks[label]
         if task.optimized:
             continue
         task.task_id = label_to_taskid[label] = slugid()
         named_task_dependencies = {
-                name: label_to_taskid[label]
-                for name, label in named_links_dict.get(label, {}).iteritems()}
+            name: label_to_taskid[label]
+            for name, label in named_links_dict.get(label, {}).iteritems()}
         task.task = resolve_task_references(task.label, task.task, named_task_dependencies)
         task.task.setdefault('dependencies', []).extend(named_task_dependencies.itervalues())
         tasks_by_taskid[task.task_id] = task
 
     # resolve edges to taskIds
     edges_by_taskid = (
         (label_to_taskid.get(left), label_to_taskid.get(right), name)
         for (left, right, name) in annotated_task_graph.graph.edges
-        )
+    )
     # ..and drop edges that are no longer in the task graph
     edges_by_taskid = set(
         (left, right, name)
         for (left, right, name) in edges_by_taskid
         if left in tasks_by_taskid and right in tasks_by_taskid
-        )
+    )
 
     return TaskGraph(
         tasks_by_taskid,
         Graph(set(tasks_by_taskid), edges_by_taskid))
 
 
 def optimization(name):
     def wrap(func):
@@ -237,8 +239,33 @@ def opt_files_changed(task, params, file
         return True
 
     changed = files_changed.check(params, file_patterns)
     if not changed:
         logger.debug('no files found matching a pattern in `skip-unless-changed` for ' +
                      task.label)
         return True
     return False
+
+
+@memoize
+def _affected_by_push(repository, revision):
+    changed_files = files_changed.get_changed_files(repository, revision)
+    affected = affected_by_paths(changed_files)
+    for class_name, class_instances in sorted(affected.items()):
+        logger.debug("affected {}'s: {}".format(class_name, ', '.join(class_instances)))
+    return set("{}:{}".format(class_name, class_instance)
+               for class_name, class_instances in affected.items()
+               for class_instance in class_instances)
+
+
+@optimization('skip-unless-affects')  # TODO: skip-unless-affected?
+def opt_affected(task, params, *conditions):
+    if params.get('pushlog_id') == -1:
+        return True
+
+    affected = _affected_by_push(params['head_repository'], params['head_rev'])
+    conditions = set(conditions)
+    if conditions <= affected:
+        # all of the conditions were affected, so do not optimize
+        return False
+
+    return True  # skip
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -137,16 +137,19 @@ task_description_schema = Schema({
     # taskcluster/taskgraph/optimize.py.
     Optional('optimizations'): [Any(
         # search the index for the given index namespace, and replace this task if found
         ['index-search', basestring],
         # consult SETA and skip this task if it is low-value
         ['seta'],
         # skip this task if none of the given file patterns match
         ['skip-unless-changed', [basestring]],
+        # skip this task if unless the changes affect all of the listed
+        # class:instance values TODO: validate valid class:instance strings
+        ['skip-unless-affected', [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.