summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/repackaging/msi.py
blob: b0b1b0998329ac45b8bbd0d1ea92b73665b4e80c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# 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/.

import os
import shutil
import subprocess
import sys
import tempfile
from xml.dom import minidom

import mozpack.path as mozpath

from mozbuild.util import ensureParentDir

_MSI_ARCH = {
    "x86": "x86",
    "x86_64": "x64",
}


def update_wsx(wfile, pvalues):

    parsed = minidom.parse(wfile)

    # construct a dictinary for the pre-processing options
    # iterate over that list and add them to the wsx xml doc
    for k, v in pvalues.items():
        entry = parsed.createProcessingInstruction("define", k + ' = "' + v + '"')
        root = parsed.firstChild
        parsed.insertBefore(entry, root)
    # write out xml to new wfile
    new_w_file = wfile + ".new"
    with open(new_w_file, "w") as fh:
        parsed.writexml(fh)
    shutil.move(new_w_file, wfile)
    return wfile


def repackage_msi(
    topsrcdir, wsx, version, locale, arch, setupexe, candle, light, output
):
    if sys.platform != "win32":
        raise Exception("repackage msi only works on windows")
    if not os.path.isdir(topsrcdir):
        raise Exception("%s does not exist." % topsrcdir)
    if not os.path.isfile(wsx):
        raise Exception("%s does not exist." % wsx)
    if version is None:
        raise Exception("version name must be provided.")
    if locale is None:
        raise Exception("locale name must be provided.")
    if arch is None or arch not in _MSI_ARCH.keys():
        raise Exception(
            "arch name must be provided and one of {}.".format(_MSI_ARCH.keys())
        )
    if not os.path.isfile(setupexe):
        raise Exception("%s does not exist." % setupexe)
    if candle is not None and not os.path.isfile(candle):
        raise Exception("%s does not exist." % candle)
    if light is not None and not os.path.isfile(light):
        raise Exception("%s does not exist." % light)
    embeddedVersion = "0.0.0.0"
    # Version string cannot contain 'a' or 'b' when embedding in msi manifest.
    if "a" not in version and "b" not in version:
        if version.endswith("esr"):
            parts = version[:-3].split(".")
        else:
            parts = version.split(".")
        while len(parts) < 4:
            parts.append("0")
        embeddedVersion = ".".join(parts)

    wsx = mozpath.realpath(wsx)
    setupexe = mozpath.realpath(setupexe)
    output = mozpath.realpath(output)
    ensureParentDir(output)

    if sys.platform == "win32":
        tmpdir = tempfile.mkdtemp()
        old_cwd = os.getcwd()
        try:
            wsx_file = os.path.split(wsx)[1]
            shutil.copy(wsx, tmpdir)
            temp_wsx_file = os.path.join(tmpdir, wsx_file)
            temp_wsx_file = mozpath.realpath(temp_wsx_file)
            pre_values = {
                "Vendor": "Mozilla",
                "BrandFullName": "Mozilla Firefox",
                "Version": version,
                "AB_CD": locale,
                "Architecture": _MSI_ARCH[arch],
                "ExeSourcePath": setupexe,
                "EmbeddedVersionCode": embeddedVersion,
            }
            # update wsx file with inputs from
            newfile = update_wsx(temp_wsx_file, pre_values)
            wix_object_file = os.path.join(tmpdir, "installer.wixobj")
            env = os.environ.copy()
            if candle is None:
                candle = "candle.exe"
            cmd = [candle, "-out", wix_object_file, newfile]
            subprocess.check_call(cmd, env=env)
            wix_installer = wix_object_file.replace(".wixobj", ".msi")
            if light is None:
                light = "light.exe"
            light_cmd = [
                light,
                "-cultures:neutral",
                "-sw1076",
                "-sw1079",
                "-out",
                wix_installer,
                wix_object_file,
            ]
            subprocess.check_call(light_cmd, env=env)
            os.remove(wix_object_file)
            # mv file to output dir
            shutil.move(wix_installer, output)
        finally:
            os.chdir(old_cwd)
            shutil.rmtree(tmpdir)