# HG changeset patch # User Matt Harbison <matt_harbison@yahoo.com> # Date 1740265702 18000 # Sat Feb 22 18:08:22 2025 -0500 # Node ID 41719e7387619306cb0c658c076d014a92ce58b6 # Parent e2c239dae5a27cae33f542fa2df9d52baf116ccd # EXP-Topic hgext-index contrib: extract the hgext index generation from setup.py We can't call `setup.py` anymore, so the old code is stranded there. We need this for py2exe (with TortoiseHg) on Windows, but probably don't need it on macOS for py2app (with TortoiseHg) because the *.py/*.pyc files live in the filesystem. I haven't looked at the state of PyOxidizer yet. I question the wisdom of the environment (e.g. why lock into pure mode- that seems like it would be an issue for the standard zstd clone, but in practice it doesn't trip over this). Likely, this was tuned for other things that `setup.py` ran, but I'm copying it over for now as-is. Since this used to be called via `setup.py` with a specific target (and wasn't part of `make local`), there's a new target instead of being buried in `make local`. Since it's dependent on the local venv, `local` is a dependency. Now a script that needs this to bundle Mercurial can call `make local hgext_index` and build everything, without affecting the developer using `make local`. We could do all kinds of fancy things like taking an output argument and not relying on the local venv, but this is a corner case in the packaging process, and I don't feel like making a project out of it. diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -71,6 +71,10 @@ pip install -e . -v $(PIP_OPTIONS_PURE) env HGRCPATH= $(VENV_NAME)/$(PYBINDIRNAME)/hg version +.PHONY: hgext_index +hgext_index: local + $(VENV_NAME)/$(PYBINDIRNAME)/python ./contrib/packaging/build-hgext-index.py + .PHONY: build-chg build-chg: make -C contrib/chg diff --git a/contrib/packaging/build-hgext-index.py b/contrib/packaging/build-hgext-index.py new file mode 100755 --- /dev/null +++ b/contrib/packaging/build-hgext-index.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# +# build-hgext-index.py - Generate the bundled extension index for frozen builds +# +# Copyright 2025 Matt Harbison <mharbison72@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import annotations + +import os +import subprocess +import sys + +_filename = 'hgext/__index__.py' + + +def localhgenv(pure_python=True): + """Get an environment dictionary to use for invoking or importing + mercurial from the local repository.""" + # Execute hg out of this directory with a custom environment which takes + # care to not use any hgrc files and do no localization. + env = { + 'HGRCPATH': '', + 'LANGUAGE': 'C', + 'PATH': '', + } # make pypi modules that use os.environ['PATH'] happy + if pure_python: + env['HGMODULEPOLICY'] = 'py' + if 'LD_LIBRARY_PATH' in os.environ: + env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] + if 'SystemRoot' in os.environ: + # SystemRoot is required by Windows to load various DLLs. See: + # https://bugs.python.org/issue13524#msg148850 + env['SystemRoot'] = os.environ['SystemRoot'] + return env + + +def main(): + """generate prebuilt index of hgext (for frozen package)""" + + # here no extension enabled, disabled() lists up everything + code = ( + 'import pprint; from mercurial import extensions; ' + 'ext = extensions.disabled();' + 'ext.pop("__index__", None);' + 'pprint.pprint(ext)' + ) + + out = subprocess.check_output( + args=[sys.executable, '-c', code], + env=localhgenv(), + ) + + with open(_filename, 'wb') as f: + f.write(b'# this file is autogenerated by setup.py\n') + f.write(b'docs = ') + f.write(out) + + return 0 + + +if __name__ == "__main__": + main() # HG changeset patch # User Matt Harbison <matt_harbison@yahoo.com> # Date 1740266124 18000 # Sat Feb 22 18:15:24 2025 -0500 # Node ID 43f4a8e56d4aa72241677834ed96784f0f1bd177 # Parent 41719e7387619306cb0c658c076d014a92ce58b6 # EXP-Topic hgext-index setup: drop the unused hgext index generation I guess this answers the question about if PyOxidizer needs it. The odd thing is that PyOxidizer builds were failing with the --no-pep-517 option until `setup.py` was recently reworked. I'm not sure how to work this into the PyOxidizer build process yet, but it's inaccessible, so drop it. diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -522,41 +522,6 @@ build_py.run(self) -class buildhgextindex(Command): - description = 'generate prebuilt index of hgext (for frozen package)' - user_options = [] - _indexfilename = 'hgext/__index__.py' - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - if os.path.exists(self._indexfilename): - with open(self._indexfilename, 'w') as f: - f.write('# empty\n') - - # here no extension enabled, disabled() lists up everything - code = ( - 'import pprint; from mercurial import extensions; ' - 'ext = extensions.disabled();' - 'ext.pop("__index__", None);' - 'pprint.pprint(ext)' - ) - returncode, out, err = runcmd( - [sys.executable, '-c', code], localhgenv() - ) - if err or returncode != 0: - raise DistutilsExecError(err) - - with open(self._indexfilename, 'wb') as f: - f.write(b'# this file is autogenerated by setup.py\n') - f.write(b'docs = ') - f.write(out) - - class buildhgexe(build_ext): description = 'compile hg.exe from mercurial/exewrapper.c' @@ -974,7 +939,6 @@ 'build_ext': hgbuildext, 'build_py': hgbuildpy, 'build_scripts': hgbuildscripts, - 'build_hgextindex': buildhgextindex, 'install': hginstall, 'install_completion': hginstallcompletion, 'install_lib': hginstalllib, @@ -1388,10 +1352,6 @@ 'product_version': version, } ] - # Sub command of 'build' because 'py2exe' does not handle sub_commands. - # Need to override hgbuild because it has a private copy of - # build.sub_commands. - hgbuild.sub_commands.insert(0, ('build_hgextindex', None)) # put dlls in sub directory so that they won't pollute PATH extra['zipfile'] = 'lib/library.zip' @@ -1414,9 +1374,6 @@ if dllexcludes: py2exedllexcludes.extend(dllexcludes.split(' ')) -if os.environ.get('PYOXIDIZER'): - hgbuild.sub_commands.insert(0, ('build_hgextindex', None)) - setup( long_description=( 'Mercurial is a distributed SCM tool written in Python.' # HG changeset patch # User Matt Harbison <matt_harbison@yahoo.com> # Date 1740612076 18000 # Wed Feb 26 18:21:16 2025 -0500 # Node ID 53711003945d18e4808a6622c5a97d8623ba719d # Parent 43f4a8e56d4aa72241677834ed96784f0f1bd177 # EXP-Topic hgext-index extensions: relax the checks around loading disabled extension help Prior to 843418dc0b1b, `hg help -e disabled_extension` would print a summary of the extension, even if the packaging was frozen and the help had to be read from `hgext.__index__`. That refactoring was meant to eliminate test instability if the index was present (since the command normally produces the full help text). That broke the command above for py2exe based packaging, so f4a363b25859 attempted to bring that back, but after the filesystem checks for the sake of test stability. That appears to keep pyoxidizer happy, but unfortunately py2exe actually has a `__file__` attribute on `hgext`, even though it is buried in a zip file. That caused the index to continue to be ignored. At this point, pyoxidizer builds seem to have `hgext.__file__` and files in the filesystem (even though the comment in `extensions._disabledpaths()` says otherwise), so this is really only for TortoiseHg builds based on py2exe. diff --git a/mercurial/extensions.py b/mercurial/extensions.py --- a/mercurial/extensions.py +++ b/mercurial/extensions.py @@ -855,7 +855,10 @@ # The extensions are filesystem based, so either an error occurred # or all are enabled. - if hasattr(hgext, '__file__'): + if ( + hasattr(hgext, '__file__') + and getattr(sys, "frozen", None) != "console_exe" # py2exe + ): return if name in _order: # enabled