Make rezipping wheels optional and off by default

This commit is contained in:
Reid 'arrdem' McKenzie 2023-07-05 12:30:47 -06:00
parent 1bf3826f54
commit b76b2437b9
6 changed files with 44 additions and 29 deletions

View file

@ -40,6 +40,9 @@ py_test(
name = "hello_native", name = "hello_native",
main = "hello.py", main = "hello.py",
srcs = ["hello.py"], srcs = ["hello.py"],
deps = [
py_requirement("pyyaml"),
]
) )
zapp_test( zapp_test(
@ -73,7 +76,6 @@ zapp_test(
], ],
) )
zapp_test( zapp_test(
name = "hello_unzipped", name = "hello_unzipped",
zip_safe = False, zip_safe = False,

View file

@ -35,7 +35,7 @@ toolchain(
# Zapp plugins used as a runtime library by rules_zapp # Zapp plugins used as a runtime library by rules_zapp
py_library( py_library(
name = "zapp_support", name = "zapp_support",
srcs = glob(["support/**/*.py"]), srcs = ["__init__.py"] + glob(["support/**/*.py"]),
imports = [ imports = [
"..", "..",
] ]

0
zapp/__init__.py Normal file
View file

View file

@ -6,9 +6,11 @@ import argparse
import json import json
import os import os
import pathlib import pathlib
import re
import stat import stat
import sys import sys
import zipfile import zipfile
from collections import defaultdict
from email.parser import Parser from email.parser import Parser
from itertools import chain from itertools import chain
from pathlib import Path from pathlib import Path
@ -20,6 +22,9 @@ from zapp.support.unpack import cache_wheel_path
parser = argparse.ArgumentParser(description="The (bootstrap) Zapp compiler") parser = argparse.ArgumentParser(description="The (bootstrap) Zapp compiler")
parser.add_argument("-o", "--out", dest="output", help="Output target file") parser.add_argument("-o", "--out", dest="output", help="Output target file")
parser.add_argument("-d", "--debug", dest="debug", action="store_true", default=False) parser.add_argument("-d", "--debug", dest="debug", action="store_true", default=False)
parser.add_argument(
"--use-wheels", dest="use_wheels", action="store_true", default=False
)
parser.add_argument("manifest", help="The (JSON) manifest") parser.add_argument("manifest", help="The (JSON) manifest")
@ -51,6 +56,9 @@ for script in {scripts!r}:
""" """
whl_workspace_pattern = re.compile(r"^external/(?P<workspace>[^/]*?)/site-packages/")
def dsub(d1: dict, d2: dict) -> dict: def dsub(d1: dict, d2: dict) -> dict:
"""Dictionary subtraction. Remove k/vs from d1 if they occur in d2.""" """Dictionary subtraction. Remove k/vs from d1 if they occur in d2."""
@ -76,7 +84,7 @@ def dir_walk_prefixes(path):
yield os.path.join(*segments) yield os.path.join(*segments)
def load_wheel(opts, manifest, path): def load_wheel(opts, path):
"""Load a single wheel, returning ...""" """Load a single wheel, returning ..."""
def _parse_email(msg): def _parse_email(msg):
@ -97,17 +105,8 @@ def load_wheel(opts, manifest, path):
with open(os.path.join(path, "WHEEL")) as wheelf: with open(os.path.join(path, "WHEEL")) as wheelf:
wheel = _parse_email(wheelf.read()) wheel = _parse_email(wheelf.read())
prefix = os.path.dirname(path)
# Naive glob of sources; note that bazel may hvae inserted empty __init__.py trash # Naive glob of sources; note that bazel may hvae inserted empty __init__.py trash
sources = [ sources = []
(
dest,
spec,
)
for dest, spec in manifest["sources"].items()
if spec["source"].startswith(prefix)
]
# Retain only manifest-listed sources (dealing with __init__.py trash, but maybe not all conflicts) # Retain only manifest-listed sources (dealing with __init__.py trash, but maybe not all conflicts)
with open(os.path.join(path, "RECORD")) as recordf: with open(os.path.join(path, "RECORD")) as recordf:
@ -187,13 +186,23 @@ def rezip_wheels(opts, manifest):
Wheels which are unzipped should be re-zipped into the cache, if not present in the cache. Wheels which are unzipped should be re-zipped into the cache, if not present in the cache.
Files sourced from unzipped wheels should be removed, and a single wheel reference inserted.""" Files sourced from unzipped wheels should be removed, and a single wheel reference inserted.
"""
wheels = [ whl_srcs = defaultdict(dict)
load_wheel(opts, manifest, os.path.dirname(s["source"])) for k, s in list(manifest["sources"].items()):
for _, s in manifest["sources"].items() src = s["source"]
if s["source"].endswith("/WHEEL") m = re.match(whl_workspace_pattern, src)
] if m:
whl_srcs[m.group(1)][re.sub(whl_workspace_pattern, "", src)] = s
del manifest["sources"][k]
wheels = []
for bundle in whl_srcs.values():
whlk = next((k for k in bundle.keys() if k.endswith("WHEEL")), None)
whl_manifest = load_wheel(opts, os.path.dirname(bundle[whlk]["source"]))
whl_manifest["sources"].update(bundle)
wheels.append(whl_manifest)
manifest["requirements"] = {} manifest["requirements"] = {}
@ -201,8 +210,6 @@ def rezip_wheels(opts, manifest):
for w in wheels: for w in wheels:
# Try to cheat and hit in the local cache first rather than building wheels every time # Try to cheat and hit in the local cache first rather than building wheels every time
wn = wheel_name(w) wn = wheel_name(w)
# Expunge sources available in the wheel
manifest["sources"] = dsub(manifest["sources"], w["sources"])
# We may have a double-path dependency. # We may have a double-path dependency.
# If we DON'T, we have to zip # If we DON'T, we have to zip
@ -302,7 +309,6 @@ def enable_unzipping(opts, manifest):
def fix_sources(opts, manifest): def fix_sources(opts, manifest):
manifest["sources"] = {f: m for f, m in manifest["sources"]} manifest["sources"] = {f: m for f, m in manifest["sources"]}
return manifest return manifest
@ -318,6 +324,7 @@ def main():
setattr(opts, "tmpdir", d) setattr(opts, "tmpdir", d)
manifest = fix_sources(opts, manifest) manifest = fix_sources(opts, manifest)
if opts.use_wheels:
manifest = rezip_wheels(opts, manifest) manifest = rezip_wheels(opts, manifest)
manifest = ensure_srcs_map(opts, manifest) manifest = ensure_srcs_map(opts, manifest)
manifest = enable_unzipping(opts, manifest) manifest = enable_unzipping(opts, manifest)
@ -339,7 +346,7 @@ def main():
"manifest": manifest, "manifest": manifest,
}, },
sys.stdout, sys.stdout,
indent=2 indent=2,
) )
with open(opts.output, "w") as zapp: with open(opts.output, "w") as zapp:

0
zapp/support/__init__.py Normal file
View file

View file

@ -141,6 +141,14 @@ def _zapp_impl(ctx):
is_executable = False, is_executable = False,
) )
args = [
"--debug",
"-o", ctx.outputs.executable.path,
manifest_file.path
]
if ctx.attr.use_wheels:
args = ["--use-wheels"] + args
# Run compiler # Run compiler
ctx.actions.run( ctx.actions.run(
inputs = [ inputs = [
@ -150,10 +158,7 @@ def _zapp_impl(ctx):
outputs = [ctx.outputs.executable], outputs = [ctx.outputs.executable],
progress_message = "Building zapp file %s" % ctx.label, progress_message = "Building zapp file %s" % ctx.label,
executable = ctx.executable.compiler, executable = ctx.executable.compiler,
arguments = [ arguments = args,
"-o", ctx.outputs.executable.path,
manifest_file.path
],
mnemonic = "PythonCompile", mnemonic = "PythonCompile",
use_default_shell_env = True, use_default_shell_env = True,
execution_requirements = { execution_requirements = {
@ -177,13 +182,14 @@ _zapp_attrs = {
"entry_point": attr.string(), "entry_point": attr.string(),
"prelude_points": attr.string_list(), "prelude_points": attr.string_list(),
"zip_safe": attr.bool(default = True), "zip_safe": attr.bool(default = True),
"use_wheels": attr.bool(default = False),
# FIXME: These are really toolchain parameters, probably. # FIXME: These are really toolchain parameters, probably.
"compiler": attr.label( "compiler": attr.label(
default = Label(DEFAULT_COMPILER), default = Label(DEFAULT_COMPILER),
executable = True, executable = True,
cfg = "host", cfg = "host",
), ),
"shebang": attr.string(default = "/usr/bin/env %py3%"), "shebang": attr.string(default = "#!/usr/bin/env %py3%"),
} }
_zapp = rule( _zapp = rule(