[PATCH 4 of 6] packaging: build TortoiseHg with py2exe under python3

1 view
Skip to first unread message

Matt Harbison

unread,
May 2, 2022, 7:04:48 PMMay 2
to thg...@googlegroups.com
# HG changeset patch
# User Matt Harbison <matt_h...@yahoo.com>
# Date 1651520663 14400
# Mon May 02 15:44:23 2022 -0400
# Node ID c4005eaf652ffa264a90e297e20cdbebe00d77ad
# Parent fab9c0c5275c6df84ff216489f10f7df9c235371
# EXP-Topic windows-py3-packaging
packaging: build TortoiseHg with py2exe under python3

This is a reproduction of the process used by the winbuild repo's setup.py to
build thg, as well as the first few lines of its `build_thg_installer` function
that actually invokes the py2exe build. The HTML Help Workshop program is
basically the last remaining thing the user needs to install, so the check for
it is done up front before too much time is spent building. Generating the
resource files is a new hack, because the py3 version of py2exe doesn't pick
them up otherwise. It looks like that goes away in Qt6, so I'm not wasting time
investigating.

The *.cfg file is newly generated from scratch by trial and error, since the old
one in the winbuild repo had so much cruft in it. I was going to make it a
standalone file, but having it as code will make it easier to conditionalize to
support Qt6. For some reason, py2exe wasn't able to pick up most builtin hgext
extensions unless they were explicitly called out, so every single mercurial
related module is listed. Maybe those should be dumped into `setup.py`. A
handful of things (`encodings`, `json`, `nntplib`, and `sspi`) are a holdover
from the old cfg file. I have no idea if they are still needed.

I see the following between the *.mo file generation and the Sphinx document
generation run, but I also see it with the winbuild script, so it's not
something newly broken here. Some existing script invoked by `build chm` must
be missing quotes around a path with "Program Files" in it.

'Files' is not recognized as an internal or external command,
operable program or batch file.

diff --git a/contrib/packaging/thgpackaging/py2exe.py b/contrib/packaging/thgpackaging/py2exe.py
--- a/contrib/packaging/thgpackaging/py2exe.py
+++ b/contrib/packaging/thgpackaging/py2exe.py
@@ -80,6 +80,28 @@
env["HGPLAIN"] = "1"
env["HGRCPATH"] = ""

+ # Locate the HTML Help Workshop installation
+ if "PROGRAMW6432" in env:
+ programfiles_x86 = env.get("PROGRAMFILES(X86)")
+ else:
+ programfiles_x86 = env.get("PROGRAMFILES")
+
+ if programfiles_x86:
+ env['PATH'] = "%s%s%s" % (
+ env['PATH'],
+ os.pathsep,
+ os.path.join(programfiles_x86, 'HTML Help Workshop')
+ )
+
+ # If HTML Help Workshop needs to be installed, it can be found here:
+ # https://download.microsoft.com/download/OfficeXPProf/Install/4.71.1015.0/W98NT42KMe/EN-US/HTMLHELP.EXE
+ hhc = shutil.which("hhc.exe", path=env['PATH'])
+ if not hhc:
+ raise Exception("Unable to find HTML Help Workshop")
+
+ # The thg/doc/build.bat task fails unless this is set explicitly.
+ env['hhc_compiler'] = hhc
+
py_info = python_exe_info(python_exe)

vc_x64 = py_info['arch'] == '64bit'
@@ -201,6 +223,154 @@
check=True,
)

+ print('building TortoiseHg')
+ thg_dir = source_dirs.thg
+
+ if thg_dir.exists():
+ shutil.rmtree(thg_dir)
+ thg_dir.mkdir()
+
+ subprocess.run(
+ ['hg.exe', 'archive', '-r', 'wdir()', str(thg_dir)],
+ cwd=str(source_dirs.original),
+ env=env,
+ check=True,
+ )
+
+ subprocess.run(
+ [str(venv_python), 'setup.py', '--version'],
+ cwd=str(thg_dir),
+ env=env,
+ check=True,
+ )
+ subprocess.run( # Build locales
+ [str(venv_python), 'setup.py', 'build_mo'],
+ cwd=str(thg_dir),
+ env=env,
+ check=True,
+ )
+ subprocess.run( # Build cmenu translation registry files
+ [str(venv_python), 'reggen.py'],
+ cwd=str(thg_dir / "win32"),
+ env=env,
+ check=True,
+ )
+
+ # Use build helpers from win32
+ dest_config_py = thg_dir / 'tortoisehg' / 'util' / 'config.py'
+ shutil.copyfile(thg_dir / 'win32' / 'config.py', dest_config_py)
+
+ with open(thg_dir / "setup.cfg", "w") as fp:
+ fp.write('''
+[py2exe]
+includes = PyQt5.sip, PyQt5.QtPrintSupport, PyQt5.QtSvg, PyQt5.QtXml,
+ mercurial_keyring, pygit2
+
+packages = ctypes, curses, email, encodings, iniparse, json, keyring,
+ keyring.backends, nntplib, pygments, sspi, sqlite3, six,
+
+ hgext,
+ hgext.convert, hgext.fastannotate, hgext.fsmonitor,
+ hgext.fsmonitor.pywatchman, hgext.git, hgext.highlight,
+ hgext.hooklib, hgext.infinitepush, hgext.largefiles,
+ hgext.lfs, hgext.narrow, hgext.remotefilelog, hgext.zeroconf,
+ mercurial.cext, mercurial.pure, mercurial.defaultrc,
+
+ hgext3rd, hgext3rd.evolve, hgext3rd.topic,
+
+ tortoisehg.hgqt, tortoisehg.util
+'''.strip())
+
+ # py2exe has trouble finding hgext3rd packages unless they're dumped into
+ # the build directory.
+ evolve_hgext3rd_dir = source_dirs.evolve / 'hgext3rd'
+ shutil.copytree(evolve_hgext3rd_dir / 'evolve', thg_dir / 'hgext3rd' / 'evolve')
+ shutil.copytree(evolve_hgext3rd_dir / 'topic', thg_dir / 'hgext3rd' / 'topic')
+ shutil.rmtree(thg_dir / 'hgext3rd' / 'evolve' / 'hack')
+
+ # Note: hgextindex was regenerated here in the winbuild script.
+
+ subprocess.run( # Build docs
+ ['build', 'chm'],
+ shell=True,
+ cwd=str(thg_dir / "doc"),
+ env=env,
+ check=True,
+ )
+
+ env['MERCURIAL_PATH'] = str(hg_dir / "mercurial")
+ env['HGEXT_PATH'] = str(hg_dir / "hgext")
+
+ # Put pyrcc5.exe installed by pip on PATH
+ env['PATH'] = "%s;%s" % (venv_python.parent, env['PATH'])
+
+ # The py2exe target generates these in the same location for both py2 and
+ # py3 builds. But for some reason, the generated *_rc.py files aren't put
+ # into library.zip as part of the py3 build process. So generate the files
+ # early and copy them to the source tree where they will be picked up.
+ subprocess.run( # Build the resource files
+ [str(venv_python), 'setup.py', 'build_qrc'],
+ cwd=str(thg_dir),
+ env=env,
+ check=True,
+ )
+ for f in ("icons_rc.py", "translations_rc.py"):
+ shutil.copyfile(
+ thg_dir / "build" / "lib" / "tortoisehg" / "hgqt" / f,
+ thg_dir / "tortoisehg" / "hgqt" / f
+ )
+
+ subprocess.run( # Build the executables
+ [str(venv_python), 'setup.py', 'py2exe', '-b3'],
+ cwd=str(thg_dir),
+ env=env,
+ check=True,
+ )
+
+ dist_dir = thg_dir / "dist"
+
+ # For some reason, py2exe doesn't copy the -x64 named files over to dist/ at
+ # all. Both the files with and without -x64 are 64-bit in the 64-bit build.
+ # The non -x64 named files get copied to dist/lib by py2exe for both 32 and
+ # 64-bit builds. In order for the update check in the aboutbox to work,
+ # the -x64 named files need to be _next_ to lib/ in the 64-bit installation,
+ # and the non -x64 named files next to lib/ 32-bit installation. The non
+ # -x64 named files are required in the 64-bit build, or the app fails to
+ # launch.
+ #
+ # The check for this working properly from ``hg debugshell`` is:
+ #
+ # >>> from PyQt5 import QtNetwork
+ # >>> print(QtNetwork.QSslSocket.sslLibraryBuildVersionString())
+ # OpenSSL 1.1.1g 21 Apr 2020
+ # >>> print(QtNetwork.QSslSocket.sslLibraryVersionString())
+ # OpenSSL 1.1.1n 15 Mar 2022
+ #
+ # When not properly positioned, the output of the last line is empty.
+ if vc_x64:
+ path_script = """
+import os, sys
+join = os.path.join
+print(join(sys.exec_prefix, 'lib', 'site-packages', 'PyQt5', 'Qt5', 'bin'))
+""".strip()
+
+ binpath = os.fsdecode(subprocess.check_output(
+ [str(venv_python), '-c', path_script],
+ cwd=str(thg_dir),
+ env=env,
+ ).strip())
+
+ # For some reason, these don't load if installed in the lib directory.
+ for f in ("libcrypto-1_1-x64.dll", "libssl-1_1-x64.dll"):
+ shutil.copy(os.path.join(binpath, f), dist_dir)
+ else:
+ # For some reason, these don't load if installed in the lib directory.
+ # If it is merely copied next to lib/, the presence of two files with
+ # the same name confuses WiX (unless a File Id is explicitly provided),
+ # so remove it from lib/.
+ for f in ("libcrypto-1_1.dll", "libssl-1_1.dll"):
+ shutil.move(dist_dir / "lib" / f, dist_dir)
+

def stage_install(
source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
Reply all
Reply to author
Forward
0 new messages