pyoxidizer: produce working Python 3 Windows installers (
issue6366)
While we've had code to produce Python 3 Windows installers with
PyOxidizer, we haven't been advertising them on the web site due to
a bug in making TLS connections and issues around resource handling.
This commit upgrades our PyOxidizer install and configuration to
use a recent Git commit of PyOxidizer. This new version of PyOxidizer
contains a *ton* of changes, improvements, and bug fixes. Notably,
Windows shared distributions now mostly "just work" and the TLS bug
and random problems with Python extension modules in the standard
library go away. And Python has been upgraded from 3.7 to 3.8.6.
The price we pay for this upgrade is a ton of backwards incompatible
changes to Starlark.
I applied this commit (the overall series actually) on stable to
produce Windows installers for Mercurial 5.5.2, which I published
shortly before submitting this commit for review.
In order to get the stable branch working, I decided to take a
less aggressive approach to Python resource management. Previously,
we were attempting to load all Python modules from memory and were
performing some hacks to copy Mercurial's non-module resources
into additional directories in Starlark. This commit implements
a resource callback function in Starlark (a new feature since
PyOxidizer 0.7) to dynamically assign standard library resources
to in-memory loading and all other resources to filesystem loading.
This means that Mercurial's files and all the other packages we ship
in the Windows installers (e.g. certifi and pygments) are loaded
from the filesystem instead of from memory. This avoids issues
due to lack of __file__ and enables us to ship a working Python
3 installer on Windows.
The end state of the install layout after this patch is not
ideal for @: we still copy resource files like templates and
help text to directories next to the hg.exe executable. There
is code in @ to use importlib.resources to load these files and
we could likely remove these copies once this lands on @. But for
now, the install layout mimics what we've shipped for seemingly
forever and is backwards compatible. It allows us to achieve the
milestone of working Python 3 Windows installers and gets us a
giant step closer to deleting Python 2.
Differential Revision: https://phab.mercurial-scm.org/D9148
$ cat >> fakepager.py <<EOF
> import sys
> printed = False
> for line in sys.stdin:
> sys.stdout.write('paged! %r\n' % line)
> printed = True
> if not printed:
> sys.stdout.write('paged empty output!\n')
> EOF
Enable ui.formatted because pager won't fire without it, and set up
pager and tell it to use our fake pager that lets us see when the
pager was running.
$ cat >> $HGRCPATH <<EOF
> [ui]
> formatted = yes
> color = no
> [pager]
> pager = "$PYTHON" $TESTTMP/fakepager.py
> EOF
$ hg init repo
$ cd repo
$ echo a >> a
$ hg add a
$ hg ci -m 'add a'
$ for x in `"$PYTHON" $TESTDIR/seq.py 1 10`; do
> echo a $x >> a
> hg ci -m "modify a $x"
> done
By default diff and log are paged, but id is not:
$ hg diff -c 2 --pager=yes
paged! 'diff -r f4be7687d414 -r bce265549556 a\n'
paged! '--- a/a\tThu Jan 01 00:00:00 1970 +0000\n'
paged! '+++ b/a\tThu Jan 01 00:00:00 1970 +0000\n'
paged! '@@ -1,2 +1,3 @@\n'
paged! ' a\n'
paged! ' a 1\n'
paged! '+a 2\n'
$ hg log --limit 2
paged! 'changeset: 10:46106edeeb38\n'
paged! 'tag: tip\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 10\n'
paged! '\n'
paged! 'changeset: 9:6dd8ea7dd621\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 9\n'
paged! '\n'
$ hg id
46106edeeb38 tip
We can control the pager from the config
$ hg log --limit 1 --config 'ui.paginate=False'
changeset: 10:46106edeeb38
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: modify a 10
$ hg log --limit 1 --config 'ui.paginate=0'
changeset: 10:46106edeeb38
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: modify a 10
$ hg log --limit 1 --config 'ui.paginate=1'
paged! 'changeset: 10:46106edeeb38\n'
paged! 'tag: tip\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 10\n'
paged! '\n'
explicit --pager=on should take precedence over other configurations
(issue5580)
$ cat >> $HGRCPATH <<EOF
> [ui]
> paginate = false
> EOF
$ hg log --limit 1 --pager=on
paged! 'changeset: 10:46106edeeb38\n'
paged! 'tag: tip\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 10\n'
paged! '\n'
$ cat >> $HGRCPATH <<EOF
> [ui]
> # true is default value of ui.paginate
> paginate = true
> EOF
$ hg log --limit 1 --pager=off
changeset: 10:46106edeeb38
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: modify a 10
We can enable the pager on id:
BROKEN: should be paged
$ hg --config pager.attend-id=yes id
46106edeeb38 tip
Setting attend-$COMMAND to a false value works, even with pager in
core:
$ hg --config pager.attend-diff=no diff -c 2
diff -r f4be7687d414 -r bce265549556 a
--- a/a Thu Jan 01 00:00:00 1970 +0000
+++ b/a Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +1,3 @@
a
a 1
+a 2
Command aliases should have same behavior as main command
$ hg history --limit 2
paged! 'changeset: 10:46106edeeb38\n'
paged! 'tag: tip\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 10\n'
paged! '\n'
paged! 'changeset: 9:6dd8ea7dd621\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 9\n'
paged! '\n'
Abbreviated command alias should also be paged
$ hg hist -l 1
paged! 'changeset: 10:46106edeeb38\n'
paged! 'tag: tip\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 10\n'
paged! '\n'
Attend for an abbreviated command does not work
$ hg --config pager.attend-ident=true ident
46106edeeb38 tip
$ hg --config extensions.pager= --config pager.attend-ident=true ident
46106edeeb38 tip
Pager should not start if stdout is not a tty.
$ hg log -l1 -q --config ui.formatted=False
10:46106edeeb38
Pager should be disabled if pager.pager is empty (otherwise the output would
be silently lost.)
$ hg log -l1 -q --config pager.pager=
10:46106edeeb38
Pager with color enabled allows colors to come through by default,
even though stdout is no longer a tty.
$ cat >> $HGRCPATH <<EOF
> [ui]
> color = always
> [color]
> mode = ansi
> EOF
$ hg log --limit 3
paged! '\x1b[0;33mchangeset: 10:46106edeeb38\x1b[0m\n'
paged! 'tag: tip\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 10\n'
paged! '\n'
paged! '\x1b[0;33mchangeset: 9:6dd8ea7dd621\x1b[0m\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 9\n'
paged! '\n'
paged! '\x1b[0;33mchangeset: 8:cff05a6312fe\x1b[0m\n'
paged! 'user: test\n'
paged! 'date: Thu Jan 01 00:00:00 1970 +0000\n'
paged! 'summary: modify a 8\n'
paged! '\n'
#if no-chg
An invalid pager command name is reported sensibly if we don't have to
use shell=True in the subprocess call:
$ hg log --limit 3 --config pager.pager=this-command-better-never-exist
missing pager command 'this-command-better-never-exist', skipping pager
\x1b[0;33mchangeset: 10:46106edeeb38\x1b[0m (esc)
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: modify a 10
\x1b[0;33mchangeset: 9:6dd8ea7dd621\x1b[0m (esc)
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: modify a 9
\x1b[0;33mchangeset: 8:cff05a6312fe\x1b[0m (esc)
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: modify a 8
#endif
A complicated pager command gets worse behavior. Bonus points if you can
improve this.
$ hg log --limit 3 \
> --config pager.pager='this-command-better-never-exist --seriously' \
> 2>/dev/null || true
Pager works with shell aliases.
$ cat >> $HGRCPATH <<EOF
> [alias]
> echoa = !echo a
> EOF
$ hg echoa
a
BROKEN: should be paged
$ hg --config pager.attend-echoa=yes echoa
a
Pager works with hg aliases including environment variables.
$ cat >> $HGRCPATH <<'EOF'
> [alias]
> printa = log -T "$A\n" -r 0
> EOF
$ A=1 hg --config pager.attend-printa=yes printa
paged! '1\n'
$ A=2 hg --config pager.attend-printa=yes printa
paged! '2\n'
Something that's explicitly attended is still not paginated if the
pager is globally set to off using a flag:
$ A=2 hg --config pager.attend-printa=yes printa --pager=no
2
Pager should not override the exit code of other commands
$ cat >> $TESTTMP/fortytwo.py <<'EOF'
> from mercurial import commands, registrar
> cmdtable = {}
> command = registrar.command(cmdtable)
> @command(b'fortytwo', [], b'fortytwo', norepo=True)
> def fortytwo(ui, *opts):
> ui.write(b'42\n')
> return 42
> EOF
$ cat >> $HGRCPATH <<'EOF'
> [extensions]
> fortytwo = $TESTTMP/fortytwo.py
> EOF
$ hg fortytwo --pager=on
paged! '42\n'
[42]
A command that asks for paging using ui.pager() directly works:
$ hg blame a
paged! ' 0: a\n'
paged! ' 1: a 1\n'
paged! ' 2: a 2\n'
paged! ' 3: a 3\n'
paged! ' 4: a 4\n'
paged! ' 5: a 5\n'
paged! ' 6: a 6\n'
paged! ' 7: a 7\n'
paged! ' 8: a 8\n'
paged! ' 9: a 9\n'
paged! '10: a 10\n'
but not with HGPLAIN
$ HGPLAIN=1 hg blame a
0: a
1: a 1
2: a 2
3: a 3
4: a 4
5: a 5
6: a 6
7: a 7
8: a 8
9: a 9
10: a 10
explicit flags work too:
$ hg blame --pager=no a
0: a
1: a 1
2: a 2
3: a 3
4: a 4
5: a 5
6: a 6
7: a 7
8: a 8
9: a 9
10: a 10
A command with --output option:
$ hg cat -r0 a
paged! 'a\n'
$ hg cat -r0 a --output=-
paged! 'a\n'
$ hg cat -r0 a --output=out
$ hg export -r0
paged! '# HG changeset patch\n'
paged! '# User test\n'
paged! '# Date 0 0\n'
paged! '# Thu Jan 01 00:00:00 1970 +0000\n'
paged! '# Node ID 1f0dee641bb7258c56bd60e93edfa2405381c41e\n'
paged! '# Parent 0000000000000000000000000000000000000000\n'
paged! 'add a\n'
paged! '\n'
paged! '\x1b[0;1mdiff -r 000000000000 -r 1f0dee641bb7 a\x1b[0m\n'
paged! '\x1b[0;31;1m--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\x1b[0m\n'
paged! '\x1b[0;32;1m+++ b/a\tThu Jan 01 00:00:00 1970 +0000\x1b[0m\n'
paged! '\x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m\n'
paged! '\x1b[0;32m+a\x1b[0m\n'
$ hg export -r0 -o -
paged! '# HG changeset patch\n'
paged! '# User test\n'
paged! '# Date 0 0\n'
paged! '# Thu Jan 01 00:00:00 1970 +0000\n'
paged! '# Node ID 1f0dee641bb7258c56bd60e93edfa2405381c41e\n'
paged! '# Parent 0000000000000000000000000000000000000000\n'
paged! 'add a\n'
paged! '\n'
paged! '\x1b[0;1mdiff -r 000000000000 -r 1f0dee641bb7 a\x1b[0m\n'
paged! '\x1b[0;31;1m--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\x1b[0m\n'
paged! '\x1b[0;32;1m+++ b/a\tThu Jan 01 00:00:00 1970 +0000\x1b[0m\n'
paged! '\x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m\n'
paged! '\x1b[0;32m+a\x1b[0m\n'
$ hg export -r0 -o out
$ rm out
Put annotate in the ignore list for pager:
$ cat >> $HGRCPATH <<EOF
> [pager]
> ignore = annotate
> EOF
$ hg blame a
0: a
1: a 1
2: a 2
3: a 3
4: a 4
5: a 5
6: a 6
7: a 7
8: a 8
9: a 9
10: a 10
During pushbuffer, pager should not start:
$ cat > $TESTTMP/pushbufferpager.py <<EOF
> def uisetup(ui):
> ui.pushbuffer()
> ui.pager(b'mycmd')
> ui.write(b'content\n')
> ui.write(ui.popbuffer())
> EOF
$ echo append >> a
$ hg --config extensions.pushbuffer=$TESTTMP/pushbufferpager.py status --color=off
content
paged! 'M a\n'
Environment variables like LESS and LV are set automatically:
$ cat > $TESTTMP/printlesslv.py <<EOF
> from __future__ import absolute_import
> import os
> import sys
> sys.stdin.read()
> for name in ['LESS', 'LV']:
> sys.stdout.write(('%s=%s\n') % (name, os.environ.get(name, '-')))
> sys.stdout.flush()
> EOF
$ cat >> $HGRCPATH <<EOF
> [alias]
> noop = log -r 0 -T ''
> [ui]
> formatted=1
> [pager]
> pager = "$PYTHON" $TESTTMP/printlesslv.py
> EOF
$ unset LESS
$ unset LV
$ hg noop --pager=on
LESS=FRX
LV=-c
$ LESS=EFGH hg noop --pager=on
LESS=EFGH
LV=-c