Skip to content
Snippets Groups Projects
Commit 57b5452a authored by Gregory Szorc's avatar Gregory Szorc
Browse files

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
parent 0c184932
No related branches found
No related tags found
No related merge requests found
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.46.0 sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup install 1.31.1 1.46.0
sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy sudo -H -u hg -g hg /home/hg/.cargo/bin/rustup component add clippy
sudo -H -u hg -g hg /home/hg/.cargo/bin/cargo install --version 0.7.0 pyoxidizer sudo -H -u hg -g hg /home/hg/.cargo/bin/cargo install --git https://github.com/indygreg/PyOxidizer.git --rev 4697fb25918dfad6dc73288daeea501063963a08 pyoxidizer
''' '''
......
...@@ -130,7 +130,7 @@ ...@@ -130,7 +130,7 @@
Invoke-Process "${prefix}\cargo\bin\rustup.exe" "component add clippy" Invoke-Process "${prefix}\cargo\bin\rustup.exe" "component add clippy"
# Install PyOxidizer for packaging. # Install PyOxidizer for packaging.
Invoke-Process "${prefix}\cargo\bin\cargo.exe" "install --version 0.7.0 pyoxidizer" Invoke-Process "${prefix}\cargo\bin\cargo.exe" "install --git https://github.com/indygreg/PyOxidizer.git --rev 4697fb25918dfad6dc73288daeea501063963a08 pyoxidizer"
} }
function Install-Dependencies($prefix) { function Install-Dependencies($prefix) {
......
...@@ -42,10 +42,10 @@ ...@@ -42,10 +42,10 @@
] ]
STAGING_RULES_APP = [ STAGING_RULES_APP = [
('mercurial/helptext/**/*.txt', 'helptext/'), ('lib/mercurial/helptext/**/*.txt', 'helptext/'),
('mercurial/defaultrc/*.rc', 'defaultrc/'), ('lib/mercurial/defaultrc/*.rc', 'defaultrc/'),
('mercurial/locale/**/*', 'locale/'), ('lib/mercurial/locale/**/*', 'locale/'),
('mercurial/templates/**/*', 'templates/'), ('lib/mercurial/templates/**/*', 'templates/'),
] ]
STAGING_EXCLUDES_WINDOWS = [ STAGING_EXCLUDES_WINDOWS = [
...@@ -109,5 +109,6 @@ ...@@ -109,5 +109,6 @@
# Now assemble all the files from PyOxidizer into the staging directory. # Now assemble all the files from PyOxidizer into the staging directory.
shutil.copytree(build_dir, out_dir) shutil.copytree(build_dir, out_dir)
# Move some of those files around. # Move some of those files around. We can get rid of this once Mercurial
# is taught to use the importlib APIs for reading resources.
process_install_rules(STAGING_RULES_APP, build_dir, out_dir) process_install_rules(STAGING_RULES_APP, build_dir, out_dir)
...@@ -113,7 +114,4 @@ ...@@ -113,7 +114,4 @@
process_install_rules(STAGING_RULES_APP, build_dir, out_dir) process_install_rules(STAGING_RULES_APP, build_dir, out_dir)
# Nuke the mercurial/* directory, as we copied resources
# to an appropriate location just above.
shutil.rmtree(out_dir / "mercurial")
# We also need to run setup.py build_doc to produce html files, # We also need to run setup.py build_doc to produce html files,
# as they aren't built as part of ``pip install``. # as they aren't built as part of ``pip install``.
......
This diff is collapsed.
...@@ -13,7 +13,11 @@ ...@@ -13,7 +13,11 @@
[dependencies] [dependencies]
jemallocator-global = { version = "0.3", optional = true } jemallocator-global = { version = "0.3", optional = true }
pyembed = { git = "https://github.com/indygreg/PyOxidizer.git", rev = "c772a1379c3026314eda1c8ea244b86c0658951d", default-features=false }
[dependencies.pyembed]
git = "https://github.com/indygreg/PyOxidizer.git"
rev = "4697fb25918dfad6dc73288daeea501063963a08"
default-features = false
[features] [features]
default = ["build-mode-pyoxidizer-exe"] default = ["build-mode-pyoxidizer-exe"]
......
ROOT = CWD + "/../.." ROOT = CWD + "/../.."
IS_WINDOWS = "windows" in BUILD_TARGET_TRIPLE
# Code to run in Python interpreter. # Code to run in Python interpreter.
RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()"
...@@ -11,5 +13,25 @@ ...@@ -11,5 +13,25 @@
def make_distribution_windows(): def make_distribution_windows():
return default_python_distribution(flavor = "standalone_dynamic") return default_python_distribution(flavor = "standalone_dynamic")
def resource_callback(policy, resource):
# We use a custom resource routing policy to influence where things are loaded
# from.
#
# For Python modules and resources, we load from memory if they are in
# the standard library and from the filesystem if not. This is because
# parts of Mercurial and some 3rd party packages aren't yet compatible
# with memory loading.
#
# For Python extension modules, we load from the filesystem because
# this yields greatest compatibility.
if type(resource) in ("PythonModuleSource", "PythonPackageResource", "PythonPackageDistributionResource"):
if resource.is_stdlib:
resource.add_location = "in-memory"
else:
resource.add_location = "filesystem-relative:lib"
elif type(resource) == "PythonExtensionModule":
resource.add_location = "filesystem-relative:lib"
def make_exe(dist): def make_exe(dist):
"""Builds a Rust-wrapped Mercurial binary.""" """Builds a Rust-wrapped Mercurial binary."""
...@@ -14,5 +36,12 @@ ...@@ -14,5 +36,12 @@
def make_exe(dist): def make_exe(dist):
"""Builds a Rust-wrapped Mercurial binary.""" """Builds a Rust-wrapped Mercurial binary."""
packaging_policy = dist.make_python_packaging_policy()
# Extension may depend on any Python functionality. Include all
# extensions.
packaging_policy.extension_module_filter = "all"
packaging_policy.resources_policy = "prefer-in-memory-fallback-filesystem-relative:lib"
packaging_policy.register_resource_callback(resource_callback)
config = PythonInterpreterConfig( config = PythonInterpreterConfig(
raw_allocator = "system", raw_allocator = "system",
run_eval = RUN_CODE, run_eval = RUN_CODE,
...@@ -25,5 +54,5 @@ ...@@ -25,5 +54,5 @@
exe = dist.to_python_executable( exe = dist.to_python_executable(
name = "hg", name = "hg",
resources_policy = "prefer-in-memory-fallback-filesystem-relative:lib", packaging_policy = packaging_policy,
config = config, config = config,
...@@ -29,7 +58,4 @@ ...@@ -29,7 +58,4 @@
config = config, config = config,
# Extension may depend on any Python functionality. Include all
# extensions.
extension_module_filter = "all",
) )
# Add Mercurial to resources. # Add Mercurial to resources.
...@@ -33,23 +59,6 @@ ...@@ -33,23 +59,6 @@
) )
# Add Mercurial to resources. # Add Mercurial to resources.
for resource in dist.pip_install(["--verbose", ROOT]): exe.add_python_resources(exe.pip_install(["--verbose", ROOT]))
# This is a bit wonky and worth explaining.
#
# Various parts of Mercurial don't yet support loading package
# resources via the ResourceReader interface. Or, not having
# file-based resources would be too inconvenient for users.
#
# So, for package resources, we package them both in the
# filesystem as well as in memory. If both are defined,
# PyOxidizer will prefer the in-memory location. So even
# if the filesystem file isn't packaged in the location
# specified here, we should never encounter an errors as the
# resource will always be available in memory.
if type(resource) == "PythonPackageResource":
exe.add_filesystem_relative_python_resource(".", resource)
exe.add_in_memory_python_resource(resource)
else:
exe.add_python_resource(resource)
# On Windows, we install extra packages for convenience. # On Windows, we install extra packages for convenience.
...@@ -54,4 +63,4 @@ ...@@ -54,4 +63,4 @@
# On Windows, we install extra packages for convenience. # On Windows, we install extra packages for convenience.
if "windows" in BUILD_TARGET_TRIPLE: if IS_WINDOWS:
exe.add_python_resources( exe.add_python_resources(
...@@ -57,5 +66,5 @@ ...@@ -57,5 +66,5 @@
exe.add_python_resources( exe.add_python_resources(
dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"]), exe.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"]),
) )
return exe return exe
...@@ -95,4 +104,5 @@ ...@@ -95,4 +104,5 @@
# Everything below this is typically managed by PyOxidizer and doesn't need # Everything below this is typically managed by PyOxidizer and doesn't need
# to be updated by people. # to be updated by people.
PYOXIDIZER_VERSION = "0.7.0" PYOXIDIZER_VERSION = "0.8.0-pre"
PYOXIDIZER_COMMIT = "4697fb25918dfad6dc73288daeea501063963a08"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment