##// END OF EJS Templates
packaging: lowercase the `contrib` and `templates` directories with Inno...
Matt Harbison -
r44712:4aedef6d stable
parent child Browse files
Show More
@@ -1,245 +1,245
1 # py2exe.py - Functionality for performing py2exe builds.
1 # py2exe.py - Functionality for performing py2exe builds.
2 #
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
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.
6 # GNU General Public License version 2 or any later version.
7
7
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import os
10 import os
11 import pathlib
11 import pathlib
12 import subprocess
12 import subprocess
13
13
14 from .downloads import download_entry
14 from .downloads import download_entry
15 from .util import (
15 from .util import (
16 extract_tar_to_directory,
16 extract_tar_to_directory,
17 extract_zip_to_directory,
17 extract_zip_to_directory,
18 process_install_rules,
18 process_install_rules,
19 python_exe_info,
19 python_exe_info,
20 )
20 )
21
21
22
22
23 STAGING_RULES = [
23 STAGING_RULES = [
24 ('contrib/bash_completion', 'Contrib/'),
24 ('contrib/bash_completion', 'contrib/'),
25 ('contrib/hgk', 'Contrib/hgk.tcl'),
25 ('contrib/hgk', 'contrib/hgk.tcl'),
26 ('contrib/hgweb.fcgi', 'Contrib/'),
26 ('contrib/hgweb.fcgi', 'contrib/'),
27 ('contrib/hgweb.wsgi', 'Contrib/'),
27 ('contrib/hgweb.wsgi', 'contrib/'),
28 ('contrib/logo-droplets.svg', 'Contrib/'),
28 ('contrib/logo-droplets.svg', 'contrib/'),
29 ('contrib/mercurial.el', 'Contrib/'),
29 ('contrib/mercurial.el', 'contrib/'),
30 ('contrib/mq.el', 'Contrib/'),
30 ('contrib/mq.el', 'contrib/'),
31 ('contrib/tcsh_completion', 'Contrib/'),
31 ('contrib/tcsh_completion', 'contrib/'),
32 ('contrib/tcsh_completion_build.sh', 'Contrib/'),
32 ('contrib/tcsh_completion_build.sh', 'contrib/'),
33 ('contrib/vim/*', 'Contrib/Vim/'),
33 ('contrib/vim/*', 'contrib/vim/'),
34 ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
34 ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
35 ('contrib/win32/ReadMe.html', 'ReadMe.html'),
35 ('contrib/win32/ReadMe.html', 'ReadMe.html'),
36 ('contrib/xml.rnc', 'Contrib/'),
36 ('contrib/xml.rnc', 'contrib/'),
37 ('contrib/zsh_completion', 'Contrib/'),
37 ('contrib/zsh_completion', 'contrib/'),
38 ('dist/hg.exe', './'),
38 ('dist/hg.exe', './'),
39 ('dist/lib/*.dll', 'lib/'),
39 ('dist/lib/*.dll', 'lib/'),
40 ('dist/lib/*.pyd', 'lib/'),
40 ('dist/lib/*.pyd', 'lib/'),
41 ('dist/lib/library.zip', 'lib/'),
41 ('dist/lib/library.zip', 'lib/'),
42 ('dist/Microsoft.VC*.CRT.manifest', './'),
42 ('dist/Microsoft.VC*.CRT.manifest', './'),
43 ('dist/msvc*.dll', './'),
43 ('dist/msvc*.dll', './'),
44 ('dist/python*.dll', './'),
44 ('dist/python*.dll', './'),
45 ('doc/*.html', 'doc/'),
45 ('doc/*.html', 'doc/'),
46 ('doc/style.css', 'doc/'),
46 ('doc/style.css', 'doc/'),
47 ('mercurial/helptext/**/*.txt', 'helptext/'),
47 ('mercurial/helptext/**/*.txt', 'helptext/'),
48 ('mercurial/defaultrc/*.rc', 'defaultrc/'),
48 ('mercurial/defaultrc/*.rc', 'defaultrc/'),
49 ('mercurial/locale/**/*', 'locale/'),
49 ('mercurial/locale/**/*', 'locale/'),
50 ('mercurial/templates/**/*', 'Templates/'),
50 ('mercurial/templates/**/*', 'templates/'),
51 ('COPYING', 'Copying.txt'),
51 ('COPYING', 'Copying.txt'),
52 ]
52 ]
53
53
54 # List of paths to exclude from the staging area.
54 # List of paths to exclude from the staging area.
55 STAGING_EXCLUDES = [
55 STAGING_EXCLUDES = [
56 'doc/hg-ssh.8.html',
56 'doc/hg-ssh.8.html',
57 ]
57 ]
58
58
59
59
60 def build_py2exe(
60 def build_py2exe(
61 source_dir: pathlib.Path,
61 source_dir: pathlib.Path,
62 build_dir: pathlib.Path,
62 build_dir: pathlib.Path,
63 python_exe: pathlib.Path,
63 python_exe: pathlib.Path,
64 build_name: str,
64 build_name: str,
65 venv_requirements_txt: pathlib.Path,
65 venv_requirements_txt: pathlib.Path,
66 extra_packages=None,
66 extra_packages=None,
67 extra_excludes=None,
67 extra_excludes=None,
68 extra_dll_excludes=None,
68 extra_dll_excludes=None,
69 extra_packages_script=None,
69 extra_packages_script=None,
70 ):
70 ):
71 """Build Mercurial with py2exe.
71 """Build Mercurial with py2exe.
72
72
73 Build files will be placed in ``build_dir``.
73 Build files will be placed in ``build_dir``.
74
74
75 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
75 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
76 for finding the Python 2.7 toolchain. So, we require the environment
76 for finding the Python 2.7 toolchain. So, we require the environment
77 to already be configured with an active toolchain.
77 to already be configured with an active toolchain.
78 """
78 """
79 if 'VCINSTALLDIR' not in os.environ:
79 if 'VCINSTALLDIR' not in os.environ:
80 raise Exception(
80 raise Exception(
81 'not running from a Visual C++ build environment; '
81 'not running from a Visual C++ build environment; '
82 'execute the "Visual C++ <version> Command Prompt" '
82 'execute the "Visual C++ <version> Command Prompt" '
83 'application shortcut or a vcsvarsall.bat file'
83 'application shortcut or a vcsvarsall.bat file'
84 )
84 )
85
85
86 # Identity x86/x64 and validate the environment matches the Python
86 # Identity x86/x64 and validate the environment matches the Python
87 # architecture.
87 # architecture.
88 vc_x64 = r'\x64' in os.environ['LIB']
88 vc_x64 = r'\x64' in os.environ['LIB']
89
89
90 py_info = python_exe_info(python_exe)
90 py_info = python_exe_info(python_exe)
91
91
92 if vc_x64:
92 if vc_x64:
93 if py_info['arch'] != '64bit':
93 if py_info['arch'] != '64bit':
94 raise Exception(
94 raise Exception(
95 'architecture mismatch: Visual C++ environment '
95 'architecture mismatch: Visual C++ environment '
96 'is configured for 64-bit but Python is 32-bit'
96 'is configured for 64-bit but Python is 32-bit'
97 )
97 )
98 else:
98 else:
99 if py_info['arch'] != '32bit':
99 if py_info['arch'] != '32bit':
100 raise Exception(
100 raise Exception(
101 'architecture mismatch: Visual C++ environment '
101 'architecture mismatch: Visual C++ environment '
102 'is configured for 32-bit but Python is 64-bit'
102 'is configured for 32-bit but Python is 64-bit'
103 )
103 )
104
104
105 if py_info['py3']:
105 if py_info['py3']:
106 raise Exception('Only Python 2 is currently supported')
106 raise Exception('Only Python 2 is currently supported')
107
107
108 build_dir.mkdir(exist_ok=True)
108 build_dir.mkdir(exist_ok=True)
109
109
110 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
110 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
111 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
111 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
112 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
112 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
113 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
113 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
114
114
115 venv_path = build_dir / (
115 venv_path = build_dir / (
116 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86')
116 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86')
117 )
117 )
118
118
119 gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
119 gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
120
120
121 if not gettext_root.exists():
121 if not gettext_root.exists():
122 extract_zip_to_directory(gettext_pkg, gettext_root)
122 extract_zip_to_directory(gettext_pkg, gettext_root)
123 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
123 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
124
124
125 # This assumes Python 2. We don't need virtualenv on Python 3.
125 # This assumes Python 2. We don't need virtualenv on Python 3.
126 virtualenv_src_path = build_dir / (
126 virtualenv_src_path = build_dir / (
127 'virtualenv-%s' % virtualenv_entry['version']
127 'virtualenv-%s' % virtualenv_entry['version']
128 )
128 )
129 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
129 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
130
130
131 if not virtualenv_src_path.exists():
131 if not virtualenv_src_path.exists():
132 extract_tar_to_directory(virtualenv_pkg, build_dir)
132 extract_tar_to_directory(virtualenv_pkg, build_dir)
133
133
134 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
134 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
135
135
136 if not py2exe_source_path.exists():
136 if not py2exe_source_path.exists():
137 extract_zip_to_directory(py2exe_pkg, build_dir)
137 extract_zip_to_directory(py2exe_pkg, build_dir)
138
138
139 if not venv_path.exists():
139 if not venv_path.exists():
140 print('creating virtualenv with dependencies')
140 print('creating virtualenv with dependencies')
141 subprocess.run(
141 subprocess.run(
142 [str(python_exe), str(virtualenv_py), str(venv_path)], check=True
142 [str(python_exe), str(virtualenv_py), str(venv_path)], check=True
143 )
143 )
144
144
145 venv_python = venv_path / 'Scripts' / 'python.exe'
145 venv_python = venv_path / 'Scripts' / 'python.exe'
146 venv_pip = venv_path / 'Scripts' / 'pip.exe'
146 venv_pip = venv_path / 'Scripts' / 'pip.exe'
147
147
148 subprocess.run(
148 subprocess.run(
149 [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True
149 [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True
150 )
150 )
151
151
152 # Force distutils to use VC++ settings from environment, which was
152 # Force distutils to use VC++ settings from environment, which was
153 # validated above.
153 # validated above.
154 env = dict(os.environ)
154 env = dict(os.environ)
155 env['DISTUTILS_USE_SDK'] = '1'
155 env['DISTUTILS_USE_SDK'] = '1'
156 env['MSSdk'] = '1'
156 env['MSSdk'] = '1'
157
157
158 if extra_packages_script:
158 if extra_packages_script:
159 more_packages = set(
159 more_packages = set(
160 subprocess.check_output(extra_packages_script, cwd=build_dir)
160 subprocess.check_output(extra_packages_script, cwd=build_dir)
161 .split(b'\0')[-1]
161 .split(b'\0')[-1]
162 .strip()
162 .strip()
163 .decode('utf-8')
163 .decode('utf-8')
164 .splitlines()
164 .splitlines()
165 )
165 )
166 if more_packages:
166 if more_packages:
167 if not extra_packages:
167 if not extra_packages:
168 extra_packages = more_packages
168 extra_packages = more_packages
169 else:
169 else:
170 extra_packages |= more_packages
170 extra_packages |= more_packages
171
171
172 if extra_packages:
172 if extra_packages:
173 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
173 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
174 hgext3rd_extras = sorted(
174 hgext3rd_extras = sorted(
175 e for e in extra_packages if e.startswith('hgext3rd.')
175 e for e in extra_packages if e.startswith('hgext3rd.')
176 )
176 )
177 if hgext3rd_extras:
177 if hgext3rd_extras:
178 env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
178 env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
179 if extra_excludes:
179 if extra_excludes:
180 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
180 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
181 if extra_dll_excludes:
181 if extra_dll_excludes:
182 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
182 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
183 sorted(extra_dll_excludes)
183 sorted(extra_dll_excludes)
184 )
184 )
185
185
186 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
186 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
187 if not py2exe_py_path.exists():
187 if not py2exe_py_path.exists():
188 print('building py2exe')
188 print('building py2exe')
189 subprocess.run(
189 subprocess.run(
190 [str(venv_python), 'setup.py', 'install'],
190 [str(venv_python), 'setup.py', 'install'],
191 cwd=py2exe_source_path,
191 cwd=py2exe_source_path,
192 env=env,
192 env=env,
193 check=True,
193 check=True,
194 )
194 )
195
195
196 # Register location of msgfmt and other binaries.
196 # Register location of msgfmt and other binaries.
197 env['PATH'] = '%s%s%s' % (
197 env['PATH'] = '%s%s%s' % (
198 env['PATH'],
198 env['PATH'],
199 os.pathsep,
199 os.pathsep,
200 str(gettext_root / 'bin'),
200 str(gettext_root / 'bin'),
201 )
201 )
202
202
203 print('building Mercurial')
203 print('building Mercurial')
204 subprocess.run(
204 subprocess.run(
205 [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'],
205 [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'],
206 cwd=str(source_dir),
206 cwd=str(source_dir),
207 env=env,
207 env=env,
208 check=True,
208 check=True,
209 )
209 )
210
210
211
211
212 def stage_install(
212 def stage_install(
213 source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
213 source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
214 ):
214 ):
215 """Copy all files to be installed to a directory.
215 """Copy all files to be installed to a directory.
216
216
217 This allows packaging to simply walk a directory tree to find source
217 This allows packaging to simply walk a directory tree to find source
218 files.
218 files.
219 """
219 """
220 if lower_case:
220 if lower_case:
221 rules = []
221 rules = []
222 for source, dest in STAGING_RULES:
222 for source, dest in STAGING_RULES:
223 # Only lower directory names.
223 # Only lower directory names.
224 if '/' in dest:
224 if '/' in dest:
225 parent, leaf = dest.rsplit('/', 1)
225 parent, leaf = dest.rsplit('/', 1)
226 dest = '%s/%s' % (parent.lower(), leaf)
226 dest = '%s/%s' % (parent.lower(), leaf)
227 rules.append((source, dest))
227 rules.append((source, dest))
228 else:
228 else:
229 rules = STAGING_RULES
229 rules = STAGING_RULES
230
230
231 process_install_rules(rules, source_dir, staging_dir)
231 process_install_rules(rules, source_dir, staging_dir)
232
232
233 # Write out a default editor.rc file to configure notepad as the
233 # Write out a default editor.rc file to configure notepad as the
234 # default editor.
234 # default editor.
235 with (staging_dir / 'defaultrc' / 'editor.rc').open(
235 with (staging_dir / 'defaultrc' / 'editor.rc').open(
236 'w', encoding='utf-8'
236 'w', encoding='utf-8'
237 ) as fh:
237 ) as fh:
238 fh.write('[ui]\neditor = notepad\n')
238 fh.write('[ui]\neditor = notepad\n')
239
239
240 # Purge any files we don't want to be there.
240 # Purge any files we don't want to be there.
241 for f in STAGING_EXCLUDES:
241 for f in STAGING_EXCLUDES:
242 p = staging_dir / f
242 p = staging_dir / f
243 if p.exists():
243 if p.exists():
244 print('removing %s' % p)
244 print('removing %s' % p)
245 p.unlink()
245 p.unlink()
General Comments 0
You need to be logged in to leave comments. Login now