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/.
8 from datetime
import datetime
11 from mozbuild
.preprocessor
import Preprocessor
12 from variables
import get_buildid
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/.
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
26 // Do not edit this file. Changes won't affect the build.
30 Identity LimitedAccessFeature {{ L"{lafidentity}_pcsmm0jrprpb2" }}
33 /////////////////////////////////////////////////////////////////////////////
39 FILEVERSION {fileversion}
40 PRODUCTVERSION {productversion}
47 BLOCK "StringFileInfo"
51 VALUE "Comments", "{comment}"
52 VALUE "LegalCopyright", "{copyright}"
53 VALUE "CompanyName", "{company}"
54 VALUE "FileDescription", "{description}"
55 VALUE "FileVersion", "{mfversion}"
56 VALUE "ProductVersion", "{mpversion}"
57 VALUE "InternalName", "{module}"
58 VALUE "LegalTrademarks", "{trademarks}"
59 VALUE "OriginalFilename", "{binary}"
60 VALUE "ProductName", "{productname}"
61 VALUE "BuildID", "{buildid}"
66 VALUE "Translation", 0x0, 1200
73 class SystemClockDiscrepancy(Exception):
74 """Represents an error encountered during the build when determining delta between the build time and
75 the commit time of milestone.txt via VCS."""
78 def preprocess(path
, defines
):
79 pp
= Preprocessor(defines
=defines
, marker
="%")
80 pp
.context
.update(defines
)
81 pp
.out
= io
.StringIO()
82 pp
.do_filter("substitution")
83 pp
.do_include(io
.open(path
, "r", encoding
="latin1"))
88 def parse_module_ver(path
, defines
):
90 for line
in preprocess(path
, defines
):
91 content
, *comment
= line
.split("#", 1)
92 if not content
.strip():
94 entry
, value
= content
.split("=", 1)
95 result
[entry
.strip()] = value
.strip()
99 def last_winversion_segment(buildid
, app_version_display
):
101 The last segment needs to fit into a 16 bit number. We also need to
102 encode what channel this version is from. We'll do this by using 2 bits
103 to encode the channel, and 14 bits to encode the number of hours since
104 the 'config/milestone.txt' was modified (relative to the build time).
106 This gives us about ~682 days of release hours that will yield a unique
107 file version for a specific channel/milestone combination. This should suffice
108 since the main problem we're trying to address is uniqueness in CI for a
109 channel/milestone over about a 1 month period.
111 If two builds for the same channel/milestone are done in CI within the same
112 hour there's still a chance for overlap and issues with AV as originally
113 reported in https://bugzilla.mozilla.org/show_bug.cgi?id=1872242
115 If a build is done after the ~682 day window of uniqueness, the value for
116 this segment will always be the maximum value for the channel (no overflow).
117 It will also always be the maximum value for the channel if a build is done
118 from a source distribution, because we cannot determine the milestone date
119 change without a VCS.
121 If necessary, you can decode the result of this function. You just need to
122 do integer division and divide it by 4. The quotient will be the delta
123 between the milestone bump and the build time, and the remainder will be
124 the channel digit. Refer to the if/else chain near the end of the function
125 for what channels the channel digits map to.
134 from mozversioncontrol
import MissingVCSTool
, get_repository_object
136 # Max 16 bit value with 2 most significant bits as 0 (reserved so we can
137 # shift later and make room for the channel digit).
142 from datetime
import timedelta
, timezone
143 from pathlib
import Path
145 topsrcdir
= buildconfig
.topsrcdir
146 repo
= get_repository_object(topsrcdir
)
148 milestone_time
= repo
.get_last_modified_time_for_file(
149 Path(topsrcdir
) / "config" / "milestone.txt"
151 # The buildid doesn't include timezone info, but the milestone_time does.
152 # We're building on this machine, so we just need the system local timezone
153 # added to a buildid constructed datetime object to make a valid comparison.
154 local_tz
= timezone(timedelta(seconds
=time
.timezone
))
155 buildid_time
= datetime
.strptime(buildid
, "%Y%m%d%H%M%S").replace(
159 time_delta
= buildid_time
- milestone_time
160 # If the time delta is negative it means that the system clock on the build machine is
161 # significantly far ahead. If we're in CI we'll raise an error, since this number mostly
162 # only matters for doing releases in CI. If we're not in CI, we'll just set the value to
163 # the maximum instead of needlessly interrupting the build of a user with fast/intentionally
164 # modified system clock.
165 if time_delta
.total_seconds() < 0:
166 if "MOZ_AUTOMATION" in os
.environ
:
167 raise SystemClockDiscrepancy(
168 f
"The system clock is ahead of the milestone.txt commit time "
169 f
"by at least {int(time_delta.total_seconds())} seconds (Since "
170 f
"the milestone commit must come before the build starts). This "
171 f
"is a problem because use a relative time difference to determine the"
172 f
"file_version (and it can't be negative), so we cannot proceed. \n\n"
173 f
"Please ensure the system clock is correct."
176 hours_from_milestone_date
= MAX_VALUE
178 # Convert from seconds to hours
179 # When a build is done more than ~682 days in the future, we can't represent the value.
180 # We'll always set the value to the maximum value instead of overflowing.
181 hours_from_milestone_date
= min(
182 int(time_delta
.total_seconds() / 3600), MAX_VALUE
184 except MissingVCSTool
:
185 # If we're here we can't use the VCS to determine the time differential, so
186 # we'll just set it to the maximum value instead of doing something weird.
187 hours_from_milestone_date
= MAX_VALUE
190 if buildconfig
.substs
.get("NIGHTLY_BUILD"):
193 elif "b" in app_version_display
:
196 elif buildconfig
.substs
.get("MOZ_ESR"):
202 # left shift to make room to encode the channel digit
203 return str((hours_from_milestone_date
<< 2) + channel_digit
)
207 for l
in range(len(s
), 0, -1):
213 def split_and_normalize_version(version
, len):
214 return ([digits_only(x
) for x
in version
.split(".")] + ["0"] * len)[:len]
217 def has_manifest(module_rc
, manifest_id
):
218 for lineFromInput
in module_rc
.splitlines():
219 line
= lineFromInput
.split(None, 2)
222 id, what
, *rest
= line
223 if id == manifest_id
and what
in ("24", "RT_MANIFEST"):
228 def generate_module_rc(binary
="", rcinclude
=None):
230 buildid
= get_buildid()
231 milestone
= buildconfig
.substs
["GRE_MILESTONE"]
232 app_version
= buildconfig
.substs
.get("MOZ_APP_VERSION") or milestone
233 app_version_display
= buildconfig
.substs
.get("MOZ_APP_VERSION_DISPLAY")
234 app_winversion
= ",".join(split_and_normalize_version(app_version
, 4))
235 milestone_winversion
= ",".join(
236 split_and_normalize_version(milestone
, 3)
237 + [last_winversion_segment(buildid
, app_version_display
)]
239 display_name
= buildconfig
.substs
.get("MOZ_APP_DISPLAYNAME", "Mozilla")
241 milestone_string
= milestone
244 if buildconfig
.substs
.get("MOZ_DEBUG"):
245 flags
.append("VS_FF_DEBUG")
246 milestone_string
+= " Debug"
247 if not buildconfig
.substs
.get("MOZILLA_OFFICIAL"):
248 flags
.append("VS_FF_PRIVATEBUILD")
249 if buildconfig
.substs
.get("NIGHTLY_BUILD"):
250 flags
.append("VS_FF_PRERELEASE")
253 "MOZ_APP_DISPLAYNAME": display_name
,
254 "MOZ_APP_VERSION": app_version
,
255 "MOZ_APP_WINVERSION": app_winversion
,
258 relobjdir
= os
.path
.relpath(".", buildconfig
.topobjdir
)
259 srcdir
= os
.path
.join(buildconfig
.topsrcdir
, relobjdir
)
260 module_ver
= os
.path
.join(srcdir
, "module.ver")
261 if os
.path
.exists(module_ver
):
263 overrides
= parse_module_ver(module_ver
, defines
)
268 include
= "// From included resource {}\n{}".format(
269 rcinclude
, preprocess(rcinclude
, defines
).read()
274 # Set the identity field for the Limited Access Feature
275 # Must match the tokens used in Win11LimitedAccessFeatures.cpp
276 lafidentity
= "MozillaFirefox"
277 # lafidentity = "FirefoxBeta"
278 # lafidentity = "FirefoxNightly"
280 data
= TEMPLATE
.format(
282 lafidentity
=lafidentity
,
283 fileversion
=overrides
.get("WIN32_MODULE_FILEVERSION", milestone_winversion
),
284 productversion
=overrides
.get(
285 "WIN32_MODULE_PRODUCTVERSION", milestone_winversion
287 fileflags
=" | ".join(flags
),
288 comment
=overrides
.get("WIN32_MODULE_COMMENT", ""),
289 copyright
=overrides
.get("WIN32_MODULE_COPYRIGHT", "License: MPL 2"),
290 company
=overrides
.get("WIN32_MODULE_COMPANYNAME", "Mozilla Foundation"),
291 description
=overrides
.get("WIN32_MODULE_DESCRIPTION", ""),
292 mfversion
=overrides
.get("WIN32_MODULE_FILEVERSION_STRING", milestone_string
),
293 mpversion
=overrides
.get("WIN32_MODULE_PRODUCTVERSION_STRING", milestone_string
),
294 module
=overrides
.get("WIN32_MODULE_NAME", ""),
295 trademarks
=overrides
.get("WIN32_MODULE_TRADEMARKS", "Mozilla"),
296 binary
=overrides
.get("WIN32_MODULE_ORIGINAL_FILENAME", binary
),
297 productname
=overrides
.get("WIN32_MODULE_PRODUCTNAME", display_name
),
301 manifest_id
= "2" if binary
.lower().endswith(".dll") else "1"
302 if binary
and not has_manifest(data
, manifest_id
):
303 manifest_path
= os
.path
.join(srcdir
, binary
+ ".manifest")
304 if os
.path
.exists(manifest_path
):
305 manifest_path
= manifest_path
.replace("\\", "\\\\")
306 data
+= '\n{} RT_MANIFEST "{}"\n'.format(manifest_id
, manifest_path
)
308 with io
.open("{}.rc".format(binary
or "module"), "w", encoding
="latin1") as fh
:
312 if __name__
== "__main__":
313 generate_module_rc(*sys
.argv
[1:])