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 433941 2c723487d70b82dbb84ee91e51c5f586e3cce6a1
parent 433940 64b1b8d041ac307768824c5e78a0a916a1e3c61c
child 433942 206ab6424edfa60b452fa5ca370132f463f07f0a
push id34526
push useraiakab@mozilla.com
push dateWed, 29 Aug 2018 21:56:30 +0000
treeherdermozilla-central@2b50a2ad969a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCallek
bugs1484264
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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