##// END OF EJS Templates
pyoxidizer: add arch to PyOxidizer MSIs...
Gregory Szorc -
r48699:3962503c stable
parent child Browse files
Show More
@@ -1,316 +1,320 b''
1 # The following variables can be passed in as parameters:
1 # The following variables can be passed in as parameters:
2 #
2 #
3 # VERSION
3 # VERSION
4 # Version string of program being produced.
4 # Version string of program being produced.
5 #
5 #
6 # MSI_NAME
6 # MSI_NAME
7 # Root name of MSI installer.
7 # Root name of MSI installer.
8 #
8 #
9 # EXTRA_MSI_FEATURES
9 # EXTRA_MSI_FEATURES
10 # ; delimited string of extra features to advertise in the built MSA.
10 # ; delimited string of extra features to advertise in the built MSA.
11 #
11 #
12 # SIGNING_PFX_PATH
12 # SIGNING_PFX_PATH
13 # Path to code signing certificate to use.
13 # Path to code signing certificate to use.
14 #
14 #
15 # SIGNING_PFX_PASSWORD
15 # SIGNING_PFX_PASSWORD
16 # Password to code signing PFX file defined by SIGNING_PFX_PATH.
16 # Password to code signing PFX file defined by SIGNING_PFX_PATH.
17 #
17 #
18 # SIGNING_SUBJECT_NAME
18 # SIGNING_SUBJECT_NAME
19 # String fragment in code signing certificate subject name used to find
19 # String fragment in code signing certificate subject name used to find
20 # code signing certificate in Windows certificate store.
20 # code signing certificate in Windows certificate store.
21 #
21 #
22 # TIME_STAMP_SERVER_URL
22 # TIME_STAMP_SERVER_URL
23 # URL of time-stamp token authority (RFC 3161) servers to stamp code signatures.
23 # URL of time-stamp token authority (RFC 3161) servers to stamp code signatures.
24
24
25 ROOT = CWD + "/../.."
25 ROOT = CWD + "/../.."
26
26
27 VERSION = VARS.get("VERSION", "5.8")
27 VERSION = VARS.get("VERSION", "5.8")
28 MSI_NAME = VARS.get("MSI_NAME", "mercurial")
28 MSI_NAME = VARS.get("MSI_NAME", "mercurial")
29 EXTRA_MSI_FEATURES = VARS.get("EXTRA_MSI_FEATURES")
29 EXTRA_MSI_FEATURES = VARS.get("EXTRA_MSI_FEATURES")
30 SIGNING_PFX_PATH = VARS.get("SIGNING_PFX_PATH")
30 SIGNING_PFX_PATH = VARS.get("SIGNING_PFX_PATH")
31 SIGNING_PFX_PASSWORD = VARS.get("SIGNING_PFX_PASSWORD", "")
31 SIGNING_PFX_PASSWORD = VARS.get("SIGNING_PFX_PASSWORD", "")
32 SIGNING_SUBJECT_NAME = VARS.get("SIGNING_SUBJECT_NAME")
32 SIGNING_SUBJECT_NAME = VARS.get("SIGNING_SUBJECT_NAME")
33 TIME_STAMP_SERVER_URL = VARS.get("TIME_STAMP_SERVER_URL", "http://timestamp.digicert.com")
33 TIME_STAMP_SERVER_URL = VARS.get("TIME_STAMP_SERVER_URL", "http://timestamp.digicert.com")
34
34
35 IS_WINDOWS = "windows" in BUILD_TARGET_TRIPLE
35 IS_WINDOWS = "windows" in BUILD_TARGET_TRIPLE
36
36
37 # Code to run in Python interpreter.
37 # Code to run in Python interpreter.
38 RUN_CODE = """
38 RUN_CODE = """
39 import os
39 import os
40 import sys
40 import sys
41 extra_path = os.environ.get('PYTHONPATH')
41 extra_path = os.environ.get('PYTHONPATH')
42 if extra_path is not None:
42 if extra_path is not None:
43 # extensions and hooks expect a working python environment
43 # extensions and hooks expect a working python environment
44 # We do not prepend the values because the Mercurial library wants to be in
44 # We do not prepend the values because the Mercurial library wants to be in
45 # the front of the sys.path to avoid picking up other installations.
45 # the front of the sys.path to avoid picking up other installations.
46 sys.path.extend(extra_path.split(os.pathsep))
46 sys.path.extend(extra_path.split(os.pathsep))
47 # Add user site to sys.path to load extensions without the full path
47 # Add user site to sys.path to load extensions without the full path
48 if os.name == 'nt':
48 if os.name == 'nt':
49 vi = sys.version_info
49 vi = sys.version_info
50 appdata = os.environ.get('APPDATA')
50 appdata = os.environ.get('APPDATA')
51 if appdata:
51 if appdata:
52 sys.path.append(
52 sys.path.append(
53 os.path.join(
53 os.path.join(
54 appdata,
54 appdata,
55 'Python',
55 'Python',
56 'Python%d%d' % (vi[0], vi[1]),
56 'Python%d%d' % (vi[0], vi[1]),
57 'site-packages',
57 'site-packages',
58 )
58 )
59 )
59 )
60 import hgdemandimport;
60 import hgdemandimport;
61 hgdemandimport.enable();
61 hgdemandimport.enable();
62 from mercurial import dispatch;
62 from mercurial import dispatch;
63 dispatch.run();
63 dispatch.run();
64 """
64 """
65
65
66 set_build_path(ROOT + "/build/pyoxidizer")
66 set_build_path(ROOT + "/build/pyoxidizer")
67
67
68 def make_distribution():
68 def make_distribution():
69 return default_python_distribution(python_version = "3.9")
69 return default_python_distribution(python_version = "3.9")
70
70
71 def resource_callback(policy, resource):
71 def resource_callback(policy, resource):
72 if not IS_WINDOWS:
72 if not IS_WINDOWS:
73 resource.add_location = "in-memory"
73 resource.add_location = "in-memory"
74 return
74 return
75
75
76 # We use a custom resource routing policy to influence where things are loaded
76 # We use a custom resource routing policy to influence where things are loaded
77 # from.
77 # from.
78 #
78 #
79 # For Python modules and resources, we load from memory if they are in
79 # For Python modules and resources, we load from memory if they are in
80 # the standard library and from the filesystem if not. This is because
80 # the standard library and from the filesystem if not. This is because
81 # parts of Mercurial and some 3rd party packages aren't yet compatible
81 # parts of Mercurial and some 3rd party packages aren't yet compatible
82 # with memory loading.
82 # with memory loading.
83 #
83 #
84 # For Python extension modules, we load from the filesystem because
84 # For Python extension modules, we load from the filesystem because
85 # this yields greatest compatibility.
85 # this yields greatest compatibility.
86 if type(resource) in ("PythonModuleSource", "PythonPackageResource", "PythonPackageDistributionResource"):
86 if type(resource) in ("PythonModuleSource", "PythonPackageResource", "PythonPackageDistributionResource"):
87 if resource.is_stdlib:
87 if resource.is_stdlib:
88 resource.add_location = "in-memory"
88 resource.add_location = "in-memory"
89 else:
89 else:
90 resource.add_location = "filesystem-relative:lib"
90 resource.add_location = "filesystem-relative:lib"
91
91
92 elif type(resource) == "PythonExtensionModule":
92 elif type(resource) == "PythonExtensionModule":
93 resource.add_location = "filesystem-relative:lib"
93 resource.add_location = "filesystem-relative:lib"
94
94
95 def make_exe(dist):
95 def make_exe(dist):
96 """Builds a Rust-wrapped Mercurial binary."""
96 """Builds a Rust-wrapped Mercurial binary."""
97 packaging_policy = dist.make_python_packaging_policy()
97 packaging_policy = dist.make_python_packaging_policy()
98
98
99 # Extension may depend on any Python functionality. Include all
99 # Extension may depend on any Python functionality. Include all
100 # extensions.
100 # extensions.
101 packaging_policy.extension_module_filter = "all"
101 packaging_policy.extension_module_filter = "all"
102 packaging_policy.resources_location = "in-memory"
102 packaging_policy.resources_location = "in-memory"
103 if IS_WINDOWS:
103 if IS_WINDOWS:
104 packaging_policy.resources_location_fallback = "filesystem-relative:lib"
104 packaging_policy.resources_location_fallback = "filesystem-relative:lib"
105 packaging_policy.register_resource_callback(resource_callback)
105 packaging_policy.register_resource_callback(resource_callback)
106
106
107 config = dist.make_python_interpreter_config()
107 config = dist.make_python_interpreter_config()
108 config.allocator_backend = "default"
108 config.allocator_backend = "default"
109 config.run_command = RUN_CODE
109 config.run_command = RUN_CODE
110
110
111 # We want to let the user load extensions from the file system
111 # We want to let the user load extensions from the file system
112 config.filesystem_importer = True
112 config.filesystem_importer = True
113
113
114 # We need this to make resourceutil happy, since it looks for sys.frozen.
114 # We need this to make resourceutil happy, since it looks for sys.frozen.
115 config.sys_frozen = True
115 config.sys_frozen = True
116 config.legacy_windows_stdio = True
116 config.legacy_windows_stdio = True
117
117
118 exe = dist.to_python_executable(
118 exe = dist.to_python_executable(
119 name = "hg",
119 name = "hg",
120 packaging_policy = packaging_policy,
120 packaging_policy = packaging_policy,
121 config = config,
121 config = config,
122 )
122 )
123
123
124 # Add Mercurial to resources.
124 # Add Mercurial to resources.
125 exe.add_python_resources(exe.pip_install(["--verbose", ROOT]))
125 exe.add_python_resources(exe.pip_install(["--verbose", ROOT]))
126
126
127 # On Windows, we install extra packages for convenience.
127 # On Windows, we install extra packages for convenience.
128 if IS_WINDOWS:
128 if IS_WINDOWS:
129 exe.add_python_resources(
129 exe.add_python_resources(
130 exe.pip_install(["-r", ROOT + "/contrib/packaging/requirements-windows-py3.txt"]),
130 exe.pip_install(["-r", ROOT + "/contrib/packaging/requirements-windows-py3.txt"]),
131 )
131 )
132 extra_packages = VARS.get("extra_py_packages", "")
132 extra_packages = VARS.get("extra_py_packages", "")
133 if extra_packages:
133 if extra_packages:
134 for extra in extra_packages.split(","):
134 for extra in extra_packages.split(","):
135 extra_src, pkgs = extra.split("=")
135 extra_src, pkgs = extra.split("=")
136 pkgs = pkgs.split(":")
136 pkgs = pkgs.split(":")
137 exe.add_python_resources(exe.read_package_root(extra_src, pkgs))
137 exe.add_python_resources(exe.read_package_root(extra_src, pkgs))
138
138
139 return exe
139 return exe
140
140
141 def make_manifest(dist, exe):
141 def make_manifest(dist, exe):
142 m = FileManifest()
142 m = FileManifest()
143 m.add_python_resource(".", exe)
143 m.add_python_resource(".", exe)
144
144
145 return m
145 return m
146
146
147
147
148 # This adjusts the InstallManifest produced from exe generation to provide
148 # This adjusts the InstallManifest produced from exe generation to provide
149 # additional files found in a Windows install layout.
149 # additional files found in a Windows install layout.
150 def make_windows_install_layout(manifest):
150 def make_windows_install_layout(manifest):
151 # Copy various files to new install locations. This can go away once
151 # Copy various files to new install locations. This can go away once
152 # we're using the importlib resource reader.
152 # we're using the importlib resource reader.
153 RECURSIVE_COPIES = {
153 RECURSIVE_COPIES = {
154 "lib/mercurial/locale/": "locale/",
154 "lib/mercurial/locale/": "locale/",
155 "lib/mercurial/templates/": "templates/",
155 "lib/mercurial/templates/": "templates/",
156 }
156 }
157 for (search, replace) in RECURSIVE_COPIES.items():
157 for (search, replace) in RECURSIVE_COPIES.items():
158 for path in manifest.paths():
158 for path in manifest.paths():
159 if path.startswith(search):
159 if path.startswith(search):
160 new_path = path.replace(search, replace)
160 new_path = path.replace(search, replace)
161 print("copy %s to %s" % (path, new_path))
161 print("copy %s to %s" % (path, new_path))
162 file = manifest.get_file(path)
162 file = manifest.get_file(path)
163 manifest.add_file(file, path = new_path)
163 manifest.add_file(file, path = new_path)
164
164
165 # Similar to above, but with filename pattern matching.
165 # Similar to above, but with filename pattern matching.
166 # lib/mercurial/helptext/**/*.txt -> helptext/
166 # lib/mercurial/helptext/**/*.txt -> helptext/
167 # lib/mercurial/defaultrc/*.rc -> defaultrc/
167 # lib/mercurial/defaultrc/*.rc -> defaultrc/
168 for path in manifest.paths():
168 for path in manifest.paths():
169 if path.startswith("lib/mercurial/helptext/") and path.endswith(".txt"):
169 if path.startswith("lib/mercurial/helptext/") and path.endswith(".txt"):
170 new_path = path[len("lib/mercurial/"):]
170 new_path = path[len("lib/mercurial/"):]
171 elif path.startswith("lib/mercurial/defaultrc/") and path.endswith(".rc"):
171 elif path.startswith("lib/mercurial/defaultrc/") and path.endswith(".rc"):
172 new_path = path[len("lib/mercurial/"):]
172 new_path = path[len("lib/mercurial/"):]
173 else:
173 else:
174 continue
174 continue
175
175
176 print("copying %s to %s" % (path, new_path))
176 print("copying %s to %s" % (path, new_path))
177 manifest.add_file(manifest.get_file(path), path = new_path)
177 manifest.add_file(manifest.get_file(path), path = new_path)
178
178
179 extra_install_files = VARS.get("extra_install_files", "")
179 extra_install_files = VARS.get("extra_install_files", "")
180 if extra_install_files:
180 if extra_install_files:
181 for extra in extra_install_files.split(","):
181 for extra in extra_install_files.split(","):
182 print("adding extra files from %s" % extra)
182 print("adding extra files from %s" % extra)
183 # TODO: I expected a ** glob to work, but it didn't.
183 # TODO: I expected a ** glob to work, but it didn't.
184 #
184 #
185 # TODO: I know this has forward-slash paths. As far as I can tell,
185 # TODO: I know this has forward-slash paths. As far as I can tell,
186 # backslashes don't ever match glob() expansions in
186 # backslashes don't ever match glob() expansions in
187 # tugger-starlark, even on Windows.
187 # tugger-starlark, even on Windows.
188 manifest.add_manifest(glob(include=[extra + "/*/*"], strip_prefix=extra+"/"))
188 manifest.add_manifest(glob(include=[extra + "/*/*"], strip_prefix=extra+"/"))
189
189
190 # We also install a handful of additional files.
190 # We also install a handful of additional files.
191 EXTRA_CONTRIB_FILES = [
191 EXTRA_CONTRIB_FILES = [
192 "bash_completion",
192 "bash_completion",
193 "hgweb.fcgi",
193 "hgweb.fcgi",
194 "hgweb.wsgi",
194 "hgweb.wsgi",
195 "logo-droplets.svg",
195 "logo-droplets.svg",
196 "mercurial.el",
196 "mercurial.el",
197 "mq.el",
197 "mq.el",
198 "tcsh_completion",
198 "tcsh_completion",
199 "tcsh_completion_build.sh",
199 "tcsh_completion_build.sh",
200 "xml.rnc",
200 "xml.rnc",
201 "zsh_completion",
201 "zsh_completion",
202 ]
202 ]
203
203
204 for f in EXTRA_CONTRIB_FILES:
204 for f in EXTRA_CONTRIB_FILES:
205 manifest.add_file(FileContent(path = ROOT + "/contrib/" + f), directory = "contrib")
205 manifest.add_file(FileContent(path = ROOT + "/contrib/" + f), directory = "contrib")
206
206
207 # Individual files with full source to destination path mapping.
207 # Individual files with full source to destination path mapping.
208 EXTRA_FILES = {
208 EXTRA_FILES = {
209 "contrib/hgk": "contrib/hgk.tcl",
209 "contrib/hgk": "contrib/hgk.tcl",
210 "contrib/win32/postinstall.txt": "ReleaseNotes.txt",
210 "contrib/win32/postinstall.txt": "ReleaseNotes.txt",
211 "contrib/win32/ReadMe.html": "ReadMe.html",
211 "contrib/win32/ReadMe.html": "ReadMe.html",
212 "doc/style.css": "doc/style.css",
212 "doc/style.css": "doc/style.css",
213 "COPYING": "Copying.txt",
213 "COPYING": "Copying.txt",
214 }
214 }
215
215
216 for source, dest in EXTRA_FILES.items():
216 for source, dest in EXTRA_FILES.items():
217 print("adding extra file %s" % dest)
217 print("adding extra file %s" % dest)
218 manifest.add_file(FileContent(path = ROOT + "/" + source), path = dest)
218 manifest.add_file(FileContent(path = ROOT + "/" + source), path = dest)
219
219
220 # And finally some wildcard matches.
220 # And finally some wildcard matches.
221 manifest.add_manifest(glob(
221 manifest.add_manifest(glob(
222 include = [ROOT + "/contrib/vim/*"],
222 include = [ROOT + "/contrib/vim/*"],
223 strip_prefix = ROOT + "/"
223 strip_prefix = ROOT + "/"
224 ))
224 ))
225 manifest.add_manifest(glob(
225 manifest.add_manifest(glob(
226 include = [ROOT + "/doc/*.html"],
226 include = [ROOT + "/doc/*.html"],
227 strip_prefix = ROOT + "/"
227 strip_prefix = ROOT + "/"
228 ))
228 ))
229
229
230 # But we don't ship hg-ssh on Windows, so exclude its documentation.
230 # But we don't ship hg-ssh on Windows, so exclude its documentation.
231 manifest.remove("doc/hg-ssh.8.html")
231 manifest.remove("doc/hg-ssh.8.html")
232
232
233 return manifest
233 return manifest
234
234
235
235
236 def make_msi(manifest):
236 def make_msi(manifest):
237 manifest = make_windows_install_layout(manifest)
237 manifest = make_windows_install_layout(manifest)
238
238
239 if "x86_64" in BUILD_TARGET_TRIPLE:
239 if "x86_64" in BUILD_TARGET_TRIPLE:
240 platform = "x64"
240 platform = "x64"
241 else:
241 else:
242 platform = "x86"
242 platform = "x86"
243
243
244 manifest.add_file(
244 manifest.add_file(
245 FileContent(path = ROOT + "/contrib/packaging/wix/COPYING.rtf"),
245 FileContent(path = ROOT + "/contrib/packaging/wix/COPYING.rtf"),
246 path = "COPYING.rtf",
246 path = "COPYING.rtf",
247 )
247 )
248 manifest.remove("Copying.txt")
248 manifest.remove("Copying.txt")
249 manifest.add_file(
249 manifest.add_file(
250 FileContent(path = ROOT + "/contrib/win32/mercurial.ini"),
250 FileContent(path = ROOT + "/contrib/win32/mercurial.ini"),
251 path = "defaultrc/mercurial.rc",
251 path = "defaultrc/mercurial.rc",
252 )
252 )
253 manifest.add_file(
253 manifest.add_file(
254 FileContent(filename = "editor.rc", content = "[ui]\neditor = notepad\n"),
254 FileContent(filename = "editor.rc", content = "[ui]\neditor = notepad\n"),
255 path = "defaultrc/editor.rc",
255 path = "defaultrc/editor.rc",
256 )
256 )
257
257
258 wix = WiXInstaller("hg", "%s-%s.msi" % (MSI_NAME, VERSION), arch = platform)
258 wix = WiXInstaller(
259 "hg",
260 "%s-%s-%s.msi" % (MSI_NAME, VERSION, platform),
261 arch = platform,
262 )
259
263
260 # Materialize files in the manifest to the install layout.
264 # Materialize files in the manifest to the install layout.
261 wix.add_install_files(manifest)
265 wix.add_install_files(manifest)
262
266
263 # From mercurial.wxs.
267 # From mercurial.wxs.
264 wix.install_files_root_directory_id = "INSTALLDIR"
268 wix.install_files_root_directory_id = "INSTALLDIR"
265
269
266 # Pull in our custom .wxs files.
270 # Pull in our custom .wxs files.
267 defines = {
271 defines = {
268 "PyOxidizer": "1",
272 "PyOxidizer": "1",
269 "Platform": platform,
273 "Platform": platform,
270 "Version": VERSION,
274 "Version": VERSION,
271 "Comments": "Installs Mercurial version %s" % VERSION,
275 "Comments": "Installs Mercurial version %s" % VERSION,
272 "PythonVersion": "3",
276 "PythonVersion": "3",
273 "MercurialHasLib": "1",
277 "MercurialHasLib": "1",
274 }
278 }
275
279
276 if EXTRA_MSI_FEATURES:
280 if EXTRA_MSI_FEATURES:
277 defines["MercurialExtraFeatures"] = EXTRA_MSI_FEATURES
281 defines["MercurialExtraFeatures"] = EXTRA_MSI_FEATURES
278
282
279 wix.add_wxs_file(
283 wix.add_wxs_file(
280 ROOT + "/contrib/packaging/wix/mercurial.wxs",
284 ROOT + "/contrib/packaging/wix/mercurial.wxs",
281 preprocessor_parameters=defines,
285 preprocessor_parameters=defines,
282 )
286 )
283
287
284 # Our .wxs references to other files. Pull those into the build environment.
288 # Our .wxs references to other files. Pull those into the build environment.
285 for f in ("defines.wxi", "guids.wxi", "COPYING.rtf"):
289 for f in ("defines.wxi", "guids.wxi", "COPYING.rtf"):
286 wix.add_build_file(f, ROOT + "/contrib/packaging/wix/" + f)
290 wix.add_build_file(f, ROOT + "/contrib/packaging/wix/" + f)
287
291
288 wix.add_build_file("mercurial.ico", ROOT + "/contrib/win32/mercurial.ico")
292 wix.add_build_file("mercurial.ico", ROOT + "/contrib/win32/mercurial.ico")
289
293
290 return wix
294 return wix
291
295
292
296
293 def register_code_signers():
297 def register_code_signers():
294 if not IS_WINDOWS:
298 if not IS_WINDOWS:
295 return
299 return
296
300
297 if SIGNING_PFX_PATH:
301 if SIGNING_PFX_PATH:
298 signer = code_signer_from_pfx_file(SIGNING_PFX_PATH, SIGNING_PFX_PASSWORD)
302 signer = code_signer_from_pfx_file(SIGNING_PFX_PATH, SIGNING_PFX_PASSWORD)
299 elif SIGNING_SUBJECT_NAME:
303 elif SIGNING_SUBJECT_NAME:
300 signer = code_signer_from_windows_store_subject(SIGNING_SUBJECT_NAME)
304 signer = code_signer_from_windows_store_subject(SIGNING_SUBJECT_NAME)
301 else:
305 else:
302 signer = None
306 signer = None
303
307
304 if signer:
308 if signer:
305 signer.set_time_stamp_server(TIME_STAMP_SERVER_URL)
309 signer.set_time_stamp_server(TIME_STAMP_SERVER_URL)
306 signer.activate()
310 signer.activate()
307
311
308
312
309 register_code_signers()
313 register_code_signers()
310
314
311 register_target("distribution", make_distribution)
315 register_target("distribution", make_distribution)
312 register_target("exe", make_exe, depends = ["distribution"])
316 register_target("exe", make_exe, depends = ["distribution"])
313 register_target("app", make_manifest, depends = ["distribution", "exe"], default = True)
317 register_target("app", make_manifest, depends = ["distribution", "exe"], default = True)
314 register_target("msi", make_msi, depends = ["app"])
318 register_target("msi", make_msi, depends = ["app"])
315
319
316 resolve_targets()
320 resolve_targets()
General Comments 0
You need to be logged in to leave comments. Login now