contrib/automation/hgautomation/windows.py
changeset 48846 d7e064d509a0
parent 48843 d953a42b157d
child 48847 4561ec90d3c1
equal deleted inserted replaced
48845:5c8148cd7f13 48846:d7e064d509a0
    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.
       
    23 # This is essentially a port of vcvarsall.bat to PowerShell.
       
    24 ACTIVATE_VC9_AMD64 = r'''
       
    25 Write-Output "activating Visual Studio 2008 environment for AMD64"
       
    26 $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0"
       
    27 $Env:VCINSTALLDIR = "${root}\VC\"
       
    28 $Env:WindowsSdkDir = "${root}\WinSDK\"
       
    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"
       
    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"
       
    33 '''.lstrip()
       
    34 
       
    35 ACTIVATE_VC9_X86 = r'''
       
    36 Write-Output "activating Visual Studio 2008 environment for x86"
       
    37 $root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0"
       
    38 $Env:VCINSTALLDIR = "${root}\VC\"
       
    39 $Env:WindowsSdkDir = "${root}\WinSDK\"
       
    40 $Env:PATH = "${root}\VC\Bin;${root}\WinSDK\Bin;$Env:PATH"
       
    41 $Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:INCLUDE"
       
    42 $Env:LIB = "${root}\VC\Lib;${root}\WinSDK\Lib;$Env:LIB"
       
    43 $Env:LIBPATH = "${root}\VC\lib;${root}\WinSDK\Lib;$Env:LIBPATH"
       
    44 '''.lstrip()
       
    45 
       
    46 HG_PURGE = r'''
    22 HG_PURGE = r'''
    47 $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH"
    23 $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH"
    48 Set-Location C:\hgdev\src
    24 Set-Location C:\hgdev\src
    49 hg.exe --config extensions.purge= purge --all
    25 hg.exe --config extensions.purge= purge --all
    50 if ($LASTEXITCODE -ne 0) {
    26 if ($LASTEXITCODE -ne 0) {
    76 if ($LASTEXITCODE -ne 0) {{
    52 if ($LASTEXITCODE -ne 0) {{
    77     throw "process exited non-0: $LASTEXITCODE"
    53     throw "process exited non-0: $LASTEXITCODE"
    78 }}
    54 }}
    79 '''
    55 '''
    80 
    56 
    81 BUILD_INNO_PYTHON2 = r'''
       
    82 Set-Location C:\hgdev\src
       
    83 $python = "C:\hgdev\python27-{arch}\python.exe"
       
    84 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python {extra_args}
       
    85 if ($LASTEXITCODE -ne 0) {{
       
    86     throw "process exited non-0: $LASTEXITCODE"
       
    87 }}
       
    88 '''.lstrip()
       
    89 
    57 
    90 BUILD_WHEEL = r'''
    58 BUILD_WHEEL = r'''
    91 Set-Location C:\hgdev\src
    59 Set-Location C:\hgdev\src
    92 C:\hgdev\python{python_version}-{arch}\python.exe -m pip wheel --wheel-dir dist .
    60 C:\hgdev\python{python_version}-{arch}\python.exe -m pip wheel --wheel-dir dist .
    93 if ($LASTEXITCODE -ne 0) {{
    61 if ($LASTEXITCODE -ne 0) {{
   103 if ($LASTEXITCODE -ne 0) {{
    71 if ($LASTEXITCODE -ne 0) {{
   104     throw "process exited non-0: $LASTEXITCODE"
    72     throw "process exited non-0: $LASTEXITCODE"
   105 }}
    73 }}
   106 '''
    74 '''
   107 
    75 
   108 BUILD_WIX_PYTHON2 = r'''
       
   109 Set-Location C:\hgdev\src
       
   110 $python = "C:\hgdev\python27-{arch}\python.exe"
       
   111 C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --python $python {extra_args}
       
   112 if ($LASTEXITCODE -ne 0) {{
       
   113     throw "process exited non-0: $LASTEXITCODE"
       
   114 }}
       
   115 '''
       
   116 
    76 
   117 RUN_TESTS = r'''
    77 RUN_TESTS = r'''
   118 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}"
    78 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}"
   119 if ($LASTEXITCODE -ne 0) {{
    79 if ($LASTEXITCODE -ne 0) {{
   120     throw "process exited non-0: $LASTEXITCODE"
    80     throw "process exited non-0: $LASTEXITCODE"
   121 }}
    81 }}
   122 '''
    82 '''
   123 
    83 
   124 WHEEL_FILENAME_PYTHON27_X86 = 'mercurial-{version}-cp27-cp27m-win32.whl'
    84 
   125 WHEEL_FILENAME_PYTHON27_X64 = 'mercurial-{version}-cp27-cp27m-win_amd64.whl'
       
   126 WHEEL_FILENAME_PYTHON37_X86 = 'mercurial-{version}-cp37-cp37m-win32.whl'
    85 WHEEL_FILENAME_PYTHON37_X86 = 'mercurial-{version}-cp37-cp37m-win32.whl'
   127 WHEEL_FILENAME_PYTHON37_X64 = 'mercurial-{version}-cp37-cp37m-win_amd64.whl'
    86 WHEEL_FILENAME_PYTHON37_X64 = 'mercurial-{version}-cp37-cp37m-win_amd64.whl'
   128 WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl'
    87 WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl'
   129 WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl'
    88 WHEEL_FILENAME_PYTHON38_X64 = 'mercurial-{version}-cp38-cp38-win_amd64.whl'
   130 WHEEL_FILENAME_PYTHON39_X86 = 'mercurial-{version}-cp39-cp39-win32.whl'
    89 WHEEL_FILENAME_PYTHON39_X86 = 'mercurial-{version}-cp39-cp39-win32.whl'
   131 WHEEL_FILENAME_PYTHON39_X64 = 'mercurial-{version}-cp39-cp39-win_amd64.whl'
    90 WHEEL_FILENAME_PYTHON39_X64 = 'mercurial-{version}-cp39-cp39-win_amd64.whl'
   132 WHEEL_FILENAME_PYTHON310_X86 = 'mercurial-{version}-cp310-cp310-win32.whl'
    91 WHEEL_FILENAME_PYTHON310_X86 = 'mercurial-{version}-cp310-cp310-win32.whl'
   133 WHEEL_FILENAME_PYTHON310_X64 = 'mercurial-{version}-cp310-cp310-win_amd64.whl'
    92 WHEEL_FILENAME_PYTHON310_X64 = 'mercurial-{version}-cp310-cp310-win_amd64.whl'
   134 
    93 
   135 EXE_FILENAME_PYTHON2_X86 = 'Mercurial-{version}-x86-python2.exe'
       
   136 EXE_FILENAME_PYTHON2_X64 = 'Mercurial-{version}-x64-python2.exe'
       
   137 EXE_FILENAME_PYTHON3_X86 = 'Mercurial-{version}-x86.exe'
    94 EXE_FILENAME_PYTHON3_X86 = 'Mercurial-{version}-x86.exe'
   138 EXE_FILENAME_PYTHON3_X64 = 'Mercurial-{version}-x64.exe'
    95 EXE_FILENAME_PYTHON3_X64 = 'Mercurial-{version}-x64.exe'
   139 
    96 
   140 MSI_FILENAME_PYTHON2_X86 = 'mercurial-{version}-x86-python2.msi'
       
   141 MSI_FILENAME_PYTHON2_X64 = 'mercurial-{version}-x64-python2.msi'
       
   142 MSI_FILENAME_PYTHON3_X86 = 'mercurial-{version}-x86.msi'
    97 MSI_FILENAME_PYTHON3_X86 = 'mercurial-{version}-x86.msi'
   143 MSI_FILENAME_PYTHON3_X64 = 'mercurial-{version}-x64.msi'
    98 MSI_FILENAME_PYTHON3_X64 = 'mercurial-{version}-x64.msi'
   144 
    99 
   145 MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows'
   100 MERCURIAL_SCM_BASE_URL = 'https://mercurial-scm.org/release/windows'
   146 
   101 
   147 X86_USER_AGENT_PATTERN = '.*Windows.*'
   102 X86_USER_AGENT_PATTERN = '.*Windows.*'
   148 X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*'
   103 X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*'
   149 
   104 
   150 EXE_PYTHON2_X86_DESCRIPTION = (
       
   151     'Mercurial {version} Inno Setup installer - x86 Windows (Python 2) '
       
   152     '- does not require admin rights'
       
   153 )
       
   154 EXE_PYTHON2_X64_DESCRIPTION = (
       
   155     'Mercurial {version} Inno Setup installer - x64 Windows (Python 2) '
       
   156     '- does not require admin rights'
       
   157 )
       
   158 # TODO remove Python version once Python 2 is dropped.
   105 # TODO remove Python version once Python 2 is dropped.
   159 EXE_PYTHON3_X86_DESCRIPTION = (
   106 EXE_PYTHON3_X86_DESCRIPTION = (
   160     'Mercurial {version} Inno Setup installer - x86 Windows (Python 3) '
   107     'Mercurial {version} Inno Setup installer - x86 Windows (Python 3) '
   161     '- does not require admin rights'
   108     '- does not require admin rights'
   162 )
   109 )
   163 EXE_PYTHON3_X64_DESCRIPTION = (
   110 EXE_PYTHON3_X64_DESCRIPTION = (
   164     'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) '
   111     'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) '
   165     '- does not require admin rights'
   112     '- does not require admin rights'
   166 )
   113 )
   167 MSI_PYTHON2_X86_DESCRIPTION = (
       
   168     'Mercurial {version} MSI installer - x86 Windows (Python 2) '
       
   169     '- requires admin rights'
       
   170 )
       
   171 MSI_PYTHON2_X64_DESCRIPTION = (
       
   172     'Mercurial {version} MSI installer - x64 Windows (Python 2) '
       
   173     '- requires admin rights'
       
   174 )
       
   175 MSI_PYTHON3_X86_DESCRIPTION = (
   114 MSI_PYTHON3_X86_DESCRIPTION = (
   176     'Mercurial {version} MSI installer - x86 Windows (Python 3) '
   115     'Mercurial {version} MSI installer - x86 Windows (Python 3) '
   177     '- requires admin rights'
   116     '- requires admin rights'
   178 )
   117 )
   179 MSI_PYTHON3_X64_DESCRIPTION = (
   118 MSI_PYTHON3_X64_DESCRIPTION = (
   180     'Mercurial {version} MSI installer - x64 Windows (Python 3) '
   119     'Mercurial {version} MSI installer - x64 Windows (Python 3) '
   181     '- requires admin rights'
   120     '- requires admin rights'
   182 )
   121 )
   183 
       
   184 
       
   185 def get_vc_prefix(arch):
       
   186     if arch == 'x86':
       
   187         return ACTIVATE_VC9_X86
       
   188     elif arch == 'x64':
       
   189         return ACTIVATE_VC9_AMD64
       
   190     else:
       
   191         raise ValueError('illegal arch: %s; must be x86 or x64' % arch)
       
   192 
   122 
   193 
   123 
   194 def fix_authorized_keys_permissions(winrm_client, path):
   124 def fix_authorized_keys_permissions(winrm_client, path):
   195     commands = [
   125     commands = [
   196         '$ErrorActionPreference = "Stop"',
   126         '$ErrorActionPreference = "Stop"',
   347     print(
   277     print(
   348         'building Inno Setup installer for Python %d %s'
   278         'building Inno Setup installer for Python %d %s'
   349         % (python_version, arch)
   279         % (python_version, arch)
   350     )
   280     )
   351 
   281 
   352     if python_version == 3:
   282     # TODO fix this limitation in packaging code
   353         # TODO fix this limitation in packaging code
   283     if not version:
   354         if not version:
   284         raise Exception("version string is required when building for Python 3")
   355             raise Exception(
   285 
   356                 "version string is required when building for Python 3"
   286     if arch == "x86":
   357             )
   287         target_triple = "i686-pc-windows-msvc"
   358 
   288     elif arch == "x64":
   359         if arch == "x86":
   289         target_triple = "x86_64-pc-windows-msvc"
   360             target_triple = "i686-pc-windows-msvc"
       
   361         elif arch == "x64":
       
   362             target_triple = "x86_64-pc-windows-msvc"
       
   363         else:
       
   364             raise Exception("unhandled arch: %s" % arch)
       
   365 
       
   366         ps = BUILD_INNO_PYTHON3.format(
       
   367             pyoxidizer_target=target_triple,
       
   368             version=version,
       
   369         )
       
   370     else:
   290     else:
   371         extra_args = []
   291         raise Exception("unhandled arch: %s" % arch)
   372         if version:
   292 
   373             extra_args.extend(['--version', version])
   293     ps = BUILD_INNO_PYTHON3.format(
   374 
   294         pyoxidizer_target=target_triple,
   375         ps = get_vc_prefix(arch) + BUILD_INNO_PYTHON2.format(
   295         version=version,
   376             arch=arch, extra_args=' '.join(extra_args)
   296     )
   377         )
       
   378 
   297 
   379     run_powershell(winrm_client, ps)
   298     run_powershell(winrm_client, ps)
   380     copy_latest_dist(winrm_client, '*.exe', dest_path)
   299     copy_latest_dist(winrm_client, '*.exe', dest_path)
   381 
   300 
   382 
   301 
   391     print('Building Windows wheel for Python %s %s' % (python_version, arch))
   310     print('Building Windows wheel for Python %s %s' % (python_version, arch))
   392 
   311 
   393     ps = BUILD_WHEEL.format(
   312     ps = BUILD_WHEEL.format(
   394         python_version=python_version.replace(".", ""), arch=arch
   313         python_version=python_version.replace(".", ""), arch=arch
   395     )
   314     )
   396 
       
   397     # Python 2.7 requires an activated environment.
       
   398     if python_version == "2.7":
       
   399         ps = get_vc_prefix(arch) + ps
       
   400 
   315 
   401     run_powershell(winrm_client, ps)
   316     run_powershell(winrm_client, ps)
   402     copy_latest_dist(winrm_client, '*.whl', dest_path)
   317     copy_latest_dist(winrm_client, '*.whl', dest_path)
   403 
   318 
   404 
   319 
   413 
   328 
   414     Using a WinRM client, remote commands are executed to build a WiX installer.
   329     Using a WinRM client, remote commands are executed to build a WiX installer.
   415     """
   330     """
   416     print('Building WiX installer for Python %d %s' % (python_version, arch))
   331     print('Building WiX installer for Python %d %s' % (python_version, arch))
   417 
   332 
   418     if python_version == 3:
   333     # TODO fix this limitation in packaging code
   419         # TODO fix this limitation in packaging code
   334     if not version:
   420         if not version:
   335         raise Exception("version string is required when building for Python 3")
   421             raise Exception(
   336 
   422                 "version string is required when building for Python 3"
   337     if arch == "x86":
   423             )
   338         target_triple = "i686-pc-windows-msvc"
   424 
   339     elif arch == "x64":
   425         if arch == "x86":
   340         target_triple = "x86_64-pc-windows-msvc"
   426             target_triple = "i686-pc-windows-msvc"
       
   427         elif arch == "x64":
       
   428             target_triple = "x86_64-pc-windows-msvc"
       
   429         else:
       
   430             raise Exception("unhandled arch: %s" % arch)
       
   431 
       
   432         ps = BUILD_WIX_PYTHON3.format(
       
   433             pyoxidizer_target=target_triple,
       
   434             version=version,
       
   435         )
       
   436     else:
   341     else:
   437         extra_args = []
   342         raise Exception("unhandled arch: %s" % arch)
   438         if version:
   343 
   439             extra_args.extend(['--version', version])
   344     ps = BUILD_WIX_PYTHON3.format(
   440 
   345         pyoxidizer_target=target_triple,
   441         ps = get_vc_prefix(arch) + BUILD_WIX_PYTHON2.format(
   346         version=version,
   442             arch=arch, extra_args=' '.join(extra_args)
   347     )
   443         )
       
   444 
   348 
   445     run_powershell(winrm_client, ps)
   349     run_powershell(winrm_client, ps)
   446     copy_latest_dist(winrm_client, '*.msi', dest_path)
   350     copy_latest_dist(winrm_client, '*.msi', dest_path)
   447 
   351 
   448 
   352 
   472     run_powershell(winrm_client, ps)
   376     run_powershell(winrm_client, ps)
   473 
   377 
   474 
   378 
   475 def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str):
   379 def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str):
   476     return (
   380     return (
   477         dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version),
       
   478         dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version),
       
   479         dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
   381         dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
   480         dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
   382         dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
   481         dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
   383         dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
   482         dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version),
   384         dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version),
   483         dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version),
   385         dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version),
   487     )
   389     )
   488 
   390 
   489 
   391 
   490 def resolve_all_artifacts(dist_path: pathlib.Path, version: str):
   392 def resolve_all_artifacts(dist_path: pathlib.Path, version: str):
   491     return (
   393     return (
   492         dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version),
       
   493         dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version),
       
   494         dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
   394         dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
   495         dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
   395         dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
   496         dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
   396         dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
   497         dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version),
   397         dist_path / WHEEL_FILENAME_PYTHON38_X64.format(version=version),
   498         dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version),
   398         dist_path / WHEEL_FILENAME_PYTHON39_X86.format(version=version),
   499         dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version),
   399         dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version),
   500         dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version),
   400         dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version),
   501         dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version),
   401         dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version),
   502         dist_path / EXE_FILENAME_PYTHON2_X86.format(version=version),
       
   503         dist_path / EXE_FILENAME_PYTHON2_X64.format(version=version),
       
   504         dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version),
   402         dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version),
   505         dist_path / EXE_FILENAME_PYTHON3_X64.format(version=version),
   403         dist_path / EXE_FILENAME_PYTHON3_X64.format(version=version),
   506         dist_path / MSI_FILENAME_PYTHON2_X86.format(version=version),
       
   507         dist_path / MSI_FILENAME_PYTHON2_X64.format(version=version),
       
   508         dist_path / MSI_FILENAME_PYTHON3_X86.format(version=version),
   404         dist_path / MSI_FILENAME_PYTHON3_X86.format(version=version),
   509         dist_path / MSI_FILENAME_PYTHON3_X64.format(version=version),
   405         dist_path / MSI_FILENAME_PYTHON3_X64.format(version=version),
   510     )
   406     )
   511 
   407 
   512 
   408 
   513 def generate_latest_dat(version: str):
   409 def generate_latest_dat(version: str):
   514     python2_x86_exe_filename = EXE_FILENAME_PYTHON2_X86.format(version=version)
       
   515     python2_x64_exe_filename = EXE_FILENAME_PYTHON2_X64.format(version=version)
       
   516     python3_x86_exe_filename = EXE_FILENAME_PYTHON3_X86.format(version=version)
   410     python3_x86_exe_filename = EXE_FILENAME_PYTHON3_X86.format(version=version)
   517     python3_x64_exe_filename = EXE_FILENAME_PYTHON3_X64.format(version=version)
   411     python3_x64_exe_filename = EXE_FILENAME_PYTHON3_X64.format(version=version)
   518     python2_x86_msi_filename = MSI_FILENAME_PYTHON2_X86.format(version=version)
       
   519     python2_x64_msi_filename = MSI_FILENAME_PYTHON2_X64.format(version=version)
       
   520     python3_x86_msi_filename = MSI_FILENAME_PYTHON3_X86.format(version=version)
   412     python3_x86_msi_filename = MSI_FILENAME_PYTHON3_X86.format(version=version)
   521     python3_x64_msi_filename = MSI_FILENAME_PYTHON3_X64.format(version=version)
   413     python3_x64_msi_filename = MSI_FILENAME_PYTHON3_X64.format(version=version)
   522 
   414 
   523     entries = (
   415     entries = (
   524         (
   416         (
   534             X64_USER_AGENT_PATTERN,
   426             X64_USER_AGENT_PATTERN,
   535             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_exe_filename),
   427             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_exe_filename),
   536             EXE_PYTHON3_X64_DESCRIPTION.format(version=version),
   428             EXE_PYTHON3_X64_DESCRIPTION.format(version=version),
   537         ),
   429         ),
   538         (
   430         (
   539             '9',
       
   540             version,
       
   541             X86_USER_AGENT_PATTERN,
       
   542             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_exe_filename),
       
   543             EXE_PYTHON2_X86_DESCRIPTION.format(version=version),
       
   544         ),
       
   545         (
       
   546             '9',
       
   547             version,
       
   548             X64_USER_AGENT_PATTERN,
       
   549             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_exe_filename),
       
   550             EXE_PYTHON2_X64_DESCRIPTION.format(version=version),
       
   551         ),
       
   552         (
       
   553             '10',
   431             '10',
   554             version,
   432             version,
   555             X86_USER_AGENT_PATTERN,
   433             X86_USER_AGENT_PATTERN,
   556             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_msi_filename),
   434             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x86_msi_filename),
   557             MSI_PYTHON3_X86_DESCRIPTION.format(version=version),
   435             MSI_PYTHON3_X86_DESCRIPTION.format(version=version),
   561             version,
   439             version,
   562             X64_USER_AGENT_PATTERN,
   440             X64_USER_AGENT_PATTERN,
   563             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_msi_filename),
   441             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_msi_filename),
   564             MSI_PYTHON3_X64_DESCRIPTION.format(version=version),
   442             MSI_PYTHON3_X64_DESCRIPTION.format(version=version),
   565         ),
   443         ),
   566         (
       
   567             '9',
       
   568             version,
       
   569             X86_USER_AGENT_PATTERN,
       
   570             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_msi_filename),
       
   571             MSI_PYTHON2_X86_DESCRIPTION.format(version=version),
       
   572         ),
       
   573         (
       
   574             '9',
       
   575             version,
       
   576             X64_USER_AGENT_PATTERN,
       
   577             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_msi_filename),
       
   578             MSI_PYTHON2_X64_DESCRIPTION.format(version=version),
       
   579         ),
       
   580     )
   444     )
   581 
   445 
   582     lines = ['\t'.join(e) for e in entries]
   446     lines = ['\t'.join(e) for e in entries]
   583 
   447 
   584     return '\n'.join(lines) + '\n'
   448     return '\n'.join(lines) + '\n'