1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from datetime import datetime
6import io
7import os
8import sys
9
10from mozbuild.preprocessor import Preprocessor
11import buildconfig
12
13
14TEMPLATE = """
15// This Source Code Form is subject to the terms of the Mozilla Public
16// License, v. 2.0. If a copy of the MPL was not distributed with this
17// file, You can obtain one at http://mozilla.org/MPL/2.0/.
18
19#include<winuser.h>
20#include<winver.h>
21
22// Note: if you contain versioning information in an included
23// RC script, it will be discarded
24// Use module.ver to explicitly set these values
25
26// Do not edit this file. Changes won't affect the build.
27
28{include}
29
30
31/////////////////////////////////////////////////////////////////////////////
32//
33// Version
34//
35
361 VERSIONINFO
37 FILEVERSION    {fileversion}
38 PRODUCTVERSION {productversion}
39 FILEFLAGSMASK 0x3fL
40 FILEFLAGS {fileflags}
41 FILEOS VOS__WINDOWS32
42 FILETYPE VFT_DLL
43 FILESUBTYPE 0x0L
44BEGIN
45    BLOCK "StringFileInfo"
46    BEGIN
47        BLOCK "000004b0"
48        BEGIN
49            VALUE "Comments", "{comment}"
50            VALUE "LegalCopyright", "{copyright}"
51            VALUE "CompanyName", "{company}"
52            VALUE "FileDescription", "{description}"
53            VALUE "FileVersion", "{mfversion}"
54            VALUE "ProductVersion", "{mpversion}"
55            VALUE "InternalName", "{module}"
56            VALUE "LegalTrademarks", "{trademarks}"
57            VALUE "OriginalFilename", "{binary}"
58            VALUE "ProductName", "{productname}"
59            VALUE "BuildID", "{buildid}"
60        END
61    END
62    BLOCK "VarFileInfo"
63    BEGIN
64        VALUE "Translation", 0x0, 1200
65    END
66END
67
68"""
69
70
71def preprocess(path, defines):
72    pp = Preprocessor(defines=defines, marker="%")
73    pp.context.update(defines)
74    pp.out = io.StringIO()
75    pp.do_filter("substitution")
76    pp.do_include(io.open(path, "r", encoding="latin1"))
77    pp.out.seek(0)
78    return pp.out
79
80
81def parse_module_ver(path, defines):
82    result = {}
83    for line in preprocess(path, defines):
84        content, *comment = line.split("#", 1)
85        if not content.strip():
86            continue
87        entry, value = content.split("=", 1)
88        result[entry.strip()] = value.strip()
89    return result
90
91
92def get_buildid():
93    path = os.path.join(buildconfig.topobjdir, "buildid.h")
94    define, MOZ_BUILDID, buildid = io.open(path, "r", encoding="utf-8").read().split()
95    return buildid
96
97
98def days_from_2000_to_buildid(buildid):
99    start = datetime(2000, 1, 1, 0, 0, 0)
100    buildid_time = datetime.strptime(buildid, "%Y%m%d%H%M%S")
101    return (buildid_time - start).days
102
103
104def digits_only(s):
105    for l in range(len(s), 0, -1):
106        if s[:l].isdigit():
107            return s[:l]
108    return "0"
109
110
111def split_and_normalize_version(version, len):
112    return ([digits_only(x) for x in version.split(".")] + ["0"] * len)[:len]
113
114
115def has_manifest(module_rc, manifest_id):
116    for line in module_rc.splitlines():
117        line = line.split(None, 2)
118        if len(line) < 2:
119            continue
120        id, what, *rest = line
121        if id == manifest_id and what in ("24", "RT_MANIFEST"):
122            return True
123    return False
124
125
126def generate_module_rc(binary="", rcinclude=None):
127    deps = set()
128    buildid = get_buildid()
129    milestone = buildconfig.substs["GRE_MILESTONE"]
130    app_version = buildconfig.substs.get("MOZ_APP_VERSION") or milestone
131    app_winversion = ",".join(split_and_normalize_version(app_version, 4))
132    milestone_winversion = ",".join(
133        split_and_normalize_version(milestone, 3)
134        + [str(days_from_2000_to_buildid(buildid))]
135    )
136    display_name = buildconfig.substs.get("MOZ_APP_DISPLAYNAME", "Mozilla")
137
138    milestone_string = milestone
139
140    flags = ["0"]
141    if buildconfig.substs.get("MOZ_DEBUG"):
142        flags.append("VS_FF_DEBUG")
143        milestone_string += " Debug"
144    if not buildconfig.substs.get("MOZILLA_OFFICIAL"):
145        flags.append("VS_FF_PRIVATEBUILD")
146    if buildconfig.substs.get("NIGHTLY_BUILD"):
147        flags.append("VS_FF_PRERELEASE")
148
149    defines = {
150        "MOZ_APP_DISPLAYNAME": display_name,
151        "MOZ_APP_VERSION": app_version,
152        "MOZ_APP_WINVERSION": app_winversion,
153    }
154
155    relobjdir = os.path.relpath(".", buildconfig.topobjdir)
156    srcdir = os.path.join(buildconfig.topsrcdir, relobjdir)
157    module_ver = os.path.join(srcdir, "module.ver")
158    if os.path.exists(module_ver):
159        deps.add(module_ver)
160        overrides = parse_module_ver(module_ver, defines)
161    else:
162        overrides = {}
163
164    if rcinclude:
165        include = "// From included resource {}\n{}".format(
166            rcinclude, preprocess(rcinclude, defines).read()
167        )
168    else:
169        include = ""
170
171    data = TEMPLATE.format(
172        include=include,
173        fileversion=overrides.get("WIN32_MODULE_FILEVERSION", milestone_winversion),
174        productversion=overrides.get(
175            "WIN32_MODULE_PRODUCTVERSION", milestone_winversion
176        ),
177        fileflags=" | ".join(flags),
178        comment=overrides.get("WIN32_MODULE_COMMENT", ""),
179        copyright=overrides.get("WIN32_MODULE_COPYRIGHT", "License: MPL 2"),
180        company=overrides.get("WIN32_MODULE_COMPANYNAME", "Mozilla Foundation"),
181        description=overrides.get("WIN32_MODULE_DESCRIPTION", ""),
182        mfversion=overrides.get("WIN32_MODULE_FILEVERSION_STRING", milestone_string),
183        mpversion=overrides.get("WIN32_MODULE_PRODUCTVERSION_STRING", milestone_string),
184        module=overrides.get("WIN32_MODULE_NAME", ""),
185        trademarks=overrides.get("WIN32_MODULE_TRADEMARKS", "Mozilla"),
186        binary=overrides.get("WIN32_MODULE_ORIGINAL_FILENAME", binary),
187        productname=overrides.get("WIN32_MODULE_PRODUCTNAME", display_name),
188        buildid=buildid,
189    )
190
191    manifest_id = "2" if binary.lower().endswith(".dll") else "1"
192    if binary and not has_manifest(data, manifest_id):
193        manifest_path = os.path.join(srcdir, binary + ".manifest")
194        if os.path.exists(manifest_path):
195            manifest_path = manifest_path.replace("\\", "\\\\")
196            data += '\n{} RT_MANIFEST "{}"\n'.format(manifest_id, manifest_path)
197
198    with io.open("{}.rc".format(binary or "module"), "w", encoding="latin1") as fh:
199        fh.write(data)
200
201
202if __name__ == "__main__":
203    generate_module_rc(*sys.argv[1:])
204