##// END OF EJS Templates
packaging: support building Inno installer with PyOxidizer...
Gregory Szorc -
r45256:9965d6c3 default draft
parent child Browse files
Show More
@@ -0,0 +1,145 b''
1 # pyoxidizer.py - Packaging support for PyOxidizer
2 #
3 # Copyright 2020 Gregory Szorc <gregory.szorc@gmail.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 # no-check-code because Python 3 native.
9
10 import os
11 import pathlib
12 import shutil
13 import subprocess
14 import sys
15
16 from .downloads import download_entry
17 from .util import (
18 extract_zip_to_directory,
19 process_install_rules,
20 find_vc_runtime_dll,
21 )
22
23
24 STAGING_RULES_WINDOWS = [
25 ('contrib/bash_completion', 'contrib/'),
26 ('contrib/hgk', 'contrib/hgk.tcl'),
27 ('contrib/hgweb.fcgi', 'contrib/'),
28 ('contrib/hgweb.wsgi', 'contrib/'),
29 ('contrib/logo-droplets.svg', 'contrib/'),
30 ('contrib/mercurial.el', 'contrib/'),
31 ('contrib/mq.el', 'contrib/'),
32 ('contrib/tcsh_completion', 'contrib/'),
33 ('contrib/tcsh_completion_build.sh', 'contrib/'),
34 ('contrib/vim/*', 'contrib/vim/'),
35 ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
36 ('contrib/win32/ReadMe.html', 'ReadMe.html'),
37 ('contrib/xml.rnc', 'contrib/'),
38 ('contrib/zsh_completion', 'contrib/'),
39 ('doc/*.html', 'doc/'),
40 ('doc/style.css', 'doc/'),
41 ('COPYING', 'Copying.txt'),
42 ]
43
44 STAGING_RULES_APP = [
45 ('mercurial/helptext/**/*.txt', 'helptext/'),
46 ('mercurial/defaultrc/*.rc', 'defaultrc/'),
47 ('mercurial/locale/**/*', 'locale/'),
48 ('mercurial/templates/**/*', 'templates/'),
49 ]
50
51 STAGING_EXCLUDES_WINDOWS = [
52 "doc/hg-ssh.8.html",
53 ]
54
55
56 def run_pyoxidizer(
57 source_dir: pathlib.Path,
58 build_dir: pathlib.Path,
59 out_dir: pathlib.Path,
60 target_triple: str,
61 ):
62 """Build Mercurial with PyOxidizer and copy additional files into place.
63
64 After successful completion, ``out_dir`` contains files constituting a
65 Mercurial install.
66 """
67 # We need to make gettext binaries available for compiling i18n files.
68 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
69 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
70
71 gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
72
73 if not gettext_root.exists():
74 extract_zip_to_directory(gettext_pkg, gettext_root)
75 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
76
77 env = dict(os.environ)
78 env["PATH"] = "%s%s%s" % (
79 env["PATH"],
80 os.pathsep,
81 str(gettext_root / "bin"),
82 )
83
84 args = [
85 "pyoxidizer",
86 "build",
87 "--path",
88 str(source_dir / "rust" / "hgcli"),
89 "--release",
90 "--target-triple",
91 target_triple,
92 ]
93
94 subprocess.run(args, env=env, check=True)
95
96 if "windows" in target_triple:
97 target = "app_windows"
98 else:
99 target = "app_posix"
100
101 build_dir = (
102 source_dir / "build" / "pyoxidizer" / target_triple / "release" / target
103 )
104
105 if out_dir.exists():
106 print("purging %s" % out_dir)
107 shutil.rmtree(out_dir)
108
109 # Now assemble all the files from PyOxidizer into the staging directory.
110 shutil.copytree(build_dir, out_dir)
111
112 # Move some of those files around.
113 process_install_rules(STAGING_RULES_APP, build_dir, out_dir)
114 # Nuke the mercurial/* directory, as we copied resources
115 # to an appropriate location just above.
116 shutil.rmtree(out_dir / "mercurial")
117
118 # We also need to run setup.py build_doc to produce html files,
119 # as they aren't built as part of ``pip install``.
120 # This will fail if docutils isn't installed.
121 subprocess.run(
122 [sys.executable, str(source_dir / "setup.py"), "build_doc", "--html"],
123 cwd=str(source_dir),
124 check=True,
125 )
126
127 if "windows" in target_triple:
128 process_install_rules(STAGING_RULES_WINDOWS, source_dir, out_dir)
129
130 # Write out a default editor.rc file to configure notepad as the
131 # default editor.
132 with (out_dir / "defaultrc" / "editor.rc").open(
133 "w", encoding="utf-8"
134 ) as fh:
135 fh.write("[ui]\neditor = notepad\n")
136
137 for f in STAGING_EXCLUDES_WINDOWS:
138 p = out_dir / f
139 if p.exists():
140 print("removing %s" % p)
141 p.unlink()
142
143 # Add vcruntimeXXX.dll next to executable.
144 vc_runtime_dll = find_vc_runtime_dll(x64="x86_64" in target_triple)
145 shutil.copy(vc_runtime_dll, out_dir / vc_runtime_dll.name)
@@ -20,8 +20,11 b' HERE = pathlib.Path(os.path.abspath(os.p'
20 20 SOURCE_DIR = HERE.parent.parent.parent
21 21
22 22
23 def build_inno(python=None, iscc=None, version=None):
24 if not os.path.isabs(python):
23 def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None):
24 if not pyoxidizer_target and not python:
25 raise Exception("--python required unless building with PyOxidizer")
26
27 if python and not os.path.isabs(python):
25 28 raise Exception("--python arg must be an absolute path")
26 29
27 30 if iscc:
@@ -35,9 +38,14 b' def build_inno(python=None, iscc=None, v'
35 38
36 39 build_dir = SOURCE_DIR / "build"
37 40
38 inno.build(
39 SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version,
40 )
41 if pyoxidizer_target:
42 inno.build_with_pyoxidizer(
43 SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version
44 )
45 else:
46 inno.build_with_py2exe(
47 SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version,
48 )
41 49
42 50
43 51 def build_wix(
@@ -88,7 +96,12 b' def get_parser():'
88 96 subparsers = parser.add_subparsers()
89 97
90 98 sp = subparsers.add_parser("inno", help="Build Inno Setup installer")
91 sp.add_argument("--python", required=True, help="path to python.exe to use")
99 sp.add_argument(
100 "--pyoxidizer-target",
101 choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
102 help="Build with PyOxidizer targeting this host triple",
103 )
104 sp.add_argument("--python", help="path to python.exe to use")
92 105 sp.add_argument("--iscc", help="path to iscc.exe to use")
93 106 sp.add_argument(
94 107 "--version",
@@ -18,8 +18,9 b' from .py2exe import ('
18 18 build_py2exe,
19 19 stage_install,
20 20 )
21 from .pyoxidizer import run_pyoxidizer
21 22 from .util import (
22 find_vc_runtime_files,
23 find_legacy_vc_runtime_files,
23 24 normalize_windows_version,
24 25 process_install_rules,
25 26 read_version_py,
@@ -41,14 +42,14 b' PACKAGE_FILES_METADATA = {'
41 42 }
42 43
43 44
44 def build(
45 def build_with_py2exe(
45 46 source_dir: pathlib.Path,
46 47 build_dir: pathlib.Path,
47 48 python_exe: pathlib.Path,
48 49 iscc_exe: pathlib.Path,
49 50 version=None,
50 51 ):
51 """Build the Inno installer.
52 """Build the Inno installer using py2exe.
52 53
53 54 Build files will be placed in ``build_dir``.
54 55
@@ -92,7 +93,7 b' def build('
92 93 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
93 94
94 95 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
95 for f in find_vc_runtime_files(vc_x64):
96 for f in find_legacy_vc_runtime_files(vc_x64):
96 97 if f.name.endswith('.manifest'):
97 98 basename = 'Microsoft.VC90.CRT.manifest'
98 99 else:
@@ -113,6 +114,35 b' def build('
113 114 )
114 115
115 116
117 def build_with_pyoxidizer(
118 source_dir: pathlib.Path,
119 build_dir: pathlib.Path,
120 target_triple: str,
121 iscc_exe: pathlib.Path,
122 version=None,
123 ):
124 """Build the Inno installer using PyOxidizer."""
125 if not iscc_exe.exists():
126 raise Exception("%s does not exist" % iscc_exe)
127
128 inno_build_dir = build_dir / ("inno-pyoxidizer-%s" % target_triple)
129 staging_dir = inno_build_dir / "stage"
130
131 inno_build_dir.mkdir(parents=True, exist_ok=True)
132 run_pyoxidizer(source_dir, inno_build_dir, staging_dir, target_triple)
133
134 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
135
136 build_installer(
137 source_dir,
138 inno_build_dir,
139 staging_dir,
140 iscc_exe,
141 version,
142 arch="x64" if "x86_64" in target_triple else None,
143 )
144
145
116 146 def build_installer(
117 147 source_dir: pathlib.Path,
118 148 inno_build_dir: pathlib.Path,
@@ -29,7 +29,59 b' def extract_zip_to_directory(source: pat'
29 29 zf.extractall(dest)
30 30
31 31
32 def find_vc_runtime_files(x64=False):
32 def find_vc_runtime_dll(x64=False):
33 """Finds Visual C++ Runtime DLL to include in distribution."""
34 # We invoke vswhere to find the latest Visual Studio install.
35 vswhere = (
36 pathlib.Path(os.environ["ProgramFiles(x86)"])
37 / "Microsoft Visual Studio"
38 / "Installer"
39 / "vswhere.exe"
40 )
41
42 if not vswhere.exists():
43 raise Exception(
44 "could not find vswhere.exe: %s does not exist" % vswhere
45 )
46
47 args = [
48 str(vswhere),
49 # -products * is necessary to return results from Build Tools
50 # (as opposed to full IDE installs).
51 "-products",
52 "*",
53 "-requires",
54 "Microsoft.VisualCpp.Redist.14.Latest",
55 "-latest",
56 "-property",
57 "installationPath",
58 ]
59
60 vs_install_path = pathlib.Path(
61 os.fsdecode(subprocess.check_output(args).strip())
62 )
63
64 # This just gets us a path like
65 # C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
66 # Actually vcruntime140.dll is under a path like:
67 # VC\Redist\MSVC\<version>\<arch>\Microsoft.VC14<X>.CRT\vcruntime140.dll.
68
69 arch = "x64" if x64 else "x86"
70
71 search_glob = (
72 r"%s\VC\Redist\MSVC\*\%s\Microsoft.VC14*.CRT\vcruntime140.dll"
73 % (vs_install_path, arch)
74 )
75
76 candidates = glob.glob(search_glob, recursive=True)
77
78 for candidate in reversed(candidates):
79 return pathlib.Path(candidate)
80
81 raise Exception("could not find vcruntime140.dll")
82
83
84 def find_legacy_vc_runtime_files(x64=False):
33 85 """Finds Visual C++ Runtime DLLs to include in distribution."""
34 86 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
35 87
@@ -1,13 +1,24 b''
1 1 ROOT = CWD + "/../.."
2 2
3 def make_exe():
4 dist = default_python_distribution()
3 # Code to run in Python interpreter.
4 RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()"
5
6
7 set_build_path(ROOT + "/build/pyoxidizer")
8
5 9
6 code = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()"
10 def make_distribution():
11 return default_python_distribution()
12
7 13
14 def make_distribution_windows():
15 return default_python_distribution(flavor="standalone_dynamic")
16
17
18 def make_exe(dist):
8 19 config = PythonInterpreterConfig(
9 20 raw_allocator = "system",
10 run_eval = code,
21 run_eval = RUN_CODE,
11 22 # We want to let the user load extensions from the file system
12 23 filesystem_importer = True,
13 24 # We need this to make resourceutil happy, since it looks for sys.frozen.
@@ -24,30 +35,65 b' def make_exe():'
24 35 extension_module_filter = "all",
25 36 )
26 37
27 exe.add_python_resources(dist.pip_install([ROOT]))
38 # Add Mercurial to resources.
39 for resource in dist.pip_install(["--verbose", ROOT]):
40 # This is a bit wonky and worth explaining.
41 #
42 # Various parts of Mercurial don't yet support loading package
43 # resources via the ResourceReader interface. Or, not having
44 # file-based resources would be too inconvenient for users.
45 #
46 # So, for package resources, we package them both in the
47 # filesystem as well as in memory. If both are defined,
48 # PyOxidizer will prefer the in-memory location. So even
49 # if the filesystem file isn't packaged in the location
50 # specified here, we should never encounter an errors as the
51 # resource will always be available in memory.
52 if type(resource) == "PythonPackageResource":
53 exe.add_filesystem_relative_python_resource(".", resource)
54 exe.add_in_memory_python_resource(resource)
55 else:
56 exe.add_python_resource(resource)
57
58 # On Windows, we install extra packages for convenience.
59 if "windows" in BUILD_TARGET_TRIPLE:
60 exe.add_python_resources(
61 dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"])
62 )
28 63
29 64 return exe
30 65
31 def make_install(exe):
66
67 def make_manifest(dist, exe):
32 68 m = FileManifest()
33
34 # `hg` goes in root directory.
35 69 m.add_python_resource(".", exe)
36 70
37 templates = glob(
38 include = [ROOT + "/mercurial/templates/**/*"],
39 strip_prefix = ROOT + "/mercurial/",
40 )
41 m.add_manifest(templates)
71 return m
42 72
43 return m
44 73
45 74 def make_embedded_resources(exe):
46 75 return exe.to_embedded_resources()
47 76
48 register_target("exe", make_exe)
49 register_target("app", make_install, depends = ["exe"], default = True)
50 register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True)
77
78 register_target("distribution_posix", make_distribution)
79 register_target("distribution_windows", make_distribution_windows)
80
81 register_target("exe_posix", make_exe, depends = ["distribution_posix"])
82 register_target("exe_windows", make_exe, depends = ["distribution_windows"])
83
84 register_target(
85 "app_posix",
86 make_manifest,
87 depends = ["distribution_posix", "exe_posix"],
88 default = "windows" not in BUILD_TARGET_TRIPLE,
89 )
90 register_target(
91 "app_windows",
92 make_manifest,
93 depends = ["distribution_windows", "exe_windows"],
94 default = "windows" in BUILD_TARGET_TRIPLE,
95 )
96
51 97 resolve_targets()
52 98
53 99 # END OF COMMON USER-ADJUSTED SETTINGS.
@@ -55,5 +101,4 b' resolve_targets()'
55 101 # Everything below this is typically managed by PyOxidizer and doesn't need
56 102 # to be updated by people.
57 103
58 PYOXIDIZER_VERSION = "0.7.0-pre"
59 PYOXIDIZER_COMMIT = "c772a1379c3026314eda1c8ea244b86c0658951d"
104 PYOXIDIZER_VERSION = "0.7.0"
@@ -27,6 +27,7 b' New errors are not allowed. Warnings are'
27 27 Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
28 28 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
29 29 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
30 Skipping contrib/packaging/hgpackaging/pyoxidizer.py it has no-che?k-code (glob)
30 31 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
31 32 Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
32 33 Skipping i18n/polib.py it has no-che?k-code (glob)
General Comments 0
You need to be logged in to leave comments. Login now