##// END OF EJS Templates
packaging: support building Inno installer with PyOxidizer...
Gregory Szorc -
r45270:94f4f2ec stable
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 SOURCE_DIR = HERE.parent.parent.parent
20 SOURCE_DIR = HERE.parent.parent.parent
21
21
22
22
23 def build_inno(python=None, iscc=None, version=None):
23 def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None):
24 if not os.path.isabs(python):
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 raise Exception("--python arg must be an absolute path")
28 raise Exception("--python arg must be an absolute path")
26
29
27 if iscc:
30 if iscc:
@@ -35,9 +38,14 b' def build_inno(python=None, iscc=None, v'
35
38
36 build_dir = SOURCE_DIR / "build"
39 build_dir = SOURCE_DIR / "build"
37
40
38 inno.build(
41 if pyoxidizer_target:
39 SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version,
42 inno.build_with_pyoxidizer(
40 )
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 def build_wix(
51 def build_wix(
@@ -88,7 +96,12 b' def get_parser():'
88 subparsers = parser.add_subparsers()
96 subparsers = parser.add_subparsers()
89
97
90 sp = subparsers.add_parser("inno", help="Build Inno Setup installer")
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 sp.add_argument("--iscc", help="path to iscc.exe to use")
105 sp.add_argument("--iscc", help="path to iscc.exe to use")
93 sp.add_argument(
106 sp.add_argument(
94 "--version",
107 "--version",
@@ -18,8 +18,9 b' from .py2exe import ('
18 build_py2exe,
18 build_py2exe,
19 stage_install,
19 stage_install,
20 )
20 )
21 from .pyoxidizer import run_pyoxidizer
21 from .util import (
22 from .util import (
22 find_vc_runtime_files,
23 find_legacy_vc_runtime_files,
23 normalize_windows_version,
24 normalize_windows_version,
24 process_install_rules,
25 process_install_rules,
25 read_version_py,
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 source_dir: pathlib.Path,
46 source_dir: pathlib.Path,
46 build_dir: pathlib.Path,
47 build_dir: pathlib.Path,
47 python_exe: pathlib.Path,
48 python_exe: pathlib.Path,
48 iscc_exe: pathlib.Path,
49 iscc_exe: pathlib.Path,
49 version=None,
50 version=None,
50 ):
51 ):
51 """Build the Inno installer.
52 """Build the Inno installer using py2exe.
52
53
53 Build files will be placed in ``build_dir``.
54 Build files will be placed in ``build_dir``.
54
55
@@ -92,7 +93,7 b' def build('
92 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
93 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
93
94
94 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
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 if f.name.endswith('.manifest'):
97 if f.name.endswith('.manifest'):
97 basename = 'Microsoft.VC90.CRT.manifest'
98 basename = 'Microsoft.VC90.CRT.manifest'
98 else:
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 def build_installer(
146 def build_installer(
117 source_dir: pathlib.Path,
147 source_dir: pathlib.Path,
118 inno_build_dir: pathlib.Path,
148 inno_build_dir: pathlib.Path,
@@ -29,7 +29,59 b' def extract_zip_to_directory(source: pat'
29 zf.extractall(dest)
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 """Finds Visual C++ Runtime DLLs to include in distribution."""
85 """Finds Visual C++ Runtime DLLs to include in distribution."""
34 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
86 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
35
87
@@ -1,13 +1,24 b''
1 ROOT = CWD + "/../.."
1 ROOT = CWD + "/../.."
2
2
3 def make_exe():
3 # Code to run in Python interpreter.
4 dist = default_python_distribution()
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 config = PythonInterpreterConfig(
19 config = PythonInterpreterConfig(
9 raw_allocator = "system",
20 raw_allocator = "system",
10 run_eval = code,
21 run_eval = RUN_CODE,
11 # We want to let the user load extensions from the file system
22 # We want to let the user load extensions from the file system
12 filesystem_importer = True,
23 filesystem_importer = True,
13 # We need this to make resourceutil happy, since it looks for sys.frozen.
24 # We need this to make resourceutil happy, since it looks for sys.frozen.
@@ -24,30 +35,65 b' def make_exe():'
24 extension_module_filter = "all",
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 return exe
64 return exe
30
65
31 def make_install(exe):
66
67 def make_manifest(dist, exe):
32 m = FileManifest()
68 m = FileManifest()
33
34 # `hg` goes in root directory.
35 m.add_python_resource(".", exe)
69 m.add_python_resource(".", exe)
36
70
37 templates = glob(
71 return m
38 include = [ROOT + "/mercurial/templates/**/*"],
39 strip_prefix = ROOT + "/mercurial/",
40 )
41 m.add_manifest(templates)
42
72
43 return m
44
73
45 def make_embedded_resources(exe):
74 def make_embedded_resources(exe):
46 return exe.to_embedded_resources()
75 return exe.to_embedded_resources()
47
76
48 register_target("exe", make_exe)
77
49 register_target("app", make_install, depends = ["exe"], default = True)
78 register_target("distribution_posix", make_distribution)
50 register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True)
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 resolve_targets()
97 resolve_targets()
52
98
53 # END OF COMMON USER-ADJUSTED SETTINGS.
99 # END OF COMMON USER-ADJUSTED SETTINGS.
@@ -55,5 +101,4 b' resolve_targets()'
55 # Everything below this is typically managed by PyOxidizer and doesn't need
101 # Everything below this is typically managed by PyOxidizer and doesn't need
56 # to be updated by people.
102 # to be updated by people.
57
103
58 PYOXIDIZER_VERSION = "0.7.0-pre"
104 PYOXIDIZER_VERSION = "0.7.0"
59 PYOXIDIZER_COMMIT = "c772a1379c3026314eda1c8ea244b86c0658951d"
@@ -27,6 +27,7 b' New errors are not allowed. Warnings are'
27 Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
27 Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
28 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
28 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
29 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
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 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
31 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
31 Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
32 Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
32 Skipping i18n/polib.py it has no-che?k-code (glob)
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