Show More
@@ -0,0 +1,153 b'' | |||||
|
1 | # cli.py - Command line interface for automation | |||
|
2 | # | |||
|
3 | # Copyright 2019 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 argparse | |||
|
11 | import os | |||
|
12 | import pathlib | |||
|
13 | ||||
|
14 | from . import ( | |||
|
15 | inno, | |||
|
16 | wix, | |||
|
17 | ) | |||
|
18 | ||||
|
19 | HERE = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) | |||
|
20 | SOURCE_DIR = HERE.parent.parent.parent | |||
|
21 | ||||
|
22 | ||||
|
23 | def build_inno(python=None, iscc=None, version=None): | |||
|
24 | if not os.path.isabs(python): | |||
|
25 | raise Exception("--python arg must be an absolute path") | |||
|
26 | ||||
|
27 | if iscc: | |||
|
28 | iscc = pathlib.Path(iscc) | |||
|
29 | else: | |||
|
30 | iscc = ( | |||
|
31 | pathlib.Path(os.environ["ProgramFiles(x86)"]) | |||
|
32 | / "Inno Setup 5" | |||
|
33 | / "ISCC.exe" | |||
|
34 | ) | |||
|
35 | ||||
|
36 | build_dir = SOURCE_DIR / "build" | |||
|
37 | ||||
|
38 | inno.build( | |||
|
39 | SOURCE_DIR, build_dir, pathlib.Path(python), iscc, version=version, | |||
|
40 | ) | |||
|
41 | ||||
|
42 | ||||
|
43 | def build_wix( | |||
|
44 | name=None, | |||
|
45 | python=None, | |||
|
46 | version=None, | |||
|
47 | sign_sn=None, | |||
|
48 | sign_cert=None, | |||
|
49 | sign_password=None, | |||
|
50 | sign_timestamp_url=None, | |||
|
51 | extra_packages_script=None, | |||
|
52 | extra_wxs=None, | |||
|
53 | extra_features=None, | |||
|
54 | ): | |||
|
55 | fn = wix.build_installer | |||
|
56 | kwargs = { | |||
|
57 | "source_dir": SOURCE_DIR, | |||
|
58 | "python_exe": pathlib.Path(python), | |||
|
59 | "version": version, | |||
|
60 | } | |||
|
61 | ||||
|
62 | if not os.path.isabs(python): | |||
|
63 | raise Exception("--python arg must be an absolute path") | |||
|
64 | ||||
|
65 | if extra_packages_script: | |||
|
66 | kwargs["extra_packages_script"] = extra_packages_script | |||
|
67 | if extra_wxs: | |||
|
68 | kwargs["extra_wxs"] = dict( | |||
|
69 | thing.split("=") for thing in extra_wxs.split(",") | |||
|
70 | ) | |||
|
71 | if extra_features: | |||
|
72 | kwargs["extra_features"] = extra_features.split(",") | |||
|
73 | ||||
|
74 | if sign_sn or sign_cert: | |||
|
75 | fn = wix.build_signed_installer | |||
|
76 | kwargs["name"] = name | |||
|
77 | kwargs["subject_name"] = sign_sn | |||
|
78 | kwargs["cert_path"] = sign_cert | |||
|
79 | kwargs["cert_password"] = sign_password | |||
|
80 | kwargs["timestamp_url"] = sign_timestamp_url | |||
|
81 | ||||
|
82 | fn(**kwargs) | |||
|
83 | ||||
|
84 | ||||
|
85 | def get_parser(): | |||
|
86 | parser = argparse.ArgumentParser() | |||
|
87 | ||||
|
88 | subparsers = parser.add_subparsers() | |||
|
89 | ||||
|
90 | sp = subparsers.add_parser("inno", help="Build Inno Setup installer") | |||
|
91 | sp.add_argument("--python", required=True, help="path to python.exe to use") | |||
|
92 | sp.add_argument("--iscc", help="path to iscc.exe to use") | |||
|
93 | sp.add_argument( | |||
|
94 | "--version", | |||
|
95 | help="Mercurial version string to use " | |||
|
96 | "(detected from __version__.py if not defined", | |||
|
97 | ) | |||
|
98 | sp.set_defaults(func=build_inno) | |||
|
99 | ||||
|
100 | sp = subparsers.add_parser( | |||
|
101 | "wix", help="Build Windows installer with WiX Toolset" | |||
|
102 | ) | |||
|
103 | sp.add_argument("--name", help="Application name", default="Mercurial") | |||
|
104 | sp.add_argument( | |||
|
105 | "--python", help="Path to Python executable to use", required=True | |||
|
106 | ) | |||
|
107 | sp.add_argument( | |||
|
108 | "--sign-sn", | |||
|
109 | help="Subject name (or fragment thereof) of certificate " | |||
|
110 | "to use for signing", | |||
|
111 | ) | |||
|
112 | sp.add_argument( | |||
|
113 | "--sign-cert", help="Path to certificate to use for signing" | |||
|
114 | ) | |||
|
115 | sp.add_argument("--sign-password", help="Password for signing certificate") | |||
|
116 | sp.add_argument( | |||
|
117 | "--sign-timestamp-url", | |||
|
118 | help="URL of timestamp server to use for signing", | |||
|
119 | ) | |||
|
120 | sp.add_argument("--version", help="Version string to use") | |||
|
121 | sp.add_argument( | |||
|
122 | "--extra-packages-script", | |||
|
123 | help=( | |||
|
124 | "Script to execute to include extra packages in " "py2exe binary." | |||
|
125 | ), | |||
|
126 | ) | |||
|
127 | sp.add_argument( | |||
|
128 | "--extra-wxs", help="CSV of path_to_wxs_file=working_dir_for_wxs_file" | |||
|
129 | ) | |||
|
130 | sp.add_argument( | |||
|
131 | "--extra-features", | |||
|
132 | help=( | |||
|
133 | "CSV of extra feature names to include " | |||
|
134 | "in the installer from the extra wxs files" | |||
|
135 | ), | |||
|
136 | ) | |||
|
137 | sp.set_defaults(func=build_wix) | |||
|
138 | ||||
|
139 | return parser | |||
|
140 | ||||
|
141 | ||||
|
142 | def main(): | |||
|
143 | parser = get_parser() | |||
|
144 | args = parser.parse_args() | |||
|
145 | ||||
|
146 | if not hasattr(args, "func"): | |||
|
147 | parser.print_help() | |||
|
148 | return | |||
|
149 | ||||
|
150 | kwargs = dict(vars(args)) | |||
|
151 | del kwargs["func"] | |||
|
152 | ||||
|
153 | args.func(**kwargs) |
@@ -0,0 +1,74 b'' | |||||
|
1 | #!/usr/bin/env python3 | |||
|
2 | # | |||
|
3 | # packaging.py - Mercurial packaging functionality | |||
|
4 | # | |||
|
5 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
6 | # | |||
|
7 | # This software may be used and distributed according to the terms of the | |||
|
8 | # GNU General Public License version 2 or any later version. | |||
|
9 | ||||
|
10 | import os | |||
|
11 | import pathlib | |||
|
12 | import subprocess | |||
|
13 | import sys | |||
|
14 | import venv | |||
|
15 | ||||
|
16 | ||||
|
17 | HERE = pathlib.Path(os.path.abspath(__file__)).parent | |||
|
18 | REQUIREMENTS_TXT = HERE / "requirements.txt" | |||
|
19 | SOURCE_DIR = HERE.parent.parent | |||
|
20 | VENV = SOURCE_DIR / "build" / "venv-packaging" | |||
|
21 | ||||
|
22 | ||||
|
23 | def bootstrap(): | |||
|
24 | venv_created = not VENV.exists() | |||
|
25 | ||||
|
26 | VENV.parent.mkdir(exist_ok=True) | |||
|
27 | ||||
|
28 | venv.create(VENV, with_pip=True) | |||
|
29 | ||||
|
30 | if os.name == "nt": | |||
|
31 | venv_bin = VENV / "Scripts" | |||
|
32 | pip = venv_bin / "pip.exe" | |||
|
33 | python = venv_bin / "python.exe" | |||
|
34 | else: | |||
|
35 | venv_bin = VENV / "bin" | |||
|
36 | pip = venv_bin / "pip" | |||
|
37 | python = venv_bin / "python" | |||
|
38 | ||||
|
39 | args = [ | |||
|
40 | str(pip), | |||
|
41 | "install", | |||
|
42 | "-r", | |||
|
43 | str(REQUIREMENTS_TXT), | |||
|
44 | "--disable-pip-version-check", | |||
|
45 | ] | |||
|
46 | ||||
|
47 | if not venv_created: | |||
|
48 | args.append("-q") | |||
|
49 | ||||
|
50 | subprocess.run(args, check=True) | |||
|
51 | ||||
|
52 | os.environ["HGPACKAGING_BOOTSTRAPPED"] = "1" | |||
|
53 | os.environ["PATH"] = "%s%s%s" % (venv_bin, os.pathsep, os.environ["PATH"]) | |||
|
54 | ||||
|
55 | subprocess.run([str(python), __file__] + sys.argv[1:], check=True) | |||
|
56 | ||||
|
57 | ||||
|
58 | def run(): | |||
|
59 | import hgpackaging.cli as cli | |||
|
60 | ||||
|
61 | # Need to strip off main Python executable. | |||
|
62 | cli.main() | |||
|
63 | ||||
|
64 | ||||
|
65 | if __name__ == "__main__": | |||
|
66 | try: | |||
|
67 | if "HGPACKAGING_BOOTSTRAPPED" not in os.environ: | |||
|
68 | bootstrap() | |||
|
69 | else: | |||
|
70 | run() | |||
|
71 | except subprocess.CalledProcessError as e: | |||
|
72 | sys.exit(e.returncode) | |||
|
73 | except KeyboardInterrupt: | |||
|
74 | sys.exit(1) |
@@ -0,0 +1,39 b'' | |||||
|
1 | # | |||
|
2 | # This file is autogenerated by pip-compile | |||
|
3 | # To update, run: | |||
|
4 | # | |||
|
5 | # pip-compile --generate-hashes --output-file=contrib/packaging/requirements.txt contrib/packaging/requirements.txt.in | |||
|
6 | # | |||
|
7 | jinja2==2.10.3 \ | |||
|
8 | --hash=sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f \ | |||
|
9 | --hash=sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de | |||
|
10 | markupsafe==1.1.1 \ | |||
|
11 | --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \ | |||
|
12 | --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \ | |||
|
13 | --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \ | |||
|
14 | --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \ | |||
|
15 | --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \ | |||
|
16 | --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \ | |||
|
17 | --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \ | |||
|
18 | --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \ | |||
|
19 | --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \ | |||
|
20 | --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \ | |||
|
21 | --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \ | |||
|
22 | --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \ | |||
|
23 | --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \ | |||
|
24 | --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \ | |||
|
25 | --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \ | |||
|
26 | --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \ | |||
|
27 | --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \ | |||
|
28 | --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \ | |||
|
29 | --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \ | |||
|
30 | --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \ | |||
|
31 | --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \ | |||
|
32 | --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \ | |||
|
33 | --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \ | |||
|
34 | --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \ | |||
|
35 | --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \ | |||
|
36 | --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \ | |||
|
37 | --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \ | |||
|
38 | --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \ | |||
|
39 | # via jinja2 |
@@ -0,0 +1,1 b'' | |||||
|
1 | jinja2 |
@@ -1,510 +1,510 b'' | |||||
1 | # windows.py - Automation specific to Windows |
|
1 | # windows.py - Automation specific to Windows | |
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 datetime |
|
10 | import datetime | |
11 | import os |
|
11 | import os | |
12 | import paramiko |
|
12 | import paramiko | |
13 | import pathlib |
|
13 | import pathlib | |
14 | import re |
|
14 | import re | |
15 | import subprocess |
|
15 | import subprocess | |
16 | import tempfile |
|
16 | import tempfile | |
17 |
|
17 | |||
18 | from .pypi import upload as pypi_upload |
|
18 | from .pypi import upload as pypi_upload | |
19 | from .winrm import run_powershell |
|
19 | from .winrm import run_powershell | |
20 |
|
20 | |||
21 |
|
21 | |||
22 | # PowerShell commands to activate a Visual Studio 2008 environment. |
|
22 | # PowerShell commands to activate a Visual Studio 2008 environment. | |
23 | # This is essentially a port of vcvarsall.bat to PowerShell. |
|
23 | # This is essentially a port of vcvarsall.bat to PowerShell. | |
24 | ACTIVATE_VC9_AMD64 = r''' |
|
24 | ACTIVATE_VC9_AMD64 = r''' | |
25 | Write-Output "activating Visual Studio 2008 environment for AMD64" |
|
25 | Write-Output "activating Visual Studio 2008 environment for AMD64" | |
26 | $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0" |
|
26 | $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0" | |
27 | $Env:VCINSTALLDIR = "${root}\VC\" |
|
27 | $Env:VCINSTALLDIR = "${root}\VC\" | |
28 | $Env:WindowsSdkDir = "${root}\WinSDK\" |
|
28 | $Env:WindowsSdkDir = "${root}\WinSDK\" | |
29 | $Env:PATH = "${root}\VC\Bin\amd64;${root}\WinSDK\Bin\x64;${root}\WinSDK\Bin;$Env:PATH" |
|
29 | $Env:PATH = "${root}\VC\Bin\amd64;${root}\WinSDK\Bin\x64;${root}\WinSDK\Bin;$Env:PATH" | |
30 | $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:PATH" |
|
30 | $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:PATH" | |
31 | $Env:LIB = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIB" |
|
31 | $Env:LIB = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIB" | |
32 | $Env:LIBPATH = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIBPATH" |
|
32 | $Env:LIBPATH = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIBPATH" | |
33 | '''.lstrip() |
|
33 | '''.lstrip() | |
34 |
|
34 | |||
35 | ACTIVATE_VC9_X86 = r''' |
|
35 | ACTIVATE_VC9_X86 = r''' | |
36 | Write-Output "activating Visual Studio 2008 environment for x86" |
|
36 | Write-Output "activating Visual Studio 2008 environment for x86" | |
37 | $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0" |
|
37 | $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0" | |
38 | $Env:VCINSTALLDIR = "${root}\VC\" |
|
38 | $Env:VCINSTALLDIR = "${root}\VC\" | |
39 | $Env:WindowsSdkDir = "${root}\WinSDK\" |
|
39 | $Env:WindowsSdkDir = "${root}\WinSDK\" | |
40 | $Env:PATH = "${root}\VC\Bin;${root}\WinSDK\Bin;$Env:PATH" |
|
40 | $Env:PATH = "${root}\VC\Bin;${root}\WinSDK\Bin;$Env:PATH" | |
41 | $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:INCLUDE" |
|
41 | $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:INCLUDE" | |
42 | $Env:LIB = "${root}\VC\Lib;${root}\WinSDK\Lib;$Env:LIB" |
|
42 | $Env:LIB = "${root}\VC\Lib;${root}\WinSDK\Lib;$Env:LIB" | |
43 | $Env:LIBPATH = "${root}\VC\lib;${root}\WinSDK\Lib;$Env:LIBPATH" |
|
43 | $Env:LIBPATH = "${root}\VC\lib;${root}\WinSDK\Lib;$Env:LIBPATH" | |
44 | '''.lstrip() |
|
44 | '''.lstrip() | |
45 |
|
45 | |||
46 | HG_PURGE = r''' |
|
46 | HG_PURGE = r''' | |
47 | $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH" |
|
47 | $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH" | |
48 | Set-Location C:\hgdev\src |
|
48 | Set-Location C:\hgdev\src | |
49 | hg.exe --config extensions.purge= purge --all |
|
49 | hg.exe --config extensions.purge= purge --all | |
50 | if ($LASTEXITCODE -ne 0) { |
|
50 | if ($LASTEXITCODE -ne 0) { | |
51 | throw "process exited non-0: $LASTEXITCODE" |
|
51 | throw "process exited non-0: $LASTEXITCODE" | |
52 | } |
|
52 | } | |
53 | Write-Output "purged Mercurial repo" |
|
53 | Write-Output "purged Mercurial repo" | |
54 | ''' |
|
54 | ''' | |
55 |
|
55 | |||
56 | HG_UPDATE_CLEAN = r''' |
|
56 | HG_UPDATE_CLEAN = r''' | |
57 | $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH" |
|
57 | $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH" | |
58 | Set-Location C:\hgdev\src |
|
58 | Set-Location C:\hgdev\src | |
59 | hg.exe --config extensions.purge= purge --all |
|
59 | hg.exe --config extensions.purge= purge --all | |
60 | if ($LASTEXITCODE -ne 0) {{ |
|
60 | if ($LASTEXITCODE -ne 0) {{ | |
61 | throw "process exited non-0: $LASTEXITCODE" |
|
61 | throw "process exited non-0: $LASTEXITCODE" | |
62 | }} |
|
62 | }} | |
63 | hg.exe update -C {revision} |
|
63 | hg.exe update -C {revision} | |
64 | if ($LASTEXITCODE -ne 0) {{ |
|
64 | if ($LASTEXITCODE -ne 0) {{ | |
65 | throw "process exited non-0: $LASTEXITCODE" |
|
65 | throw "process exited non-0: $LASTEXITCODE" | |
66 | }} |
|
66 | }} | |
67 | hg.exe log -r . |
|
67 | hg.exe log -r . | |
68 | Write-Output "updated Mercurial working directory to {revision}" |
|
68 | Write-Output "updated Mercurial working directory to {revision}" | |
69 | '''.lstrip() |
|
69 | '''.lstrip() | |
70 |
|
70 | |||
71 | BUILD_INNO = r''' |
|
71 | BUILD_INNO = r''' | |
72 | Set-Location C:\hgdev\src |
|
72 | Set-Location C:\hgdev\src | |
73 | $python = "C:\hgdev\python27-{arch}\python.exe" |
|
73 | $python = "C:\hgdev\python27-{arch}\python.exe" | |
74 |
C:\hgdev\python37-x64\python.exe contrib\packaging\ |
|
74 | C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python | |
75 | if ($LASTEXITCODE -ne 0) {{ |
|
75 | if ($LASTEXITCODE -ne 0) {{ | |
76 | throw "process exited non-0: $LASTEXITCODE" |
|
76 | throw "process exited non-0: $LASTEXITCODE" | |
77 | }} |
|
77 | }} | |
78 | '''.lstrip() |
|
78 | '''.lstrip() | |
79 |
|
79 | |||
80 | BUILD_WHEEL = r''' |
|
80 | BUILD_WHEEL = r''' | |
81 | Set-Location C:\hgdev\src |
|
81 | Set-Location C:\hgdev\src | |
82 | C:\hgdev\python27-{arch}\Scripts\pip.exe wheel --wheel-dir dist . |
|
82 | C:\hgdev\python27-{arch}\Scripts\pip.exe wheel --wheel-dir dist . | |
83 | if ($LASTEXITCODE -ne 0) {{ |
|
83 | if ($LASTEXITCODE -ne 0) {{ | |
84 | throw "process exited non-0: $LASTEXITCODE" |
|
84 | throw "process exited non-0: $LASTEXITCODE" | |
85 | }} |
|
85 | }} | |
86 | ''' |
|
86 | ''' | |
87 |
|
87 | |||
88 | BUILD_WIX = r''' |
|
88 | BUILD_WIX = r''' | |
89 | Set-Location C:\hgdev\src |
|
89 | Set-Location C:\hgdev\src | |
90 | $python = "C:\hgdev\python27-{arch}\python.exe" |
|
90 | $python = "C:\hgdev\python27-{arch}\python.exe" | |
91 |
C:\hgdev\python37-x64\python.exe contrib\packaging\ |
|
91 | C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --python $python {extra_args} | |
92 | if ($LASTEXITCODE -ne 0) {{ |
|
92 | if ($LASTEXITCODE -ne 0) {{ | |
93 | throw "process exited non-0: $LASTEXITCODE" |
|
93 | throw "process exited non-0: $LASTEXITCODE" | |
94 | }} |
|
94 | }} | |
95 | ''' |
|
95 | ''' | |
96 |
|
96 | |||
97 | RUN_TESTS = r''' |
|
97 | RUN_TESTS = r''' | |
98 | C:\hgdev\MinGW\msys\1.0\bin\sh.exe --login -c "cd /c/hgdev/src/tests && /c/hgdev/{python_path}/python.exe run-tests.py {test_flags}" |
|
98 | C:\hgdev\MinGW\msys\1.0\bin\sh.exe --login -c "cd /c/hgdev/src/tests && /c/hgdev/{python_path}/python.exe run-tests.py {test_flags}" | |
99 | if ($LASTEXITCODE -ne 0) {{ |
|
99 | if ($LASTEXITCODE -ne 0) {{ | |
100 | throw "process exited non-0: $LASTEXITCODE" |
|
100 | throw "process exited non-0: $LASTEXITCODE" | |
101 | }} |
|
101 | }} | |
102 | ''' |
|
102 | ''' | |
103 |
|
103 | |||
104 | X86_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win32.whl' |
|
104 | X86_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win32.whl' | |
105 | X64_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win_amd64.whl' |
|
105 | X64_WHEEL_FILENAME = 'mercurial-{version}-cp27-cp27m-win_amd64.whl' | |
106 | X86_EXE_FILENAME = 'Mercurial-{version}.exe' |
|
106 | X86_EXE_FILENAME = 'Mercurial-{version}.exe' | |
107 | X64_EXE_FILENAME = 'Mercurial-{version}-x64.exe' |
|
107 | X64_EXE_FILENAME = 'Mercurial-{version}-x64.exe' | |
108 | X86_MSI_FILENAME = 'mercurial-{version}-x86.msi' |
|
108 | X86_MSI_FILENAME = 'mercurial-{version}-x86.msi' | |
109 | X64_MSI_FILENAME = 'mercurial-{version}-x64.msi' |
|
109 | X64_MSI_FILENAME = 'mercurial-{version}-x64.msi' | |
110 |
|
110 | |||
111 | MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows' |
|
111 | MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows' | |
112 |
|
112 | |||
113 | X86_USER_AGENT_PATTERN = '.*Windows.*' |
|
113 | X86_USER_AGENT_PATTERN = '.*Windows.*' | |
114 | X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' |
|
114 | X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*' | |
115 |
|
115 | |||
116 | X86_EXE_DESCRIPTION = ( |
|
116 | X86_EXE_DESCRIPTION = ( | |
117 | 'Mercurial {version} Inno Setup installer - x86 Windows ' |
|
117 | 'Mercurial {version} Inno Setup installer - x86 Windows ' | |
118 | '- does not require admin rights' |
|
118 | '- does not require admin rights' | |
119 | ) |
|
119 | ) | |
120 | X64_EXE_DESCRIPTION = ( |
|
120 | X64_EXE_DESCRIPTION = ( | |
121 | 'Mercurial {version} Inno Setup installer - x64 Windows ' |
|
121 | 'Mercurial {version} Inno Setup installer - x64 Windows ' | |
122 | '- does not require admin rights' |
|
122 | '- does not require admin rights' | |
123 | ) |
|
123 | ) | |
124 | X86_MSI_DESCRIPTION = ( |
|
124 | X86_MSI_DESCRIPTION = ( | |
125 | 'Mercurial {version} MSI installer - x86 Windows ' '- requires admin rights' |
|
125 | 'Mercurial {version} MSI installer - x86 Windows ' '- requires admin rights' | |
126 | ) |
|
126 | ) | |
127 | X64_MSI_DESCRIPTION = ( |
|
127 | X64_MSI_DESCRIPTION = ( | |
128 | 'Mercurial {version} MSI installer - x64 Windows ' '- requires admin rights' |
|
128 | 'Mercurial {version} MSI installer - x64 Windows ' '- requires admin rights' | |
129 | ) |
|
129 | ) | |
130 |
|
130 | |||
131 |
|
131 | |||
132 | def get_vc_prefix(arch): |
|
132 | def get_vc_prefix(arch): | |
133 | if arch == 'x86': |
|
133 | if arch == 'x86': | |
134 | return ACTIVATE_VC9_X86 |
|
134 | return ACTIVATE_VC9_X86 | |
135 | elif arch == 'x64': |
|
135 | elif arch == 'x64': | |
136 | return ACTIVATE_VC9_AMD64 |
|
136 | return ACTIVATE_VC9_AMD64 | |
137 | else: |
|
137 | else: | |
138 | raise ValueError('illegal arch: %s; must be x86 or x64' % arch) |
|
138 | raise ValueError('illegal arch: %s; must be x86 or x64' % arch) | |
139 |
|
139 | |||
140 |
|
140 | |||
141 | def fix_authorized_keys_permissions(winrm_client, path): |
|
141 | def fix_authorized_keys_permissions(winrm_client, path): | |
142 | commands = [ |
|
142 | commands = [ | |
143 | '$ErrorActionPreference = "Stop"', |
|
143 | '$ErrorActionPreference = "Stop"', | |
144 | 'Repair-AuthorizedKeyPermission -FilePath %s -Confirm:$false' % path, |
|
144 | 'Repair-AuthorizedKeyPermission -FilePath %s -Confirm:$false' % path, | |
145 | r'icacls %s /remove:g "NT Service\sshd"' % path, |
|
145 | r'icacls %s /remove:g "NT Service\sshd"' % path, | |
146 | ] |
|
146 | ] | |
147 |
|
147 | |||
148 | run_powershell(winrm_client, '\n'.join(commands)) |
|
148 | run_powershell(winrm_client, '\n'.join(commands)) | |
149 |
|
149 | |||
150 |
|
150 | |||
151 | def synchronize_hg(hg_repo: pathlib.Path, revision: str, ec2_instance): |
|
151 | def synchronize_hg(hg_repo: pathlib.Path, revision: str, ec2_instance): | |
152 | """Synchronize local Mercurial repo to remote EC2 instance.""" |
|
152 | """Synchronize local Mercurial repo to remote EC2 instance.""" | |
153 |
|
153 | |||
154 | winrm_client = ec2_instance.winrm_client |
|
154 | winrm_client = ec2_instance.winrm_client | |
155 |
|
155 | |||
156 | with tempfile.TemporaryDirectory() as temp_dir: |
|
156 | with tempfile.TemporaryDirectory() as temp_dir: | |
157 | temp_dir = pathlib.Path(temp_dir) |
|
157 | temp_dir = pathlib.Path(temp_dir) | |
158 |
|
158 | |||
159 | ssh_dir = temp_dir / '.ssh' |
|
159 | ssh_dir = temp_dir / '.ssh' | |
160 | ssh_dir.mkdir() |
|
160 | ssh_dir.mkdir() | |
161 | ssh_dir.chmod(0o0700) |
|
161 | ssh_dir.chmod(0o0700) | |
162 |
|
162 | |||
163 | # Generate SSH key to use for communication. |
|
163 | # Generate SSH key to use for communication. | |
164 | subprocess.run( |
|
164 | subprocess.run( | |
165 | [ |
|
165 | [ | |
166 | 'ssh-keygen', |
|
166 | 'ssh-keygen', | |
167 | '-t', |
|
167 | '-t', | |
168 | 'rsa', |
|
168 | 'rsa', | |
169 | '-b', |
|
169 | '-b', | |
170 | '4096', |
|
170 | '4096', | |
171 | '-N', |
|
171 | '-N', | |
172 | '', |
|
172 | '', | |
173 | '-f', |
|
173 | '-f', | |
174 | str(ssh_dir / 'id_rsa'), |
|
174 | str(ssh_dir / 'id_rsa'), | |
175 | ], |
|
175 | ], | |
176 | check=True, |
|
176 | check=True, | |
177 | capture_output=True, |
|
177 | capture_output=True, | |
178 | ) |
|
178 | ) | |
179 |
|
179 | |||
180 | # Add it to ~/.ssh/authorized_keys on remote. |
|
180 | # Add it to ~/.ssh/authorized_keys on remote. | |
181 | # This assumes the file doesn't already exist. |
|
181 | # This assumes the file doesn't already exist. | |
182 | authorized_keys = r'c:\Users\Administrator\.ssh\authorized_keys' |
|
182 | authorized_keys = r'c:\Users\Administrator\.ssh\authorized_keys' | |
183 | winrm_client.execute_cmd(r'mkdir c:\Users\Administrator\.ssh') |
|
183 | winrm_client.execute_cmd(r'mkdir c:\Users\Administrator\.ssh') | |
184 | winrm_client.copy(str(ssh_dir / 'id_rsa.pub'), authorized_keys) |
|
184 | winrm_client.copy(str(ssh_dir / 'id_rsa.pub'), authorized_keys) | |
185 | fix_authorized_keys_permissions(winrm_client, authorized_keys) |
|
185 | fix_authorized_keys_permissions(winrm_client, authorized_keys) | |
186 |
|
186 | |||
187 | public_ip = ec2_instance.public_ip_address |
|
187 | public_ip = ec2_instance.public_ip_address | |
188 |
|
188 | |||
189 | ssh_config = temp_dir / '.ssh' / 'config' |
|
189 | ssh_config = temp_dir / '.ssh' / 'config' | |
190 |
|
190 | |||
191 | with open(ssh_config, 'w', encoding='utf-8') as fh: |
|
191 | with open(ssh_config, 'w', encoding='utf-8') as fh: | |
192 | fh.write('Host %s\n' % public_ip) |
|
192 | fh.write('Host %s\n' % public_ip) | |
193 | fh.write(' User Administrator\n') |
|
193 | fh.write(' User Administrator\n') | |
194 | fh.write(' StrictHostKeyChecking no\n') |
|
194 | fh.write(' StrictHostKeyChecking no\n') | |
195 | fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts')) |
|
195 | fh.write(' UserKnownHostsFile %s\n' % (ssh_dir / 'known_hosts')) | |
196 | fh.write(' IdentityFile %s\n' % (ssh_dir / 'id_rsa')) |
|
196 | fh.write(' IdentityFile %s\n' % (ssh_dir / 'id_rsa')) | |
197 |
|
197 | |||
198 | if not (hg_repo / '.hg').is_dir(): |
|
198 | if not (hg_repo / '.hg').is_dir(): | |
199 | raise Exception( |
|
199 | raise Exception( | |
200 | '%s is not a Mercurial repository; ' |
|
200 | '%s is not a Mercurial repository; ' | |
201 | 'synchronization not yet supported' % hg_repo |
|
201 | 'synchronization not yet supported' % hg_repo | |
202 | ) |
|
202 | ) | |
203 |
|
203 | |||
204 | env = dict(os.environ) |
|
204 | env = dict(os.environ) | |
205 | env['HGPLAIN'] = '1' |
|
205 | env['HGPLAIN'] = '1' | |
206 | env['HGENCODING'] = 'utf-8' |
|
206 | env['HGENCODING'] = 'utf-8' | |
207 |
|
207 | |||
208 | hg_bin = hg_repo / 'hg' |
|
208 | hg_bin = hg_repo / 'hg' | |
209 |
|
209 | |||
210 | res = subprocess.run( |
|
210 | res = subprocess.run( | |
211 | ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], |
|
211 | ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'], | |
212 | cwd=str(hg_repo), |
|
212 | cwd=str(hg_repo), | |
213 | env=env, |
|
213 | env=env, | |
214 | check=True, |
|
214 | check=True, | |
215 | capture_output=True, |
|
215 | capture_output=True, | |
216 | ) |
|
216 | ) | |
217 |
|
217 | |||
218 | full_revision = res.stdout.decode('ascii') |
|
218 | full_revision = res.stdout.decode('ascii') | |
219 |
|
219 | |||
220 | args = [ |
|
220 | args = [ | |
221 | 'python2.7', |
|
221 | 'python2.7', | |
222 | hg_bin, |
|
222 | hg_bin, | |
223 | '--config', |
|
223 | '--config', | |
224 | 'ui.ssh=ssh -F %s' % ssh_config, |
|
224 | 'ui.ssh=ssh -F %s' % ssh_config, | |
225 | '--config', |
|
225 | '--config', | |
226 | 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', |
|
226 | 'ui.remotecmd=c:/hgdev/venv-bootstrap/Scripts/hg.exe', | |
227 | # Also ensure .hgtags changes are present so auto version |
|
227 | # Also ensure .hgtags changes are present so auto version | |
228 | # calculation works. |
|
228 | # calculation works. | |
229 | 'push', |
|
229 | 'push', | |
230 | '-f', |
|
230 | '-f', | |
231 | '-r', |
|
231 | '-r', | |
232 | full_revision, |
|
232 | full_revision, | |
233 | '-r', |
|
233 | '-r', | |
234 | 'file(.hgtags)', |
|
234 | 'file(.hgtags)', | |
235 | 'ssh://%s/c:/hgdev/src' % public_ip, |
|
235 | 'ssh://%s/c:/hgdev/src' % public_ip, | |
236 | ] |
|
236 | ] | |
237 |
|
237 | |||
238 | res = subprocess.run(args, cwd=str(hg_repo), env=env) |
|
238 | res = subprocess.run(args, cwd=str(hg_repo), env=env) | |
239 |
|
239 | |||
240 | # Allow 1 (no-op) to not trigger error. |
|
240 | # Allow 1 (no-op) to not trigger error. | |
241 | if res.returncode not in (0, 1): |
|
241 | if res.returncode not in (0, 1): | |
242 | res.check_returncode() |
|
242 | res.check_returncode() | |
243 |
|
243 | |||
244 | run_powershell( |
|
244 | run_powershell( | |
245 | winrm_client, HG_UPDATE_CLEAN.format(revision=full_revision) |
|
245 | winrm_client, HG_UPDATE_CLEAN.format(revision=full_revision) | |
246 | ) |
|
246 | ) | |
247 |
|
247 | |||
248 | # TODO detect dirty local working directory and synchronize accordingly. |
|
248 | # TODO detect dirty local working directory and synchronize accordingly. | |
249 |
|
249 | |||
250 |
|
250 | |||
251 | def purge_hg(winrm_client): |
|
251 | def purge_hg(winrm_client): | |
252 | """Purge the Mercurial source repository on an EC2 instance.""" |
|
252 | """Purge the Mercurial source repository on an EC2 instance.""" | |
253 | run_powershell(winrm_client, HG_PURGE) |
|
253 | run_powershell(winrm_client, HG_PURGE) | |
254 |
|
254 | |||
255 |
|
255 | |||
256 | def find_latest_dist(winrm_client, pattern): |
|
256 | def find_latest_dist(winrm_client, pattern): | |
257 | """Find path to newest file in dist/ directory matching a pattern.""" |
|
257 | """Find path to newest file in dist/ directory matching a pattern.""" | |
258 |
|
258 | |||
259 | res = winrm_client.execute_ps( |
|
259 | res = winrm_client.execute_ps( | |
260 | r'$v = Get-ChildItem -Path C:\hgdev\src\dist -Filter "%s" ' |
|
260 | r'$v = Get-ChildItem -Path C:\hgdev\src\dist -Filter "%s" ' | |
261 | '| Sort-Object LastWriteTime -Descending ' |
|
261 | '| Sort-Object LastWriteTime -Descending ' | |
262 | '| Select-Object -First 1\n' |
|
262 | '| Select-Object -First 1\n' | |
263 | '$v.name' % pattern |
|
263 | '$v.name' % pattern | |
264 | ) |
|
264 | ) | |
265 | return res[0] |
|
265 | return res[0] | |
266 |
|
266 | |||
267 |
|
267 | |||
268 | def copy_latest_dist(winrm_client, pattern, dest_path): |
|
268 | def copy_latest_dist(winrm_client, pattern, dest_path): | |
269 | """Copy latest file matching pattern in dist/ directory. |
|
269 | """Copy latest file matching pattern in dist/ directory. | |
270 |
|
270 | |||
271 | Given a WinRM client and a file pattern, find the latest file on the remote |
|
271 | Given a WinRM client and a file pattern, find the latest file on the remote | |
272 | matching that pattern and copy it to the ``dest_path`` directory on the |
|
272 | matching that pattern and copy it to the ``dest_path`` directory on the | |
273 | local machine. |
|
273 | local machine. | |
274 | """ |
|
274 | """ | |
275 | latest = find_latest_dist(winrm_client, pattern) |
|
275 | latest = find_latest_dist(winrm_client, pattern) | |
276 | source = r'C:\hgdev\src\dist\%s' % latest |
|
276 | source = r'C:\hgdev\src\dist\%s' % latest | |
277 | dest = dest_path / latest |
|
277 | dest = dest_path / latest | |
278 | print('copying %s to %s' % (source, dest)) |
|
278 | print('copying %s to %s' % (source, dest)) | |
279 | winrm_client.fetch(source, str(dest)) |
|
279 | winrm_client.fetch(source, str(dest)) | |
280 |
|
280 | |||
281 |
|
281 | |||
282 | def build_inno_installer( |
|
282 | def build_inno_installer( | |
283 | winrm_client, arch: str, dest_path: pathlib.Path, version=None |
|
283 | winrm_client, arch: str, dest_path: pathlib.Path, version=None | |
284 | ): |
|
284 | ): | |
285 | """Build the Inno Setup installer on a remote machine. |
|
285 | """Build the Inno Setup installer on a remote machine. | |
286 |
|
286 | |||
287 | Using a WinRM client, remote commands are executed to build |
|
287 | Using a WinRM client, remote commands are executed to build | |
288 | a Mercurial Inno Setup installer. |
|
288 | a Mercurial Inno Setup installer. | |
289 | """ |
|
289 | """ | |
290 | print('building Inno Setup installer for %s' % arch) |
|
290 | print('building Inno Setup installer for %s' % arch) | |
291 |
|
291 | |||
292 | extra_args = [] |
|
292 | extra_args = [] | |
293 | if version: |
|
293 | if version: | |
294 | extra_args.extend(['--version', version]) |
|
294 | extra_args.extend(['--version', version]) | |
295 |
|
295 | |||
296 | ps = get_vc_prefix(arch) + BUILD_INNO.format( |
|
296 | ps = get_vc_prefix(arch) + BUILD_INNO.format( | |
297 | arch=arch, extra_args=' '.join(extra_args) |
|
297 | arch=arch, extra_args=' '.join(extra_args) | |
298 | ) |
|
298 | ) | |
299 | run_powershell(winrm_client, ps) |
|
299 | run_powershell(winrm_client, ps) | |
300 | copy_latest_dist(winrm_client, '*.exe', dest_path) |
|
300 | copy_latest_dist(winrm_client, '*.exe', dest_path) | |
301 |
|
301 | |||
302 |
|
302 | |||
303 | def build_wheel(winrm_client, arch: str, dest_path: pathlib.Path): |
|
303 | def build_wheel(winrm_client, arch: str, dest_path: pathlib.Path): | |
304 | """Build Python wheels on a remote machine. |
|
304 | """Build Python wheels on a remote machine. | |
305 |
|
305 | |||
306 | Using a WinRM client, remote commands are executed to build a Python wheel |
|
306 | Using a WinRM client, remote commands are executed to build a Python wheel | |
307 | for Mercurial. |
|
307 | for Mercurial. | |
308 | """ |
|
308 | """ | |
309 | print('Building Windows wheel for %s' % arch) |
|
309 | print('Building Windows wheel for %s' % arch) | |
310 | ps = get_vc_prefix(arch) + BUILD_WHEEL.format(arch=arch) |
|
310 | ps = get_vc_prefix(arch) + BUILD_WHEEL.format(arch=arch) | |
311 | run_powershell(winrm_client, ps) |
|
311 | run_powershell(winrm_client, ps) | |
312 | copy_latest_dist(winrm_client, '*.whl', dest_path) |
|
312 | copy_latest_dist(winrm_client, '*.whl', dest_path) | |
313 |
|
313 | |||
314 |
|
314 | |||
315 | def build_wix_installer( |
|
315 | def build_wix_installer( | |
316 | winrm_client, arch: str, dest_path: pathlib.Path, version=None |
|
316 | winrm_client, arch: str, dest_path: pathlib.Path, version=None | |
317 | ): |
|
317 | ): | |
318 | """Build the WiX installer on a remote machine. |
|
318 | """Build the WiX installer on a remote machine. | |
319 |
|
319 | |||
320 | Using a WinRM client, remote commands are executed to build a WiX installer. |
|
320 | Using a WinRM client, remote commands are executed to build a WiX installer. | |
321 | """ |
|
321 | """ | |
322 | print('Building WiX installer for %s' % arch) |
|
322 | print('Building WiX installer for %s' % arch) | |
323 | extra_args = [] |
|
323 | extra_args = [] | |
324 | if version: |
|
324 | if version: | |
325 | extra_args.extend(['--version', version]) |
|
325 | extra_args.extend(['--version', version]) | |
326 |
|
326 | |||
327 | ps = get_vc_prefix(arch) + BUILD_WIX.format( |
|
327 | ps = get_vc_prefix(arch) + BUILD_WIX.format( | |
328 | arch=arch, extra_args=' '.join(extra_args) |
|
328 | arch=arch, extra_args=' '.join(extra_args) | |
329 | ) |
|
329 | ) | |
330 | run_powershell(winrm_client, ps) |
|
330 | run_powershell(winrm_client, ps) | |
331 | copy_latest_dist(winrm_client, '*.msi', dest_path) |
|
331 | copy_latest_dist(winrm_client, '*.msi', dest_path) | |
332 |
|
332 | |||
333 |
|
333 | |||
334 | def run_tests(winrm_client, python_version, arch, test_flags=''): |
|
334 | def run_tests(winrm_client, python_version, arch, test_flags=''): | |
335 | """Run tests on a remote Windows machine. |
|
335 | """Run tests on a remote Windows machine. | |
336 |
|
336 | |||
337 | ``python_version`` is a ``X.Y`` string like ``2.7`` or ``3.7``. |
|
337 | ``python_version`` is a ``X.Y`` string like ``2.7`` or ``3.7``. | |
338 | ``arch`` is ``x86`` or ``x64``. |
|
338 | ``arch`` is ``x86`` or ``x64``. | |
339 | ``test_flags`` is a str representing extra arguments to pass to |
|
339 | ``test_flags`` is a str representing extra arguments to pass to | |
340 | ``run-tests.py``. |
|
340 | ``run-tests.py``. | |
341 | """ |
|
341 | """ | |
342 | if not re.match(r'\d\.\d', python_version): |
|
342 | if not re.match(r'\d\.\d', python_version): | |
343 | raise ValueError( |
|
343 | raise ValueError( | |
344 | r'python_version must be \d.\d; got %s' % python_version |
|
344 | r'python_version must be \d.\d; got %s' % python_version | |
345 | ) |
|
345 | ) | |
346 |
|
346 | |||
347 | if arch not in ('x86', 'x64'): |
|
347 | if arch not in ('x86', 'x64'): | |
348 | raise ValueError('arch must be x86 or x64; got %s' % arch) |
|
348 | raise ValueError('arch must be x86 or x64; got %s' % arch) | |
349 |
|
349 | |||
350 | python_path = 'python%s-%s' % (python_version.replace('.', ''), arch) |
|
350 | python_path = 'python%s-%s' % (python_version.replace('.', ''), arch) | |
351 |
|
351 | |||
352 | ps = RUN_TESTS.format(python_path=python_path, test_flags=test_flags or '',) |
|
352 | ps = RUN_TESTS.format(python_path=python_path, test_flags=test_flags or '',) | |
353 |
|
353 | |||
354 | run_powershell(winrm_client, ps) |
|
354 | run_powershell(winrm_client, ps) | |
355 |
|
355 | |||
356 |
|
356 | |||
357 | def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str): |
|
357 | def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str): | |
358 | return ( |
|
358 | return ( | |
359 | dist_path / X86_WHEEL_FILENAME.format(version=version), |
|
359 | dist_path / X86_WHEEL_FILENAME.format(version=version), | |
360 | dist_path / X64_WHEEL_FILENAME.format(version=version), |
|
360 | dist_path / X64_WHEEL_FILENAME.format(version=version), | |
361 | ) |
|
361 | ) | |
362 |
|
362 | |||
363 |
|
363 | |||
364 | def resolve_all_artifacts(dist_path: pathlib.Path, version: str): |
|
364 | def resolve_all_artifacts(dist_path: pathlib.Path, version: str): | |
365 | return ( |
|
365 | return ( | |
366 | dist_path / X86_WHEEL_FILENAME.format(version=version), |
|
366 | dist_path / X86_WHEEL_FILENAME.format(version=version), | |
367 | dist_path / X64_WHEEL_FILENAME.format(version=version), |
|
367 | dist_path / X64_WHEEL_FILENAME.format(version=version), | |
368 | dist_path / X86_EXE_FILENAME.format(version=version), |
|
368 | dist_path / X86_EXE_FILENAME.format(version=version), | |
369 | dist_path / X64_EXE_FILENAME.format(version=version), |
|
369 | dist_path / X64_EXE_FILENAME.format(version=version), | |
370 | dist_path / X86_MSI_FILENAME.format(version=version), |
|
370 | dist_path / X86_MSI_FILENAME.format(version=version), | |
371 | dist_path / X64_MSI_FILENAME.format(version=version), |
|
371 | dist_path / X64_MSI_FILENAME.format(version=version), | |
372 | ) |
|
372 | ) | |
373 |
|
373 | |||
374 |
|
374 | |||
375 | def generate_latest_dat(version: str): |
|
375 | def generate_latest_dat(version: str): | |
376 | x86_exe_filename = X86_EXE_FILENAME.format(version=version) |
|
376 | x86_exe_filename = X86_EXE_FILENAME.format(version=version) | |
377 | x64_exe_filename = X64_EXE_FILENAME.format(version=version) |
|
377 | x64_exe_filename = X64_EXE_FILENAME.format(version=version) | |
378 | x86_msi_filename = X86_MSI_FILENAME.format(version=version) |
|
378 | x86_msi_filename = X86_MSI_FILENAME.format(version=version) | |
379 | x64_msi_filename = X64_MSI_FILENAME.format(version=version) |
|
379 | x64_msi_filename = X64_MSI_FILENAME.format(version=version) | |
380 |
|
380 | |||
381 | entries = ( |
|
381 | entries = ( | |
382 | ( |
|
382 | ( | |
383 | '10', |
|
383 | '10', | |
384 | version, |
|
384 | version, | |
385 | X86_USER_AGENT_PATTERN, |
|
385 | X86_USER_AGENT_PATTERN, | |
386 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_exe_filename), |
|
386 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_exe_filename), | |
387 | X86_EXE_DESCRIPTION.format(version=version), |
|
387 | X86_EXE_DESCRIPTION.format(version=version), | |
388 | ), |
|
388 | ), | |
389 | ( |
|
389 | ( | |
390 | '10', |
|
390 | '10', | |
391 | version, |
|
391 | version, | |
392 | X64_USER_AGENT_PATTERN, |
|
392 | X64_USER_AGENT_PATTERN, | |
393 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_exe_filename), |
|
393 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_exe_filename), | |
394 | X64_EXE_DESCRIPTION.format(version=version), |
|
394 | X64_EXE_DESCRIPTION.format(version=version), | |
395 | ), |
|
395 | ), | |
396 | ( |
|
396 | ( | |
397 | '10', |
|
397 | '10', | |
398 | version, |
|
398 | version, | |
399 | X86_USER_AGENT_PATTERN, |
|
399 | X86_USER_AGENT_PATTERN, | |
400 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_msi_filename), |
|
400 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x86_msi_filename), | |
401 | X86_MSI_DESCRIPTION.format(version=version), |
|
401 | X86_MSI_DESCRIPTION.format(version=version), | |
402 | ), |
|
402 | ), | |
403 | ( |
|
403 | ( | |
404 | '10', |
|
404 | '10', | |
405 | version, |
|
405 | version, | |
406 | X64_USER_AGENT_PATTERN, |
|
406 | X64_USER_AGENT_PATTERN, | |
407 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename), |
|
407 | '%s/%s' % (MERCURIAL_SCM_BASE_URL, x64_msi_filename), | |
408 | X64_MSI_DESCRIPTION.format(version=version), |
|
408 | X64_MSI_DESCRIPTION.format(version=version), | |
409 | ), |
|
409 | ), | |
410 | ) |
|
410 | ) | |
411 |
|
411 | |||
412 | lines = ['\t'.join(e) for e in entries] |
|
412 | lines = ['\t'.join(e) for e in entries] | |
413 |
|
413 | |||
414 | return '\n'.join(lines) + '\n' |
|
414 | return '\n'.join(lines) + '\n' | |
415 |
|
415 | |||
416 |
|
416 | |||
417 | def publish_artifacts_pypi(dist_path: pathlib.Path, version: str): |
|
417 | def publish_artifacts_pypi(dist_path: pathlib.Path, version: str): | |
418 | """Publish Windows release artifacts to PyPI.""" |
|
418 | """Publish Windows release artifacts to PyPI.""" | |
419 |
|
419 | |||
420 | wheel_paths = resolve_wheel_artifacts(dist_path, version) |
|
420 | wheel_paths = resolve_wheel_artifacts(dist_path, version) | |
421 |
|
421 | |||
422 | for p in wheel_paths: |
|
422 | for p in wheel_paths: | |
423 | if not p.exists(): |
|
423 | if not p.exists(): | |
424 | raise Exception('%s not found' % p) |
|
424 | raise Exception('%s not found' % p) | |
425 |
|
425 | |||
426 | print('uploading wheels to PyPI (you may be prompted for credentials)') |
|
426 | print('uploading wheels to PyPI (you may be prompted for credentials)') | |
427 | pypi_upload(wheel_paths) |
|
427 | pypi_upload(wheel_paths) | |
428 |
|
428 | |||
429 |
|
429 | |||
430 | def publish_artifacts_mercurial_scm_org( |
|
430 | def publish_artifacts_mercurial_scm_org( | |
431 | dist_path: pathlib.Path, version: str, ssh_username=None |
|
431 | dist_path: pathlib.Path, version: str, ssh_username=None | |
432 | ): |
|
432 | ): | |
433 | """Publish Windows release artifacts to mercurial-scm.org.""" |
|
433 | """Publish Windows release artifacts to mercurial-scm.org.""" | |
434 | all_paths = resolve_all_artifacts(dist_path, version) |
|
434 | all_paths = resolve_all_artifacts(dist_path, version) | |
435 |
|
435 | |||
436 | for p in all_paths: |
|
436 | for p in all_paths: | |
437 | if not p.exists(): |
|
437 | if not p.exists(): | |
438 | raise Exception('%s not found' % p) |
|
438 | raise Exception('%s not found' % p) | |
439 |
|
439 | |||
440 | client = paramiko.SSHClient() |
|
440 | client = paramiko.SSHClient() | |
441 | client.load_system_host_keys() |
|
441 | client.load_system_host_keys() | |
442 | # We assume the system SSH configuration knows how to connect. |
|
442 | # We assume the system SSH configuration knows how to connect. | |
443 | print('connecting to mercurial-scm.org via ssh...') |
|
443 | print('connecting to mercurial-scm.org via ssh...') | |
444 | try: |
|
444 | try: | |
445 | client.connect('mercurial-scm.org', username=ssh_username) |
|
445 | client.connect('mercurial-scm.org', username=ssh_username) | |
446 | except paramiko.AuthenticationException: |
|
446 | except paramiko.AuthenticationException: | |
447 | print('error authenticating; is an SSH key available in an SSH agent?') |
|
447 | print('error authenticating; is an SSH key available in an SSH agent?') | |
448 | raise |
|
448 | raise | |
449 |
|
449 | |||
450 | print('SSH connection established') |
|
450 | print('SSH connection established') | |
451 |
|
451 | |||
452 | print('opening SFTP client...') |
|
452 | print('opening SFTP client...') | |
453 | sftp = client.open_sftp() |
|
453 | sftp = client.open_sftp() | |
454 | print('SFTP client obtained') |
|
454 | print('SFTP client obtained') | |
455 |
|
455 | |||
456 | for p in all_paths: |
|
456 | for p in all_paths: | |
457 | dest_path = '/var/www/release/windows/%s' % p.name |
|
457 | dest_path = '/var/www/release/windows/%s' % p.name | |
458 | print('uploading %s to %s' % (p, dest_path)) |
|
458 | print('uploading %s to %s' % (p, dest_path)) | |
459 |
|
459 | |||
460 | with p.open('rb') as fh: |
|
460 | with p.open('rb') as fh: | |
461 | data = fh.read() |
|
461 | data = fh.read() | |
462 |
|
462 | |||
463 | with sftp.open(dest_path, 'wb') as fh: |
|
463 | with sftp.open(dest_path, 'wb') as fh: | |
464 | fh.write(data) |
|
464 | fh.write(data) | |
465 | fh.chmod(0o0664) |
|
465 | fh.chmod(0o0664) | |
466 |
|
466 | |||
467 | latest_dat_path = '/var/www/release/windows/latest.dat' |
|
467 | latest_dat_path = '/var/www/release/windows/latest.dat' | |
468 |
|
468 | |||
469 | now = datetime.datetime.utcnow() |
|
469 | now = datetime.datetime.utcnow() | |
470 | backup_path = dist_path / ( |
|
470 | backup_path = dist_path / ( | |
471 | 'latest-windows-%s.dat' % now.strftime('%Y%m%dT%H%M%S') |
|
471 | 'latest-windows-%s.dat' % now.strftime('%Y%m%dT%H%M%S') | |
472 | ) |
|
472 | ) | |
473 | print('backing up %s to %s' % (latest_dat_path, backup_path)) |
|
473 | print('backing up %s to %s' % (latest_dat_path, backup_path)) | |
474 |
|
474 | |||
475 | with sftp.open(latest_dat_path, 'rb') as fh: |
|
475 | with sftp.open(latest_dat_path, 'rb') as fh: | |
476 | latest_dat_old = fh.read() |
|
476 | latest_dat_old = fh.read() | |
477 |
|
477 | |||
478 | with backup_path.open('wb') as fh: |
|
478 | with backup_path.open('wb') as fh: | |
479 | fh.write(latest_dat_old) |
|
479 | fh.write(latest_dat_old) | |
480 |
|
480 | |||
481 | print('writing %s with content:' % latest_dat_path) |
|
481 | print('writing %s with content:' % latest_dat_path) | |
482 | latest_dat_content = generate_latest_dat(version) |
|
482 | latest_dat_content = generate_latest_dat(version) | |
483 | print(latest_dat_content) |
|
483 | print(latest_dat_content) | |
484 |
|
484 | |||
485 | with sftp.open(latest_dat_path, 'wb') as fh: |
|
485 | with sftp.open(latest_dat_path, 'wb') as fh: | |
486 | fh.write(latest_dat_content.encode('ascii')) |
|
486 | fh.write(latest_dat_content.encode('ascii')) | |
487 |
|
487 | |||
488 |
|
488 | |||
489 | def publish_artifacts( |
|
489 | def publish_artifacts( | |
490 | dist_path: pathlib.Path, |
|
490 | dist_path: pathlib.Path, | |
491 | version: str, |
|
491 | version: str, | |
492 | pypi=True, |
|
492 | pypi=True, | |
493 | mercurial_scm_org=True, |
|
493 | mercurial_scm_org=True, | |
494 | ssh_username=None, |
|
494 | ssh_username=None, | |
495 | ): |
|
495 | ): | |
496 | """Publish Windows release artifacts. |
|
496 | """Publish Windows release artifacts. | |
497 |
|
497 | |||
498 | Files are found in `dist_path`. We will look for files with version string |
|
498 | Files are found in `dist_path`. We will look for files with version string | |
499 | `version`. |
|
499 | `version`. | |
500 |
|
500 | |||
501 | `pypi` controls whether we upload to PyPI. |
|
501 | `pypi` controls whether we upload to PyPI. | |
502 | `mercurial_scm_org` controls whether we upload to mercurial-scm.org. |
|
502 | `mercurial_scm_org` controls whether we upload to mercurial-scm.org. | |
503 | """ |
|
503 | """ | |
504 | if pypi: |
|
504 | if pypi: | |
505 | publish_artifacts_pypi(dist_path, version) |
|
505 | publish_artifacts_pypi(dist_path, version) | |
506 |
|
506 | |||
507 | if mercurial_scm_org: |
|
507 | if mercurial_scm_org: | |
508 | publish_artifacts_mercurial_scm_org( |
|
508 | publish_artifacts_mercurial_scm_org( | |
509 | dist_path, version, ssh_username=ssh_username |
|
509 | dist_path, version, ssh_username=ssh_username | |
510 | ) |
|
510 | ) |
@@ -1,61 +1,61 b'' | |||||
1 | Requirements |
|
1 | Requirements | |
2 | ============ |
|
2 | ============ | |
3 |
|
3 | |||
4 | Building the Inno installer requires a Windows machine. |
|
4 | Building the Inno installer requires a Windows machine. | |
5 |
|
5 | |||
6 | The following system dependencies must be installed: |
|
6 | The following system dependencies must be installed: | |
7 |
|
7 | |||
8 | * Python 2.7 (download from https://www.python.org/downloads/) |
|
8 | * Python 2.7 (download from https://www.python.org/downloads/) | |
9 | * Microsoft Visual C++ Compiler for Python 2.7 |
|
9 | * Microsoft Visual C++ Compiler for Python 2.7 | |
10 | (https://www.microsoft.com/en-us/download/details.aspx?id=44266) |
|
10 | (https://www.microsoft.com/en-us/download/details.aspx?id=44266) | |
11 | * Inno Setup (http://jrsoftware.org/isdl.php) version 5.4 or newer. |
|
11 | * Inno Setup (http://jrsoftware.org/isdl.php) version 5.4 or newer. | |
12 | Be sure to install the optional Inno Setup Preprocessor feature, |
|
12 | Be sure to install the optional Inno Setup Preprocessor feature, | |
13 | which is required. |
|
13 | which is required. | |
14 |
* Python 3.5+ (to run the `` |
|
14 | * Python 3.5+ (to run the ``packaging.py`` script) | |
15 |
|
15 | |||
16 | Building |
|
16 | Building | |
17 | ======== |
|
17 | ======== | |
18 |
|
18 | |||
19 |
The `` |
|
19 | The ``packaging.py`` script automates the process of producing an | |
20 | Inno installer. It manages fetching and configuring the |
|
20 | Inno installer. It manages fetching and configuring the | |
21 | non-system dependencies (such as py2exe, gettext, and various |
|
21 | non-system dependencies (such as py2exe, gettext, and various | |
22 | Python packages). |
|
22 | Python packages). | |
23 |
|
23 | |||
24 | The script requires an activated ``Visual C++ 2008`` command prompt. |
|
24 | The script requires an activated ``Visual C++ 2008`` command prompt. | |
25 | A shortcut to such a prompt was installed with ``Microsoft Visual C++ |
|
25 | A shortcut to such a prompt was installed with ``Microsoft Visual C++ | |
26 | Compiler for Python 2.7``. From your Start Menu, look for |
|
26 | Compiler for Python 2.7``. From your Start Menu, look for | |
27 | ``Microsoft Visual C++ Compiler Package for Python 2.7`` then launch |
|
27 | ``Microsoft Visual C++ Compiler Package for Python 2.7`` then launch | |
28 | either ``Visual C++ 2008 32-bit Command Prompt`` or |
|
28 | either ``Visual C++ 2008 32-bit Command Prompt`` or | |
29 | ``Visual C++ 2008 64-bit Command Prompt``. |
|
29 | ``Visual C++ 2008 64-bit Command Prompt``. | |
30 |
|
30 | |||
31 | From the prompt, change to the Mercurial source directory. e.g. |
|
31 | From the prompt, change to the Mercurial source directory. e.g. | |
32 | ``cd c:\src\hg``. |
|
32 | ``cd c:\src\hg``. | |
33 |
|
33 | |||
34 |
Next, invoke `` |
|
34 | Next, invoke ``packaging.py`` to produce an Inno installer. You will | |
35 | need to supply the path to the Python interpreter to use.:: |
|
35 | need to supply the path to the Python interpreter to use.:: | |
36 |
|
36 | |||
37 |
$ python3.exe contrib\packaging\ |
|
37 | $ python3.exe contrib\packaging\packaging.py \ | |
38 | --python c:\python27\python.exe |
|
38 | inno --python c:\python27\python.exe | |
39 |
|
39 | |||
40 | .. note:: |
|
40 | .. note:: | |
41 |
|
41 | |||
42 | The script validates that the Visual C++ environment is |
|
42 | The script validates that the Visual C++ environment is | |
43 | active and that the architecture of the specified Python |
|
43 | active and that the architecture of the specified Python | |
44 | interpreter matches the Visual C++ environment and errors |
|
44 | interpreter matches the Visual C++ environment and errors | |
45 | if not. |
|
45 | if not. | |
46 |
|
46 | |||
47 | If everything runs as intended, dependencies will be fetched and |
|
47 | If everything runs as intended, dependencies will be fetched and | |
48 | configured into the ``build`` sub-directory, Mercurial will be built, |
|
48 | configured into the ``build`` sub-directory, Mercurial will be built, | |
49 | and an installer placed in the ``dist`` sub-directory. The final |
|
49 | and an installer placed in the ``dist`` sub-directory. The final | |
50 | line of output should print the name of the generated installer. |
|
50 | line of output should print the name of the generated installer. | |
51 |
|
51 | |||
52 |
Additional options may be configured. Run |
|
52 | Additional options may be configured. Run | |
53 | see a list of program flags. |
|
53 | ``packaging.py inno --help`` to see a list of program flags. | |
54 |
|
54 | |||
55 | MinGW |
|
55 | MinGW | |
56 | ===== |
|
56 | ===== | |
57 |
|
57 | |||
58 | It is theoretically possible to generate an installer that uses |
|
58 | It is theoretically possible to generate an installer that uses | |
59 |
MinGW. This isn't well tested and `` |
|
59 | MinGW. This isn't well tested and ``packaging.py`` and may properly | |
60 | support it. See old versions of this file in version control for |
|
60 | support it. See old versions of this file in version control for | |
61 | potentially useful hints as to how to achieve this. |
|
61 | potentially useful hints as to how to achieve this. |
@@ -1,71 +1,71 b'' | |||||
1 | WiX Installer |
|
1 | WiX Installer | |
2 | ============= |
|
2 | ============= | |
3 |
|
3 | |||
4 | The files in this directory are used to produce an MSI installer using |
|
4 | The files in this directory are used to produce an MSI installer using | |
5 | the WiX Toolset (http://wixtoolset.org/). |
|
5 | the WiX Toolset (http://wixtoolset.org/). | |
6 |
|
6 | |||
7 | The MSI installers require elevated (admin) privileges due to the |
|
7 | The MSI installers require elevated (admin) privileges due to the | |
8 | installation of MSVC CRT libraries into the Windows system store. See |
|
8 | installation of MSVC CRT libraries into the Windows system store. See | |
9 | the Inno Setup installers in the ``inno`` sibling directory for installers |
|
9 | the Inno Setup installers in the ``inno`` sibling directory for installers | |
10 | that do not have this requirement. |
|
10 | that do not have this requirement. | |
11 |
|
11 | |||
12 | Requirements |
|
12 | Requirements | |
13 | ============ |
|
13 | ============ | |
14 |
|
14 | |||
15 | Building the WiX installers requires a Windows machine. The following |
|
15 | Building the WiX installers requires a Windows machine. The following | |
16 | dependencies must be installed: |
|
16 | dependencies must be installed: | |
17 |
|
17 | |||
18 | * Python 2.7 (download from https://www.python.org/downloads/) |
|
18 | * Python 2.7 (download from https://www.python.org/downloads/) | |
19 | * Microsoft Visual C++ Compiler for Python 2.7 |
|
19 | * Microsoft Visual C++ Compiler for Python 2.7 | |
20 | (https://www.microsoft.com/en-us/download/details.aspx?id=44266) |
|
20 | (https://www.microsoft.com/en-us/download/details.aspx?id=44266) | |
21 |
* Python 3.5+ (to run the `` |
|
21 | * Python 3.5+ (to run the ``packaging.py`` script) | |
22 |
|
22 | |||
23 | Building |
|
23 | Building | |
24 | ======== |
|
24 | ======== | |
25 |
|
25 | |||
26 |
The `` |
|
26 | The ``packaging.py`` script automates the process of producing an MSI | |
27 | installer. It manages fetching and configuring non-system dependencies |
|
27 | installer. It manages fetching and configuring non-system dependencies | |
28 | (such as py2exe, gettext, and various Python packages). |
|
28 | (such as py2exe, gettext, and various Python packages). | |
29 |
|
29 | |||
30 | The script requires an activated ``Visual C++ 2008`` command prompt. |
|
30 | The script requires an activated ``Visual C++ 2008`` command prompt. | |
31 | A shortcut to such a prompt was installed with ``Microsoft Visual |
|
31 | A shortcut to such a prompt was installed with ``Microsoft Visual | |
32 | C++ Compiler for Python 2.7``. From your Start Menu, look for |
|
32 | C++ Compiler for Python 2.7``. From your Start Menu, look for | |
33 | ``Microsoft Visual C++ Compiler Package for Python 2.7`` then |
|
33 | ``Microsoft Visual C++ Compiler Package for Python 2.7`` then | |
34 | launch either ``Visual C++ 2008 32-bit Command Prompt`` or |
|
34 | launch either ``Visual C++ 2008 32-bit Command Prompt`` or | |
35 | ``Visual C++ 2008 64-bit Command Prompt``. |
|
35 | ``Visual C++ 2008 64-bit Command Prompt``. | |
36 |
|
36 | |||
37 | From the prompt, change to the Mercurial source directory. e.g. |
|
37 | From the prompt, change to the Mercurial source directory. e.g. | |
38 | ``cd c:\src\hg``. |
|
38 | ``cd c:\src\hg``. | |
39 |
|
39 | |||
40 |
Next, invoke `` |
|
40 | Next, invoke ``packaging.py`` to produce an MSI installer. You will need | |
41 | to supply the path to the Python interpreter to use.:: |
|
41 | to supply the path to the Python interpreter to use.:: | |
42 |
|
42 | |||
43 |
$ python3 contrib\packaging\ |
|
43 | $ python3 contrib\packaging\packaging.py \ | |
44 | --python c:\python27\python.exe |
|
44 | wix --python c:\python27\python.exe | |
45 |
|
45 | |||
46 | .. note:: |
|
46 | .. note:: | |
47 |
|
47 | |||
48 | The script validates that the Visual C++ environment is active and |
|
48 | The script validates that the Visual C++ environment is active and | |
49 | that the architecture of the specified Python interpreter matches the |
|
49 | that the architecture of the specified Python interpreter matches the | |
50 | Visual C++ environment. An error is raised otherwise. |
|
50 | Visual C++ environment. An error is raised otherwise. | |
51 |
|
51 | |||
52 | If everything runs as intended, dependencies will be fetched and |
|
52 | If everything runs as intended, dependencies will be fetched and | |
53 | configured into the ``build`` sub-directory, Mercurial will be built, |
|
53 | configured into the ``build`` sub-directory, Mercurial will be built, | |
54 | and an installer placed in the ``dist`` sub-directory. The final line |
|
54 | and an installer placed in the ``dist`` sub-directory. The final line | |
55 | of output should print the name of the generated installer. |
|
55 | of output should print the name of the generated installer. | |
56 |
|
56 | |||
57 |
Additional options may be configured. Run `` |
|
57 | Additional options may be configured. Run ``packaging.py wix --help`` to | |
58 | a list of program flags. |
|
58 | see a list of program flags. | |
59 |
|
59 | |||
60 | Relationship to TortoiseHG |
|
60 | Relationship to TortoiseHG | |
61 | ========================== |
|
61 | ========================== | |
62 |
|
62 | |||
63 | TortoiseHG uses the WiX files in this directory. |
|
63 | TortoiseHG uses the WiX files in this directory. | |
64 |
|
64 | |||
65 | The code for building TortoiseHG installers lives at |
|
65 | The code for building TortoiseHG installers lives at | |
66 | https://bitbucket.org/tortoisehg/thg-winbuild and is maintained by |
|
66 | https://bitbucket.org/tortoisehg/thg-winbuild and is maintained by | |
67 | Steve Borho (steve@borho.org). |
|
67 | Steve Borho (steve@borho.org). | |
68 |
|
68 | |||
69 | When changing behavior of the WiX installer, be sure to notify |
|
69 | When changing behavior of the WiX installer, be sure to notify | |
70 | the TortoiseHG Project of the changes so they have ample time |
|
70 | the TortoiseHG Project of the changes so they have ample time | |
71 | provide feedback and react to those changes. |
|
71 | provide feedback and react to those changes. |
@@ -1,86 +1,85 b'' | |||||
1 | #require test-repo |
|
1 | #require test-repo | |
2 |
|
2 | |||
3 | $ . "$TESTDIR/helpers-testrepo.sh" |
|
3 | $ . "$TESTDIR/helpers-testrepo.sh" | |
4 | $ check_code="$TESTDIR"/../contrib/check-code.py |
|
4 | $ check_code="$TESTDIR"/../contrib/check-code.py | |
5 | $ cd "$TESTDIR"/.. |
|
5 | $ cd "$TESTDIR"/.. | |
6 |
|
6 | |||
7 | New errors are not allowed. Warnings are strongly discouraged. |
|
7 | New errors are not allowed. Warnings are strongly discouraged. | |
8 | (The writing "no-che?k-code" is for not skipping this file when checking.) |
|
8 | (The writing "no-che?k-code" is for not skipping this file when checking.) | |
9 |
|
9 | |||
10 | $ testrepohg locate \ |
|
10 | $ testrepohg locate \ | |
11 | > -X contrib/python-zstandard \ |
|
11 | > -X contrib/python-zstandard \ | |
12 | > -X hgext/fsmonitor/pywatchman \ |
|
12 | > -X hgext/fsmonitor/pywatchman \ | |
13 | > -X mercurial/thirdparty \ |
|
13 | > -X mercurial/thirdparty \ | |
14 | > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false |
|
14 | > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false | |
15 | Skipping contrib/automation/hgautomation/__init__.py it has no-che?k-code (glob) |
|
15 | Skipping contrib/automation/hgautomation/__init__.py it has no-che?k-code (glob) | |
16 | Skipping contrib/automation/hgautomation/aws.py it has no-che?k-code (glob) |
|
16 | Skipping contrib/automation/hgautomation/aws.py it has no-che?k-code (glob) | |
17 | Skipping contrib/automation/hgautomation/cli.py it has no-che?k-code (glob) |
|
17 | Skipping contrib/automation/hgautomation/cli.py it has no-che?k-code (glob) | |
18 | Skipping contrib/automation/hgautomation/linux.py it has no-che?k-code (glob) |
|
18 | Skipping contrib/automation/hgautomation/linux.py it has no-che?k-code (glob) | |
19 | Skipping contrib/automation/hgautomation/pypi.py it has no-che?k-code (glob) |
|
19 | Skipping contrib/automation/hgautomation/pypi.py it has no-che?k-code (glob) | |
20 | Skipping contrib/automation/hgautomation/ssh.py it has no-che?k-code (glob) |
|
20 | Skipping contrib/automation/hgautomation/ssh.py it has no-che?k-code (glob) | |
21 | Skipping contrib/automation/hgautomation/try_server.py it has no-che?k-code (glob) |
|
21 | Skipping contrib/automation/hgautomation/try_server.py it has no-che?k-code (glob) | |
22 | Skipping contrib/automation/hgautomation/windows.py it has no-che?k-code (glob) |
|
22 | Skipping contrib/automation/hgautomation/windows.py it has no-che?k-code (glob) | |
23 | Skipping contrib/automation/hgautomation/winrm.py it has no-che?k-code (glob) |
|
23 | Skipping contrib/automation/hgautomation/winrm.py it has no-che?k-code (glob) | |
|
24 | Skipping contrib/packaging/hgpackaging/cli.py it has no-che?k-code (glob) | |||
24 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) |
|
25 | Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob) | |
25 | Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) |
|
26 | Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob) | |
26 | Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob) |
|
27 | Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob) | |
27 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) |
|
28 | Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob) | |
28 | Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) |
|
29 | Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob) | |
29 | Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob) |
|
|||
30 | Skipping contrib/packaging/wix/build.py it has no-che?k-code (glob) |
|
|||
31 | Skipping i18n/polib.py it has no-che?k-code (glob) |
|
30 | Skipping i18n/polib.py it has no-che?k-code (glob) | |
32 | Skipping mercurial/statprof.py it has no-che?k-code (glob) |
|
31 | Skipping mercurial/statprof.py it has no-che?k-code (glob) | |
33 | Skipping tests/badserverext.py it has no-che?k-code (glob) |
|
32 | Skipping tests/badserverext.py it has no-che?k-code (glob) | |
34 |
|
33 | |||
35 | @commands in debugcommands.py should be in alphabetical order. |
|
34 | @commands in debugcommands.py should be in alphabetical order. | |
36 |
|
35 | |||
37 | >>> import re |
|
36 | >>> import re | |
38 | >>> commands = [] |
|
37 | >>> commands = [] | |
39 | >>> with open('mercurial/debugcommands.py', 'rb') as fh: |
|
38 | >>> with open('mercurial/debugcommands.py', 'rb') as fh: | |
40 | ... for line in fh: |
|
39 | ... for line in fh: | |
41 | ... m = re.match(br"^@command\('([a-z]+)", line) |
|
40 | ... m = re.match(br"^@command\('([a-z]+)", line) | |
42 | ... if m: |
|
41 | ... if m: | |
43 | ... commands.append(m.group(1)) |
|
42 | ... commands.append(m.group(1)) | |
44 | >>> scommands = list(sorted(commands)) |
|
43 | >>> scommands = list(sorted(commands)) | |
45 | >>> for i, command in enumerate(scommands): |
|
44 | >>> for i, command in enumerate(scommands): | |
46 | ... if command != commands[i]: |
|
45 | ... if command != commands[i]: | |
47 | ... print('commands in debugcommands.py not sorted; first differing ' |
|
46 | ... print('commands in debugcommands.py not sorted; first differing ' | |
48 | ... 'command is %s; expected %s' % (commands[i], command)) |
|
47 | ... 'command is %s; expected %s' % (commands[i], command)) | |
49 | ... break |
|
48 | ... break | |
50 |
|
49 | |||
51 | Prevent adding new files in the root directory accidentally. |
|
50 | Prevent adding new files in the root directory accidentally. | |
52 |
|
51 | |||
53 | $ testrepohg files 'glob:*' |
|
52 | $ testrepohg files 'glob:*' | |
54 | .arcconfig |
|
53 | .arcconfig | |
55 | .clang-format |
|
54 | .clang-format | |
56 | .editorconfig |
|
55 | .editorconfig | |
57 | .hgignore |
|
56 | .hgignore | |
58 | .hgsigs |
|
57 | .hgsigs | |
59 | .hgtags |
|
58 | .hgtags | |
60 | .jshintrc |
|
59 | .jshintrc | |
61 | CONTRIBUTING |
|
60 | CONTRIBUTING | |
62 | CONTRIBUTORS |
|
61 | CONTRIBUTORS | |
63 | COPYING |
|
62 | COPYING | |
64 | Makefile |
|
63 | Makefile | |
65 | README.rst |
|
64 | README.rst | |
66 | black.toml |
|
65 | black.toml | |
67 | hg |
|
66 | hg | |
68 | hgeditor |
|
67 | hgeditor | |
69 | hgweb.cgi |
|
68 | hgweb.cgi | |
70 | setup.py |
|
69 | setup.py | |
71 |
|
70 | |||
72 | Prevent adding modules which could be shadowed by ancient .so/.dylib. |
|
71 | Prevent adding modules which could be shadowed by ancient .so/.dylib. | |
73 |
|
72 | |||
74 | $ testrepohg files \ |
|
73 | $ testrepohg files \ | |
75 | > mercurial/base85.py \ |
|
74 | > mercurial/base85.py \ | |
76 | > mercurial/bdiff.py \ |
|
75 | > mercurial/bdiff.py \ | |
77 | > mercurial/diffhelpers.py \ |
|
76 | > mercurial/diffhelpers.py \ | |
78 | > mercurial/mpatch.py \ |
|
77 | > mercurial/mpatch.py \ | |
79 | > mercurial/osutil.py \ |
|
78 | > mercurial/osutil.py \ | |
80 | > mercurial/parsers.py \ |
|
79 | > mercurial/parsers.py \ | |
81 | > mercurial/zstd.py |
|
80 | > mercurial/zstd.py | |
82 | [1] |
|
81 | [1] | |
83 |
|
82 | |||
84 | Keep python3 tests sorted: |
|
83 | Keep python3 tests sorted: | |
85 | $ sort < contrib/python3-whitelist > $TESTTMP/py3sorted |
|
84 | $ sort < contrib/python3-whitelist > $TESTTMP/py3sorted | |
86 | $ cmp contrib/python3-whitelist $TESTTMP/py3sorted || echo 'Please sort passing tests!' |
|
85 | $ cmp contrib/python3-whitelist $TESTTMP/py3sorted || echo 'Please sort passing tests!' |
@@ -1,52 +1,53 b'' | |||||
1 | #require test-repo |
|
1 | #require test-repo | |
2 |
|
2 | |||
3 | $ . "$TESTDIR/helpers-testrepo.sh" |
|
3 | $ . "$TESTDIR/helpers-testrepo.sh" | |
4 | $ cd "$TESTDIR"/.. |
|
4 | $ cd "$TESTDIR"/.. | |
5 |
|
5 | |||
6 | #if no-py3 |
|
6 | #if no-py3 | |
7 | $ testrepohg files 'set:(**.py)' \ |
|
7 | $ testrepohg files 'set:(**.py)' \ | |
8 | > -X contrib/automation/ \ |
|
8 | > -X contrib/automation/ \ | |
9 | > -X contrib/packaging/hgpackaging/ \ |
|
9 | > -X contrib/packaging/hgpackaging/ \ | |
10 | > -X contrib/packaging/inno/ \ |
|
10 | > -X contrib/packaging/inno/ \ | |
|
11 | > -X contrib/packaging/packaging.py \ | |||
11 | > -X contrib/packaging/wix/ \ |
|
12 | > -X contrib/packaging/wix/ \ | |
12 | > -X hgdemandimport/demandimportpy2.py \ |
|
13 | > -X hgdemandimport/demandimportpy2.py \ | |
13 | > -X mercurial/thirdparty/cbor \ |
|
14 | > -X mercurial/thirdparty/cbor \ | |
14 | > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py |
|
15 | > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py | |
15 | contrib/python-zstandard/setup.py not using absolute_import |
|
16 | contrib/python-zstandard/setup.py not using absolute_import | |
16 | contrib/python-zstandard/setup_zstd.py not using absolute_import |
|
17 | contrib/python-zstandard/setup_zstd.py not using absolute_import | |
17 | contrib/python-zstandard/tests/common.py not using absolute_import |
|
18 | contrib/python-zstandard/tests/common.py not using absolute_import | |
18 | contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import |
|
19 | contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import | |
19 | contrib/python-zstandard/tests/test_compressor.py not using absolute_import |
|
20 | contrib/python-zstandard/tests/test_compressor.py not using absolute_import | |
20 | contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import |
|
21 | contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import | |
21 | contrib/python-zstandard/tests/test_data_structures.py not using absolute_import |
|
22 | contrib/python-zstandard/tests/test_data_structures.py not using absolute_import | |
22 | contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import |
|
23 | contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import | |
23 | contrib/python-zstandard/tests/test_decompressor.py not using absolute_import |
|
24 | contrib/python-zstandard/tests/test_decompressor.py not using absolute_import | |
24 | contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import |
|
25 | contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import | |
25 | contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import |
|
26 | contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import | |
26 | contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import |
|
27 | contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import | |
27 | contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import |
|
28 | contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import | |
28 | setup.py not using absolute_import |
|
29 | setup.py not using absolute_import | |
29 | #endif |
|
30 | #endif | |
30 |
|
31 | |||
31 | #if py3 |
|
32 | #if py3 | |
32 | $ testrepohg files 'set:(**.py) - grep(pygments)' \ |
|
33 | $ testrepohg files 'set:(**.py) - grep(pygments)' \ | |
33 | > -X hgdemandimport/demandimportpy2.py \ |
|
34 | > -X hgdemandimport/demandimportpy2.py \ | |
34 | > -X hgext/fsmonitor/pywatchman \ |
|
35 | > -X hgext/fsmonitor/pywatchman \ | |
35 | > -X mercurial/cffi \ |
|
36 | > -X mercurial/cffi \ | |
36 | > -X mercurial/thirdparty \ |
|
37 | > -X mercurial/thirdparty \ | |
37 | > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \ |
|
38 | > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \ | |
38 | > | sed 's/[0-9][0-9]*)$/*)/' |
|
39 | > | sed 's/[0-9][0-9]*)$/*)/' | |
39 | hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?) |
|
40 | hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?) | |
40 | hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?) |
|
41 | hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?) | |
41 | mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !) |
|
42 | mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !) | |
42 | mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !) |
|
43 | mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !) | |
43 | mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !) |
|
44 | mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !) | |
44 | mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !) |
|
45 | mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !) | |
45 | mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !) |
|
46 | mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !) | |
46 | #endif |
|
47 | #endif | |
47 |
|
48 | |||
48 | #if py3 pygments |
|
49 | #if py3 pygments | |
49 | $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \ |
|
50 | $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \ | |
50 | > | xargs "$PYTHON" contrib/check-py3-compat.py \ |
|
51 | > | xargs "$PYTHON" contrib/check-py3-compat.py \ | |
51 | > | sed 's/[0-9][0-9]*)$/*)/' |
|
52 | > | sed 's/[0-9][0-9]*)$/*)/' | |
52 | #endif |
|
53 | #endif |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now