Bug 1484264: [mozharness] Move repacakge config from mozharness to taskcluster; r=Callek
authorTom Prince <mozilla@hocat.ca>
Fri, 17 Aug 2018 09:40:57 -0600
changeset 482228 2c723487d70b82dbb84ee91e51c5f586e3cce6a1
parent 482227 64b1b8d041ac307768824c5e78a0a916a1e3c61c
child 482229 206ab6424edfa60b452fa5ca370132f463f07f0a
push id232
push userfmarier@mozilla.com
push dateWed, 05 Sep 2018 20:45:54 +0000
reviewersCallek
bugs1484264
milestone63.0a1
Bug 1484264: [mozharness] Move repacakge config from mozharness to taskcluster; r=Callek Differential Revision: https://phabricator.services.mozilla.com/D3624
taskcluster/ci/release-eme-free-repack-repackage/kind.yml
taskcluster/ci/release-partner-repack-repackage/kind.yml
taskcluster/ci/repackage-l10n/kind.yml
taskcluster/ci/repackage/kind.yml
taskcluster/taskgraph/transforms/repackage.py
taskcluster/taskgraph/transforms/repackage_partner.py
taskcluster/taskgraph/util/platforms.py
testing/mozharness/configs/repackage/base.py
testing/mozharness/configs/repackage/linux32_signed.py
testing/mozharness/configs/repackage/linux64_signed.py
testing/mozharness/configs/repackage/osx_partner.py
testing/mozharness/configs/repackage/osx_signed.py
testing/mozharness/configs/repackage/win32_partner.py
testing/mozharness/configs/repackage/win32_signed.py
testing/mozharness/configs/repackage/win64_partner.py
testing/mozharness/configs/repackage/win64_signed.py
testing/mozharness/scripts/repackage.py
--- a/taskcluster/ci/release-eme-free-repack-repackage/kind.yml
+++ b/taskcluster/ci/release-eme-free-repack-repackage/kind.yml
@@ -22,13 +22,21 @@ only-for-build-platforms:
    - win32-nightly/opt
    - win64-nightly/opt
 
 job-template:
    mozharness:
       config:
          by-build-platform:
             macosx64-.*:
+               - repackage/base.py
                - repackage/osx_partner.py
             win32-.*:
+               - repackage/base.py
                - repackage/win32_partner.py
             win64-.*:
+               - repackage/base.py
                - repackage/win64_partner.py
+   package-formats:
+      by-build-platform:
+         macosx64\b.*: [dmg]
+         win32\b.*: [installer]
+         win64\b.*: [installer]
--- a/taskcluster/ci/release-partner-repack-repackage/kind.yml
+++ b/taskcluster/ci/release-partner-repack-repackage/kind.yml
@@ -22,13 +22,21 @@ only-for-build-platforms:
    - win32-nightly/opt
    - win64-nightly/opt
 
 job-template:
    mozharness:
       config:
          by-build-platform:
             macosx64-.*:
+               - repackage/base.py
                - repackage/osx_partner.py
             win32-.*:
+               - repackage/base.py
                - repackage/win32_partner.py
             win64-.*:
+               - repackage/base.py
                - repackage/win64_partner.py
+   package-formats:
+      by-build-platform:
+         macosx64\b.*: [dmg]
+         win32\b.*: [installer]
+         win64\b.*: [installer]
--- a/taskcluster/ci/repackage-l10n/kind.yml
+++ b/taskcluster/ci/repackage-l10n/kind.yml
@@ -1,44 +1,56 @@
 # 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/.
 
 loader: taskgraph.loader.single_dep:loader
 
 transforms:
-   - taskgraph.transforms.repackage_l10n:transforms
-   - taskgraph.transforms.name_sanity:transforms
-   - taskgraph.transforms.repackage:transforms
-   - taskgraph.transforms.use_toolchains:transforms
-   - taskgraph.transforms.job:transforms
-   - taskgraph.transforms.task:transforms
+    - taskgraph.transforms.repackage_l10n:transforms
+    - taskgraph.transforms.name_sanity:transforms
+    - taskgraph.transforms.repackage:transforms
+    - taskgraph.transforms.use_toolchains:transforms
+    - taskgraph.transforms.job:transforms
+    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-   - nightly-l10n-signing
-   - toolchain
+    - nightly-l10n-signing
+    - toolchain
 
 only-for-build-platforms:
-   - linux-nightly/opt
-   - linux64-nightly/opt
-   - macosx64-nightly/opt
-   - win32-nightly/opt
-   - win64-nightly/opt
-   - linux-devedition-nightly/opt
-   - linux64-devedition-nightly/opt
-   - macosx64-devedition-nightly/opt
-   - win32-devedition-nightly/opt
-   - win64-devedition-nightly/opt
+    - linux-nightly/opt
+    - linux64-nightly/opt
+    - macosx64-nightly/opt
+    - win32-nightly/opt
+    - win64-nightly/opt
+    - linux-devedition-nightly/opt
+    - linux64-devedition-nightly/opt
+    - macosx64-devedition-nightly/opt
+    - win32-devedition-nightly/opt
+    - win64-devedition-nightly/opt
 
 job-template:
-   mozharness:
-      config:
-         by-build-platform:
-            linux-.*:
-               - repackage/linux32_signed.py
-            linux64-.*:
-               - repackage/linux64_signed.py
-            macosx64-.*:
-               - repackage/osx_signed.py
-            win32-.*:
-               - repackage/win32_signed.py
-            win64-.*:
-               - repackage/win64_signed.py
+    mozharness:
+        config:
+            by-build-platform:
+                linux-.*:
+                    - repackage/base.py
+                    - repackage/linux32_signed.py
+                linux64-.*:
+                    - repackage/base.py
+                    - repackage/linux64_signed.py
+                macosx64-.*:
+                    - repackage/base.py
+                    - repackage/osx_signed.py
+                win32-.*:
+                    - repackage/base.py
+                    - repackage/win32_signed.py
+                win64-.*:
+                    - repackage/base.py
+                    - repackage/win64_signed.py
+    package-formats:
+        by-build-platform:
+            linux.*: [mar]
+            linux4\b.*: [mar]
+            macosx64\b.*: [mar, dmg]
+            win32\b.*: [mar, installer]
+            win64\b.*: [mar, installer]
--- a/taskcluster/ci/repackage/kind.yml
+++ b/taskcluster/ci/repackage/kind.yml
@@ -35,17 +35,29 @@ only-for-build-platforms:
     - win64/opt
     - win64-asan-reporter-nightly/opt
 
 job-template:
     mozharness:
         config:
             by-build-platform:
                 linux\b.*:
+                    - repackage/base.py
                     - repackage/linux32_signed.py
                 linux64\b.*:
+                    - repackage/base.py
                     - repackage/linux64_signed.py
                 macosx64\b.*:
+                    - repackage/base.py
                     - repackage/osx_signed.py
                 win32\b.*:
+                    - repackage/base.py
                     - repackage/win32_signed.py
                 win64\b.*:
+                    - repackage/base.py
                     - repackage/win64_signed.py
+    package-formats:
+        by-build-platform:
+            linux.*: [mar]
+            linux4\b.*: [mar]
+            macosx64\b.*: [mar, dmg]
+            win32\b.*: [mar, installer]
+            win64\b.*: [mar, installer]
--- a/taskcluster/taskgraph/transforms/repackage.py
+++ b/taskcluster/taskgraph/transforms/repackage.py
@@ -13,16 +13,17 @@ from taskgraph.transforms.base import Tr
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import (
     validate_schema,
     optionally_keyed_by,
     resolve_keyed_by,
     Schema,
 )
 from taskgraph.util.taskcluster import get_artifact_prefix
+from taskgraph.util.platforms import archive_format, executable_extension
 from taskgraph.transforms.task import task_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
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
@@ -60,31 +61,85 @@ packaging_description_schema = Schema({
 
     # passed through directly to the job description
     Optional('extra'): task_description_schema['extra'],
 
     # Shipping product and phase
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 
+    Required('package-formats'): _by_platform([basestring]),
+
     # All l10n jobs use mozharness
     Required('mozharness'): {
         # Config files passed to the mozharness script
         Required('config'): _by_platform([basestring]),
 
         # Additional paths to look for mozharness configs in. These should be
         # relative to the base of the source checkout
         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,
     }
 })
 
+# The configuration passed to the mozharness repackage script. This defines the
+# arguments passed to `mach repackage`
+# - `args` is interpolated by mozharness (`{installer-tag}`,
+#   `{stub-installer-tag}`, `{sfx-stub}`) with values from the mozharness
+#   config.
+# - `inputs` are passed as long-options, with the filename prefixed by
+#   `MOZ_FETCH_DIR`. The filename is interpolated by taskgraph
+#   (`{archive_format}`, `{executable_extension}`).
+# - `output` is passed to `--output`, with the filename prefixed by the output
+#   directory.
+PACKAGE_FORMATS = {
+    'mar': {
+        'args': ['mar'],
+        'inputs': {
+            'input': 'target{archive_format}',
+            'mar': 'mar{executable_extension}',
+        },
+        'output': "target.complete.mar",
+    },
+    'dmg': {
+        'args': ['dmg'],
+        'inputs': {
+            'input': 'target{archive_format}',
+        },
+        'output': "target.dmg",
+    },
+    'installer': {
+        'args': [
+            "installer",
+            "--package-name", "firefox",
+            "--tag", "{installer-tag}",
+            "--sfx-stub", "{sfx-stub}",
+        ],
+        'inputs': {
+            'package': 'target{archive_format}',
+            "setupexe": "setup.exe",
+        },
+        'output': "target.installer.exe",
+    },
+    'installer-stub': {
+        'args': [
+            "installer",
+            "--tag", "{stub-installer-tag}",
+            "--sfx-stub", "{sfx-stub}",
+        ],
+        'inputs': {
+            "setupexe": "setup-stub.exe",
+        },
+        'output': 'target.stub-installer.exe',
+    },
+}
+
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job.get('dependent-task', object).__dict__.get('label', '?no-label?')
         validate_schema(
             packaging_description_schema, job,
             "In packaging ({!r} kind) task for {!r}:".format(config.kind, label))
@@ -99,17 +154,18 @@ def copy_in_useful_magic(config, jobs):
         job['build-platform'] = dep.attributes.get("build_platform")
         yield job
 
 
 @transforms.add
 def handle_keyed_by(config, jobs):
     """Resolve fields that can be keyed by platform, etc."""
     fields = [
-        "mozharness.config",
+        'mozharness.config',
+        'package-formats',
     ]
     for job in jobs:
         job = copy.deepcopy(job)  # don't overwrite dict values here
         for field in fields:
             resolve_keyed_by(item=job, field=field, item_name="?")
         yield job
 
 
@@ -170,45 +226,62 @@ def make_job_description(config, jobs):
         attributes['repackage_type'] = 'repackage'
 
         locale = None
         if job.get('locale'):
             locale = job['locale']
             attributes['locale'] = locale
 
         level = config.params['level']
+        build_platform = attributes['build_platform']
 
-        build_platform = attributes['build_platform']
+        use_stub = attributes.get('stub-installer')
+
+        repackage_config = []
+        package_formats = job.get('package-formats')
+        if use_stub:
+            package_formats += ['installer-stub']
+        for format in package_formats:
+            command = copy.deepcopy(PACKAGE_FORMATS[format])
+            substs = {
+                'archive_format': archive_format(build_platform),
+                'executable_extension': executable_extension(build_platform),
+            }
+            command['inputs'] = {
+                name: filename.format(**substs)
+                for name, filename in command['inputs'].items()
+            }
+            repackage_config.append(command)
+
         run = job.get('mozharness', {})
         run.update({
             'using': 'mozharness',
             'script': 'mozharness/scripts/repackage.py',
             'job-script': 'taskcluster/scripts/builder/repackage.sh',
             'actions': ['setup', 'repackage'],
             'extra-workspace-cache-key': 'repackage',
+            'extra-config': {
+                'repackage_config': repackage_config,
+            },
         })
 
         worker = {
             'artifacts': _generate_task_output_files(dep_job, build_platform,
                                                      locale=locale,
                                                      project=config.params["project"]),
             'chain-of-trust': True,
             'max-run-time': 7200 if build_platform.startswith('win') else 3600,
             # Don't add generic artifact directory.
             'skip-artifacts': True,
         }
 
         if locale:
             # Make sure we specify the locale-specific upload dir
             worker.setdefault('env', {}).update(LOCALE=locale)
 
-        use_stub = attributes.get('stub-installer')
-        if not use_stub:
-            worker.setdefault('env', {})['NO_STUB_INSTALLER'] = '1'
-
         if build_platform.startswith('win'):
             worker_type = 'aws-provisioner-v1/gecko-%s-b-win2012' % level
             run['use-magic-mh-args'] = False
         else:
             if build_platform.startswith(('linux', 'macosx')):
                 worker_type = 'aws-provisioner-v1/gecko-%s-b-linux' % level
             else:
                 raise NotImplementedError(
@@ -253,21 +326,20 @@ def make_job_description(config, jobs):
         yield task
 
 
 def _generate_download_config(task, build_platform, build_task, signing_task, locale=None,
                               project=None):
     locale_path = '{}/'.format(locale) if locale else ''
 
     if build_platform.startswith('linux') or build_platform.startswith('macosx'):
-        tarball_extension = 'bz2' if build_platform.startswith('linux') else 'gz'
         return {
             signing_task: [
                 {
-                    'artifact': '{}target.tar.{}'.format(locale_path, tarball_extension),
+                    'artifact': '{}target{}'.format(locale_path, archive_format(build_platform)),
                     'extract': False,
                 },
             ],
             build_task: [
                 'host/bin/mar',
             ],
         }
     elif build_platform.startswith('win'):
--- a/taskcluster/taskgraph/transforms/repackage_partner.py
+++ b/taskcluster/taskgraph/transforms/repackage_partner.py
@@ -14,17 +14,19 @@ from taskgraph.util.attributes import co
 from taskgraph.util.schema import (
     validate_schema,
     optionally_keyed_by,
     resolve_keyed_by,
     Schema,
 )
 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.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()}
 
@@ -53,16 +55,18 @@ packaging_description_schema = Schema({
 
     # passed through directly to the job description
     Optional('extra'): task_description_schema['extra'],
 
     # Shipping product and phase
     Optional('shipping-product'): task_description_schema['shipping-product'],
     Optional('shipping-phase'): task_description_schema['shipping-phase'],
 
+    Required('package-formats'): _by_platform([basestring]),
+
     # All l10n jobs use mozharness
     Required('mozharness'): {
         # Config files passed to the mozharness script
         Required('config'): _by_platform([basestring]),
 
         # Additional paths to look for mozharness configs in. These should be
         # relative to the base of the source checkout
         Optional('config-paths'): [basestring],
@@ -95,16 +99,17 @@ def copy_in_useful_magic(config, jobs):
         yield job
 
 
 @transforms.add
 def handle_keyed_by(config, jobs):
     """Resolve fields that can be keyed by platform, etc."""
     fields = [
         "mozharness.config",
+        'package-formats',
     ]
     for job in jobs:
         job = copy.deepcopy(job)  # don't overwrite dict values here
         for field in fields:
             resolve_keyed_by(item=job, field=field, item_name="?")
         yield job
 
 
@@ -143,37 +148,53 @@ def make_job_description(config, jobs):
             elif build_platform.startswith('win') and dependency.endswith('repack'):
                 signing_task = dependency
 
         attributes['repackage_type'] = 'repackage'
 
         level = config.params['level']
         repack_id = job['extra']['repack_id']
 
+        repackage_config = []
+        for format in job.get('package-formats'):
+            command = copy.deepcopy(PACKAGE_FORMATS[format])
+            substs = {
+                'archive_format': archive_format(build_platform),
+                'executable_extension': executable_extension(build_platform),
+            }
+            command['inputs'] = {
+                name: filename.format(**substs)
+                for name, filename in command['inputs'].items()
+            }
+            repackage_config.append(command)
+
         run = job.get('mozharness', {})
         run.update({
             'using': 'mozharness',
             'script': 'mozharness/scripts/repackage.py',
             'job-script': 'taskcluster/scripts/builder/repackage.sh',
             'actions': ['download_input', 'setup', 'repackage'],
             'extra-workspace-cache-key': 'repackage',
+            'extra-config': {
+                'repackage_config': repackage_config,
+            },
         })
 
         worker = {
             'artifacts': _generate_task_output_files(dep_job, build_platform, partner=repack_id),
             'chain-of-trust': True,
             'max-run-time': 7200 if build_platform.startswith('win') else 3600,
             'taskcluster-proxy': True if get_artifact_prefix(dep_job) else False,
-            'env': {},
+            'env': {
+                'REPACK_ID': repack_id,
+            },
             # Don't add generic artifact directory.
             'skip-artifacts': True,
         }
 
-        worker['env'].update(REPACK_ID=repack_id)
-
         if build_platform.startswith('win'):
             worker_type = 'aws-provisioner-v1/gecko-%s-b-win2012' % level
             run['use-magic-mh-args'] = False
         else:
             if build_platform.startswith('macosx'):
                 worker_type = 'aws-provisioner-v1/gecko-%s-b-linux' % level
             else:
                 raise NotImplementedError(
--- a/taskcluster/taskgraph/util/platforms.py
+++ b/taskcluster/taskgraph/util/platforms.py
@@ -9,12 +9,35 @@ import re
 # platform family is extracted from build platform by taking the alphabetic prefix
 # and then translating win -> windows
 _platform_re = re.compile(r'^[a-z]*')
 _renames = {
     'win': 'windows'
 }
 
 
+_archive_formats = {
+    'linux': '.tar.bz2',
+    'macosx': '.tar.gz',
+    'windows': '.zip',
+}
+
+_executable_extension = {
+    'linux': '',
+    'macosx': '',
+    'windows': '.exe',
+}
+
+
 def platform_family(build_platform):
     """Given a build platform, return the platform family (linux, macosx, etc.)"""
     family = _platform_re.match(build_platform).group(0)
     return _renames.get(family, family)
+
+
+def archive_format(build_platform):
+    """Given a build platform, return the archive format used on the platform."""
+    return _archive_formats[platform_family(build_platform)]
+
+
+def executable_extension(build_platform):
+    """Given a build platform, return the executable extension used on the platform."""
+    return _executable_extension[platform_family(build_platform)]
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/repackage/base.py
@@ -0,0 +1,5 @@
+config = {
+    "installer-tag": "browser/installer/windows/app.tag",
+    "stub-installer-tag": "browser/installer/windows/stub.tag",
+    "sfx-stub": "other-licenses/7zstub/firefox/7zSD.sfx",
+}
--- a/testing/mozharness/configs/repackage/linux32_signed.py
+++ b/testing/mozharness/configs/repackage/linux32_signed.py
@@ -1,20 +1,13 @@
 import os
 
 platform = "linux32"
 
 config = {
     "locale": os.environ.get("LOCALE"),
 
-    "repackage_config": [[
-        "mar",
-        "-i", "{abs_input_dir}/target.tar.bz2",
-        "--mar", "{abs_input_dir}/mar",
-        "-o", "{abs_output_dir}/target.complete.mar"
-    ]],
-
     # ToolTool
     "tooltool_url": 'http://relengapi/tooltool/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 
     'run_configure': False,
 }
--- a/testing/mozharness/configs/repackage/linux64_signed.py
+++ b/testing/mozharness/configs/repackage/linux64_signed.py
@@ -1,20 +1,13 @@
 import os
 
 platform = "linux64"
 
 config = {
     "locale": os.environ.get("LOCALE"),
 
-    "repackage_config": [[
-        "mar",
-        "-i", "{abs_input_dir}/target.tar.bz2",
-        "--mar", "{abs_input_dir}/mar",
-        "-o", "{abs_output_dir}/target.complete.mar"
-    ]],
-
     # ToolTool
     "tooltool_url": 'http://relengapi/tooltool/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 
     'run_configure': False,
 }
--- a/testing/mozharness/configs/repackage/osx_partner.py
+++ b/testing/mozharness/configs/repackage/osx_partner.py
@@ -1,17 +1,11 @@
 import os
 
 config = {
     "src_mozconfig": "browser/config/mozconfigs/macosx64/repack",
 
     "repack_id": os.environ.get("REPACK_ID"),
 
-    "repackage_config": [[
-        "dmg",
-        "-i", "{abs_input_dir}/target.tar.gz",
-        "-o", "{abs_output_dir}/target.dmg"
-    ]],
-
     # ToolTool
     "tooltool_url": 'http://relengapi/tooltool/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 }
--- a/testing/mozharness/configs/repackage/osx_signed.py
+++ b/testing/mozharness/configs/repackage/osx_signed.py
@@ -1,22 +1,11 @@
 import os
 
 config = {
     "src_mozconfig": "browser/config/mozconfigs/macosx64/repack",
 
     "locale": os.environ.get("LOCALE"),
 
-    "repackage_config": [[
-        "dmg",
-        "-i", "{abs_input_dir}/target.tar.gz",
-        "-o", "{abs_output_dir}/target.dmg"
-    ], [
-        "mar",
-        "-i", "{abs_input_dir}/target.tar.gz",
-        "--mar", "{abs_input_dir}/mar",
-        "-o", "{abs_output_dir}/target.complete.mar"
-    ]],
-
     # ToolTool
     "tooltool_url": 'http://relengapi/tooltool/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 }
--- a/testing/mozharness/configs/repackage/win32_partner.py
+++ b/testing/mozharness/configs/repackage/win32_partner.py
@@ -1,26 +1,14 @@
 import os
 
 platform = "win32"
 
-repackage_config = [[
-        "installer",
-        "--package-name", "firefox",
-        "--package", "{abs_input_dir}\\target.zip",
-        "--tag", "{abs_mozilla_dir}\\browser\\installer\\windows\\app.tag",
-        "--setupexe", "{abs_input_dir}\\setup.exe",
-        "-o", "{abs_output_dir}\\target.installer.exe",
-        "--sfx-stub", "other-licenses/7zstub/firefox/7zSD.sfx",
-    ]]
-
 config = {
     "repack_id": os.environ.get("REPACK_ID"),
 
-    "repackage_config": repackage_config,
-
     # ToolTool
     "tooltool_manifest_src": 'browser\\config\\tooltool-manifests\\{}\\releng.manifest'.format(platform),
     'tooltool_url': 'https://tooltool.mozilla-releng.net/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 
     'run_configure': False,
 }
--- a/testing/mozharness/configs/repackage/win32_signed.py
+++ b/testing/mozharness/configs/repackage/win32_signed.py
@@ -1,41 +1,14 @@
 import os
 
 platform = "win32"
 
-repackage_config = [[
-        "installer",
-        "--package-name", "firefox",
-        "--package", "{abs_input_dir}\\target.zip",
-        "--tag", "{abs_mozilla_dir}\\browser\\installer\\windows\\app.tag",
-        "--setupexe", "{abs_input_dir}\\setup.exe",
-        "-o", "{abs_output_dir}\\target.installer.exe",
-        "--sfx-stub", "other-licenses/7zstub/firefox/7zSD.sfx",
-    ], [
-        "mar",
-        "-i", "{abs_input_dir}\\target.zip",
-        "--mar", "{abs_input_dir}\\mar.exe",
-        "-o", "{abs_output_dir}\\target.complete.mar",
-    ]]
-
-if not os.environ.get("NO_STUB_INSTALLER"):
-    # Some channels, like esr don't build a stub installer
-    repackage_config.append([
-        "installer",
-        "--tag", "{abs_mozilla_dir}\\browser\\installer\\windows\\stub.tag",
-        "--setupexe", "{abs_input_dir}\\setup-stub.exe",
-        "-o", "{abs_output_dir}\\target.stub-installer.exe",
-        "--sfx-stub", "other-licenses/7zstub/firefox/7zSD.sfx",
-    ])
-
 config = {
     "locale": os.environ.get("LOCALE"),
 
-    "repackage_config": repackage_config,
-
     # ToolTool
     "tooltool_manifest_src": 'browser\\config\\tooltool-manifests\\{}\\releng.manifest'.format(platform),
     'tooltool_url': 'https://tooltool.mozilla-releng.net/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 
     'run_configure': False,
 }
--- a/testing/mozharness/configs/repackage/win64_partner.py
+++ b/testing/mozharness/configs/repackage/win64_partner.py
@@ -1,26 +1,14 @@
 import os
 
 platform = "win64"
 
-repackage_config = [[
-        "installer",
-        "--package-name", "firefox",
-        "--package", "{abs_input_dir}\\target.zip",
-        "--tag", "{abs_mozilla_dir}\\browser\\installer\\windows\\app.tag",
-        "--setupexe", "{abs_input_dir}\\setup.exe",
-        "-o", "{abs_output_dir}\\target.installer.exe",
-        "--sfx-stub", "other-licenses/7zstub/firefox/7zSD.sfx",
-    ]]
-
 config = {
     "repack_id": os.environ.get("REPACK_ID"),
 
-    "repackage_config": repackage_config,
-
     # ToolTool
     "tooltool_manifest_src": 'browser\\config\\tooltool-manifests\\{}\\releng.manifest'.format(platform),
     'tooltool_url': 'https://tooltool.mozilla-releng.net/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 
     'run_configure': False,
 }
--- a/testing/mozharness/configs/repackage/win64_signed.py
+++ b/testing/mozharness/configs/repackage/win64_signed.py
@@ -1,31 +1,14 @@
 import os
 
 platform = "win64"
 
-repackage_config = [[
-        "installer",
-        "--package-name", "firefox",
-        "--package", "{abs_input_dir}\\target.zip",
-        "--tag", "{abs_mozilla_dir}\\browser\\installer\\windows\\app.tag",
-        "--setupexe", "{abs_input_dir}\\setup.exe",
-        "-o", "{abs_output_dir}\\target.installer.exe",
-        "--sfx-stub", "other-licenses/7zstub/firefox/7zSD.sfx",
-    ], [
-        "mar",
-        "-i", "{abs_input_dir}\\target.zip",
-        "--mar", "{abs_input_dir}\\mar.exe",
-        "-o", "{abs_output_dir}\\target.complete.mar",
-    ]]
-
 config = {
     "locale": os.environ.get("LOCALE"),
 
-    "repackage_config": repackage_config,
-
     # ToolTool
     "tooltool_manifest_src": 'browser\\config\\tooltool-manifests\\{}\\releng.manifest'.format(platform),
     'tooltool_url': 'https://tooltool.mozilla-releng.net/',
     'tooltool_cache': os.environ.get('TOOLTOOL_CACHE'),
 
     'run_configure': False,
 }
--- a/testing/mozharness/scripts/repackage.py
+++ b/testing/mozharness/scripts/repackage.py
@@ -28,34 +28,30 @@ class Repackage(BaseScript):
 
         self._run_tooltool()
 
         mar_path = os.path.join(dirs['abs_input_dir'], 'mar')
         if self._is_windows():
             mar_path += '.exe'
         if mar_path:
             self.chmod(mar_path, 0755)
-
         if self.config.get("run_configure", True):
             self._get_mozconfig()
             self._run_configure()
 
     def query_abs_dirs(self):
         if self.abs_dirs:
             return self.abs_dirs
         abs_dirs = super(Repackage, self).query_abs_dirs()
         config = self.config
 
         dirs = {}
         dirs['abs_tools_dir'] = os.path.join(abs_dirs['abs_work_dir'], 'tools')
         dirs['abs_mozilla_dir'] = os.path.join(abs_dirs['abs_work_dir'], 'src')
-        dirs['abs_input_dir'] = os.path.join(
-            abs_dirs['base_work_dir'],
-            os.environ.get('MOZ_FETCHES_DIR', 'fetches'),
-        )
+        dirs['abs_input_dir'] = os.path.join(abs_dirs['base_work_dir'], 'fetches')
         output_dir_suffix = []
         if config.get('locale'):
             output_dir_suffix.append(config['locale'])
         if config.get('repack_id'):
             output_dir_suffix.append(config['repack_id'])
         dirs['abs_output_dir'] = os.path.join(
             abs_dirs['abs_work_dir'], 'outputs', *output_dir_suffix)
         for key in dirs.keys():
@@ -63,23 +59,37 @@ class Repackage(BaseScript):
                 abs_dirs[key] = dirs[key]
         self.abs_dirs = abs_dirs
         return self.abs_dirs
 
     def repackage(self):
         config = self.config
         dirs = self.query_abs_dirs()
 
+        subst = {
+            'sfx-stub': config['sfx-stub'],
+            'installer-tag': config['installer-tag'],
+            'stub-installer-tag': config['stub-installer-tag'],
+        }
+        subst.update(dirs)
+
         # Make sure the upload dir is around.
         self.mkdir_p(dirs['abs_output_dir'])
 
         for repack_config in config["repackage_config"]:
-            command = [sys.executable, 'mach', '--log-no-times', 'repackage'] + \
-                [arg.format(**dirs)
-                    for arg in list(repack_config)]
+            command = [sys.executable, 'mach', '--log-no-times', 'repackage']
+            command.extend([arg.format(**subst) for arg in repack_config['args']])
+            for arg, filename in repack_config['inputs'].items():
+                command.extend([
+                    '--{}'.format(arg),
+                    os.path.join(dirs['abs_input_dir'], filename),
+                ])
+            command.extend([
+                '--output', os.path.join(dirs['abs_output_dir'], repack_config['output']),
+            ])
             self.run_command(
                 command=command,
                 cwd=dirs['abs_mozilla_dir'],
                 halt_on_failure=True,
             )
 
     def _run_tooltool(self):
         config = self.config