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_ |
|
|
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 = |
|
|
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 |
|
|
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