diff --git a/WORKSPACE b/WORKSPACE
index b4975b4..9b9fe27 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -52,6 +52,12 @@ pip_install(
     python_interpreter = "python3",
 )
 
+git_repository(
+    name = "rules_zapp",
+    remote = "https://github.com/arrdem/rules_zapp.git",
+    tag = "0.1.0",
+)
+
 ####################################################################################################
 # Postscript
 ####################################################################################################
diff --git a/projects/datalog-shell/BUILD b/projects/datalog-shell/BUILD
index 5e00b40..0a4e2d9 100644
--- a/projects/datalog-shell/BUILD
+++ b/projects/datalog-shell/BUILD
@@ -1,4 +1,4 @@
-py_binary(
+zapp_binary(
     name = "datalog-shell",
     main = "src/python/datalog/shell/__main__.py",
     imports = [
diff --git a/projects/zapp/BUILD b/projects/zapp/BUILD
deleted file mode 100644
index ba39da1..0000000
--- a/projects/zapp/BUILD
+++ /dev/null
@@ -1,32 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-load("zapp.bzl",
-     "zapp_binary",
-)
-
-# Bootstrapping Zapp using py_binary
-py_binary(
-    name = "zappc",
-    main = "src/python/zapp/compiler/__main__.py",
-    imports = [
-        "src/python",
-    ],
-)
-
-# Zapp plugins used as a runtime library by rules_zapp
-py_library(
-    name = "zapp_support",
-    srcs = glob(["src/python/zapp/support/**/*.py"]),
-    imports = [
-        "src/python",
-    ],
-)
-
-# Zapped zapp
-zapp_binary(
-    name = "zapzap",
-    main = "src/python/zapp/__main__.py",
-    imports = [
-        "src/python",
-    ],
-)
diff --git a/projects/zapp/README.md b/projects/zapp/README.md
deleted file mode 100644
index 2c13035..0000000
--- a/projects/zapp/README.md
+++ /dev/null
@@ -1,123 +0,0 @@
-# Zapp
-<img align="right" src="zapp.jpg" alt="Spaceman spiff sets his zorcher to shake and bake" width=250>
-
-Zapp is a comically-named tool for making Python [zipapps](https://www.python.org/dev/peps/pep-0441/).
-
-Zipapps or zapps as we call them (hence the raygun theme) are packagings of Python programs into zip files. It's
-comparable to [Pex](https://github.com/pantsbuild/pex/), [Subpar](https://github.com/google/subpar/) and
-[Shiv](https://github.com/linkedin/shiv/) in intent, but shares the most with Subpar in particulars as like subpar Zapp
-is designed for use with Bazel (and is co-developed with appropriate Bazel build rules).
-
-## A quick overview of zipapps
-
-A Python zipapp is a file with two parts - a "plain" text file with a "shebang" specifying a Python interpreter, followed by a ZIP formatted archive after the newline.
-This is (for better or worse) a valid ZIP format archive, as the specification does not preclude prepended data.
-
-When Python encounters a zipapp, it assumes you meant `PYTHONPATH=your.zip <shebang> -m __main__`.
-See [the upstream docs](https://docs.python.org/3/library/zipapp.html#the-python-zip-application-archive-format).
-So not only must `zapp` generate a prefix script, it needs to insert a `__main__.py` that'll to your application.
-
-## A quick overview of zapp
-
-Zapp is really two artifacts - `zapp.bzl` which defines `rules_python` (`zapp_binary`, `zapp_test`) macros and implementations.
-These Bazel macros work together with the `zappc` "compiler" to make producing zapps from Bazel convenient.
-
-## A demo
-
-So let's give zapp a spin
-
-``` shellsession
-$ cd projects/zapp/examples
-$ cat BUILD
-load("//projects/zapp:zapp.bzl",
-     "zapp",
-     "zapp_binary",
-)
-
-# ...
-
-zapp_binary(
-    name = "hello_deps",
-    main = "hello.py",
-    deps = [
-        py_requirement("pyyaml"),
-    ]
-)
-
-```
-
-In this directory there's a couple of `hello_*` targets that are variously zapped. This one uses an external dependency via `rules_python`'s `py_requirement` machinery.
-
-Let's try `bazel build :hello_deps` to see how it gets zapped.
-
-``` shellsession
-$ bazel build :hello_deps
-bazel build :hello_deps
-INFO: Analyzed target //projects/zapp/example:hello_deps (22 packages loaded, 70 targets configured).
-INFO: Found 1 target...
-INFO: From Building zapp file //projects/zapp/example:hello_deps:
-{'manifest': {'entry_point': 'projects.zapp.example.hello',
-              'prelude_points': ['zapp.support.unpack:unpack_deps'],
-              'shebang': '/usr/bin/env python3',
-              'sources': {'__init__.py': None,
-                          'projects/__init__.py': None,
-                          'projects/zapp/__init__.py': None,
-                          'projects/zapp/example/__init__.py': None,
-                          'projects/zapp/example/hello.py': 'projects/zapp/example/hello.py',
-                          'zapp/__init__.py': None,
-                          'zapp/manifest.json': 'bazel-out/k8-fastbuild/bin/projects/zapp/example/hello_deps.zapp-manifest.json',
-                          'zapp/support/__init__.py': None,
-                          'zapp/support/manifest.py': 'projects/zapp/src/python/zapp/support/manifest.py',
-                          'zapp/support/unpack.py': 'projects/zapp/src/python/zapp/support/unpack.py'},
-              'wheels': {'PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl':
-                          {'hashes': [],
-                           'source': 'external/arrdem_source_pypi/pypi__pyyaml/PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl'}},
-              'zip_safe': True},
- 'opts': {'debug': True,
-          'manifest': 'bazel-out/k8-fastbuild/bin/projects/zapp/example/hello_deps.zapp-manifest.json',
-          'output': 'bazel-out/k8-fastbuild/bin/projects/zapp/example/hello_deps'}}
-Target //projects/zapp/example:hello_deps up-to-date:
-  bazel-bin/projects/zapp/example/hello_deps
-INFO: Elapsed time: 0.497s, Critical Path: 0.13s
-INFO: 8 processes: 7 internal, 1 linux-sandbox.
-INFO: Build completed successfully, 8 total actions
-```
-
-Here, I've got the `zapp` compiler configured to debug what it's doing.
-This is a bit unusual, but it's convenient for peeking under the hood.
-
-The manifest which `zapp` consumes describes the relocation of files (and wheels, more on that in a bit) from the Bazel source tree per python `import = [...]` specifiers to locations in the container/logical filesystem within the zip archive.
-
-We can see that the actual `hello.py` file (known as `projects/zapp/hello.py` within the repo) is being mapped into the zip archive without relocation.
-
-We can also see that a `PyYAML` wheel is marked for inclusion in the archive.
-
-If we run the produced zipapp -
-
-``` shellsession
-$ bazel run :hello_deps
-INFO: Analyzed target //projects/zapp/example:hello_deps (0 packages loaded, 0 targets configured).
-INFO: Found 1 target...
-Target //projects/zapp/example:hello_deps up-to-date:
-  bazel-bin/projects/zapp/example/hello_deps
-INFO: Elapsed time: 0.068s, Critical Path: 0.00s
-INFO: 1 process: 1 internal.
-INFO: Build completed successfully, 1 total action
-INFO: Build completed successfully, 1 total action
- - /home/arrdem/.cache/zapp/wheels/PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl
- - /home/arrdem/.cache/bazel/_bazel_arrdem/6259d2555f41e1db0292a7d7f00f78ca/execroot/arrdem_source/bazel-out/k8-fastbuild/bin/projects/zapp/example/hello_deps
- - /usr/lib/python39.zip
- - /usr/lib/python3.9
- - /usr/lib/python3.9/lib-dynload
- - /home/arrdem/.virtualenvs/source/lib/python3.9/site-packages
-hello, world!
-I have YAML! and nothing to do with it. /home/arrdem/.cache/zapp/wheels/PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl/yaml/__init__.py
-```
-
-Here we can see that zapp when executed unpacked the wheel into a cache, inserted that cached wheel into the `sys.path`, and correctly delegated to our `hello.py` script, which was able to `import yaml` from the packaged wheel! 🎉
-
-## License
-
-Copyright Reid 'arrdem' McKenzie August 2021.
-
-Published under the terms of the MIT license.
diff --git a/projects/zapp/example/BUILD b/projects/zapp/example/BUILD
deleted file mode 100644
index d9c91a6..0000000
--- a/projects/zapp/example/BUILD
+++ /dev/null
@@ -1,18 +0,0 @@
-load("//projects/zapp:zapp.bzl",
-     "zapp_binary",
-)
-
-zapp_binary(
-    name = "hello_script",
-    main = "hello.py",
-    # entry_point is inferred from main =
-)
-
-zapp_binary(
-    name = "hello_deps",
-    main = "hello.py",
-    # deps also get zapped via their underlying wheels
-    deps = [
-        py_requirement("pyyaml"),
-    ]
-)
diff --git a/projects/zapp/example/hello.py b/projects/zapp/example/hello.py
deleted file mode 100644
index 6f8db37..0000000
--- a/projects/zapp/example/hello.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import sys
-
-
-def main():
-    for e in sys.path:
-        print(" -", e)
-
-    print("hello, world!")
-
-    try:
-        import yaml
-        print("I have YAML! and nothing to do with it.", yaml.__file__)
-    except ImportError:
-        print("Don't have YAML.")
-
-
-if __name__ == "__main__":
-    main()
diff --git a/projects/zapp/src/python/zapp/__main__.py b/projects/zapp/src/python/zapp/__main__.py
deleted file mode 100644
index 7114c14..0000000
--- a/projects/zapp/src/python/zapp/__main__.py
+++ /dev/null
@@ -1,133 +0,0 @@
-"""
-The Zapp compiler.
-"""
-
-import argparse
-import io
-import json
-import os
-import sys
-import zipfile
-import pathlib
-import stat
-
-parser = argparse.ArgumentParser(description="The (bootstrap) Zapp compiler")
-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("manifest", help="The (JSON) manifest")
-
-
-MAIN_TEMPLATE = """\
-# -*- coding: utf-8 -*-
-
-\"\"\"Zapp-generated __main__\""\"
-
-from importlib import import_module
-# FIXME: This is absolutely implementation details.
-# Execing would be somewhat nicer
-from runpy import _run_module_as_main
-
-for script in {scripts!r}:
-    print(script)
-    mod, sep, fn = script.partition(':')
-    mod_ok = all(part.isidentifier() for part in mod.split('.'))
-    fn_ok = all(part.isidentifier() for part in fn.split('.'))
-
-    if not mod_ok:
-        raise RuntimeError("Invalid module reference {{!r}}".format(mod))
-    if fn and not fn_ok:
-        raise RuntimeError("Invalid function reference {{!r}}".format(fn))
-
-    if mod and fn and False:
-        mod = import_module(mod)
-        getattr(mod, fn)()
-    else:
-        _run_module_as_main(mod)
-"""
-
-
-def make_dunder_main(manifest):
-    """Generate a __main__.py file for the given manifest."""
-
-    prelude = manifest.get("prelude_points", [])
-    main = manifest.get("entry_point")
-    scripts = prelude + [main]
-    return MAIN_TEMPLATE.format(**locals())
-
-
-def dir_walk_prefixes(path):
-    """Helper. Walk all slices of a path."""
-
-    segments = []
-    yield ""
-    for segment in path.split("/"):
-        segments.append(segment)
-        yield os.path.join(*segments)
-
-
-def generate_dunder_inits(manifest):
-    """Hack the manifest to insert __init__ files as needed."""
-
-    sources = manifest["sources"]
-
-    for input_file in list(sources.keys()):
-        for path in dir_walk_prefixes(os.path.dirname(input_file)):
-            init_file = os.path.join(path, "__init__.py")
-            if init_file not in sources:
-                sources[init_file] = ""
-
-    return manifest
-
-
-def generate_manifest(opts, manifest):
-    """Insert the manifest.json file."""
-
-    manifest["sources"]["zapp/manifest.json"] = opts.manifest
-
-    return manifest
-
-
-def main():
-    opts, args = parser.parse_known_args()
-
-    with open(opts.manifest) as fp:
-        manifest = json.load(fp)
-
-    manifest = generate_manifest(opts, manifest)
-    # Patch the manifest to insert needed __init__ files
-    # NOTE: This has to be the LAST thing we do
-    manifest = generate_dunder_inits(manifest)
-
-    if opts.debug:
-        from pprint import pprint
-        pprint({
-            "opts": {k: getattr(opts, k) for k in dir(opts) if not k.startswith("_")},
-            "manifest": manifest
-        })
-
-    with open(opts.output, 'w') as zapp:
-        shebang = "#!" + manifest["shebang"] + "\n"
-        zapp.write(shebang)
-
-    # Now we're gonna build the zapp from the manifest
-    with zipfile.ZipFile(opts.output, 'a') as zapp:
-
-        # Append the __main__.py generated record
-        zapp.writestr("__main__.py", make_dunder_main(manifest))
-
-        # Append user-specified sources
-        for dest, src in manifest["sources"].items():
-            if src == "":
-                zapp.writestr(dest, "")
-            else:
-                zapp.write(src, dest)
-
-        # Append user-specified libraries
-        # FIXME
-
-    zapp = pathlib.Path(opts.output)
-    zapp.chmod(zapp.stat().st_mode | stat.S_IEXEC)
-
-
-if __name__ == "__main__" or 1:
-    main()
diff --git a/projects/zapp/src/python/zapp/compiler/__main__.py b/projects/zapp/src/python/zapp/compiler/__main__.py
deleted file mode 100644
index 7e31000..0000000
--- a/projects/zapp/src/python/zapp/compiler/__main__.py
+++ /dev/null
@@ -1,153 +0,0 @@
-"""
-The Zapp compiler.
-"""
-
-import argparse
-import io
-import json
-import os
-import sys
-import zipfile
-import pathlib
-import stat
-
-parser = argparse.ArgumentParser(description="The (bootstrap) Zapp compiler")
-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("manifest", help="The (JSON) manifest")
-
-
-MAIN_TEMPLATE = """\
-# -*- coding: utf-8 -*-
-
-\"\"\"Zapp-generated __main__\""\"
-
-from importlib import import_module
-import os
-import sys
-# FIXME: This is absolutely implementation details.
-# Execing would be somewhat nicer
-from runpy import _run_module_as_main
-
-for script in {scripts!r}:
-    mod, sep, fn = script.partition(':')
-    mod_ok = all(part.isidentifier() for part in mod.split('.'))
-    fn_ok = all(part.isidentifier() for part in fn.split('.'))
-
-    if not mod_ok:
-        raise RuntimeError("Invalid module reference {{!r}}".format(mod))
-    if fn and not fn_ok:
-        raise RuntimeError("Invalid function reference {{!r}}".format(fn))
-
-    if mod and fn:
-        mod = import_module(mod)
-        getattr(mod, fn)()
-    else:
-        _run_module_as_main(mod)
-"""
-
-
-def make_dunder_main(manifest):
-    """Generate a __main__.py file for the given manifest."""
-
-    prelude = manifest.get("prelude_points", [])
-    main = manifest.get("entry_point")
-    scripts = prelude + [main]
-    return MAIN_TEMPLATE.format(**locals())
-
-def dir_walk_prefixes(path):
-    """Helper. Walk all slices of a path."""
-
-    segments = []
-    yield ""
-    for segment in path.split("/"):
-        segments.append(segment)
-        yield os.path.join(*segments)
-
-
-def generate_dunder_inits(manifest):
-    """Hack the manifest to insert __init__ files as needed."""
-
-    sources = manifest["sources"]
-
-    for input_file in list(sources.keys()):
-        for path in dir_walk_prefixes(os.path.dirname(input_file)):
-            init_file = os.path.join(path, "__init__.py")
-            if init_file not in sources:
-                sources[init_file] = None
-
-    return manifest
-
-
-def insert_manifest_json(opts, manifest):
-    """Insert the manifest.json file."""
-
-    manifest["sources"]["zapp/manifest.json"] = opts.manifest
-
-    return manifest
-
-
-def enable_unzipping(manifest):
-    """Inject unzipping behavior as needed."""
-
-    if manifest["wheels"]:
-        manifest["prelude_points"].append("zapp.support.unpack:unpack_deps")
-
-    # FIXME:
-    # if not manifest["zip_safe"]:
-    # enable a similar injection for unzipping
-
-    return manifest
-
-
-def main():
-    opts, args = parser.parse_known_args()
-
-    with open(opts.manifest) as fp:
-        manifest = json.load(fp)
-
-    manifest = insert_manifest_json(opts, manifest)
-    manifest = enable_unzipping(manifest)
-    # Patch the manifest to insert needed __init__ files
-    # NOTE: This has to be the LAST thing we do
-    manifest = generate_dunder_inits(manifest)
-
-    if opts.debug:
-        from pprint import pprint
-        pprint({
-            "opts": {k: getattr(opts, k) for k in dir(opts) if not k.startswith("_")},
-            "manifest": manifest
-        })
-
-    with open(opts.output, 'w') as zapp:
-        shebang = "#!" + manifest["shebang"] + "\n"
-        zapp.write(shebang)
-
-    if "__main__.py" in manifest["sources"]:
-        print("Error: __main__.py conflict.", file=sys.stderr)
-        exit(1)
-
-    # Now we're gonna build the zapp from the manifest
-    with zipfile.ZipFile(opts.output, 'a') as zapp:
-
-        # Append the __main__.py generated record
-        zapp.writestr("__main__.py", make_dunder_main(manifest))
-
-        # Append user-specified sources
-        for dest, src in sorted(manifest["sources"].items(),
-                                key=lambda x: x[0]):
-            if src is None:
-                zapp.writestr(dest, "")
-            else:
-                zapp.write(src, dest)
-
-        # Append user-specified libraries
-        for whl, config in manifest["wheels"].items():
-            zapp.write(config["source"], ".deps/" + whl)
-
-    zapp = pathlib.Path(opts.output)
-    zapp.chmod(zapp.stat().st_mode | stat.S_IEXEC)
-
-
-if __name__ == "__main__" or 1:
-    main()
diff --git a/projects/zapp/src/python/zapp/support/manifest.py b/projects/zapp/src/python/zapp/support/manifest.py
deleted file mode 100644
index 0de064f..0000000
--- a/projects/zapp/src/python/zapp/support/manifest.py
+++ /dev/null
@@ -1,17 +0,0 @@
-"""The Zapp runtime manifest API."""
-
-from copy import deepcopy
-from importlib.resources import open_text
-import json
-
-with open_text("zapp", "manifest.json") as fp:
-    _MANIFEST = json.load(fp)
-
-
-def manifest():
-    """Return (a copy) of the runtime manifest."""
-
-    return deepcopy(_MANIFEST)
-
-
-__all__ = ["manifest"]
diff --git a/projects/zapp/src/python/zapp/support/unpack.py b/projects/zapp/src/python/zapp/support/unpack.py
deleted file mode 100644
index 41d94d2..0000000
--- a/projects/zapp/src/python/zapp/support/unpack.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""Conditionally unpack a zapp (and its deps)."""
-
-import sys
-import os
-from pathlib import Path
-from zipfile import ZipFile
-
-from .manifest import manifest
-
-
-MANIFEST = manifest()
-
-
-def cache_root() -> Path:
-    return Path(os.path.join(os.path.expanduser("~"))) / ".cache" / "zapp"
-
-
-def cache_wheel_root():
-    return cache_root() / "wheels"
-
-
-def cache_wheel_path(wheel: str) -> Path:
-    return cache_wheel_root() / wheel
-
-
-def cache_zapp_root():
-    return cache_root() / "zapps"
-
-
-def cache_zapp_path(fingerprint):
-    return cache_zapp_root() / fingerprint
-
-
-def unpack_deps():
-    """Unpack deps, populating and updating the host's cache."""
-
-    # Create the cache dir as needed
-    cache_wheel_root().mkdir(parents=True, exist_ok=True)
-
-    # For each wheel, touch the existing cached wheel or unpack this one.
-    with ZipFile(sys.argv[0], "r") as zf:
-        for whl, config in MANIFEST["wheels"].items():
-            cached_whl = cache_wheel_path(whl)
-            if cached_whl.exists():
-                cached_whl.touch()
-
-            else:
-                with open(cached_whl, "wb") as of:
-                    of.write(zf.read(".deps/" + whl))
-
-            sys.path.insert(0, str(cached_whl))
-
-
-def main():
-    """Inspect the manifest."""
-
-    unpack_deps()
diff --git a/projects/zapp/zapp.bzl b/projects/zapp/zapp.bzl
deleted file mode 100644
index 05df346..0000000
--- a/projects/zapp/zapp.bzl
+++ /dev/null
@@ -1,249 +0,0 @@
-"""
-An implementation of driving zappc from Bazel.
-"""
-
-
-load("@rules_python//python:defs.bzl", "py_library", "py_binary")
-
-
-DEFAULT_COMPILER = "//projects/zapp:zappc"
-DEFAULT_RUNTIME  = "//projects/zapp:zapp_support"
-
-
-def _store_path(path, ctx, imports):
-    """Given a path, prepend the workspace name as the zappent directory"""
-
-    # It feels like there should be an easier, less fragile way.
-    if path.startswith("../"):
-        # External workspace, for example
-        # '../protobuf/python/google/protobuf/any_pb2.py'
-        stored_path = path[len("../"):]
-
-    elif path.startswith("external/"):
-        # External workspace, for example
-        # 'external/protobuf/python/__init__.py'
-        stored_path = path[len("external/"):]
-
-    else:
-        # Main workspace, for example 'mypackage/main.py'
-        # stored_path = ctx.workspace_name + "/" + path
-        stored_path = path
-
-    matching_prefix = None
-    for i in imports:
-        if stored_path.startswith(i):
-            stored_path = stored_path[len(i):]
-            matching_prefix = i
-            break
-
-    stored_path = stored_path.lstrip("/")
-
-    return stored_path
-
-
-def _check_script(point, sources_map):
-    """Check that a given 'script' (eg. module:fn ref.) maps to a file in sources."""
-
-    fname = point.split(":")[0].replace(".", "/") + ".py"
-    if fname not in sources_map:
-        fail("Point %s (%s) is not a known source!" % (fname, sources_map))
-
-
-def _zapp_impl(ctx):
-    """Implementation of zapp() rule"""
-
-    # TODO: Take wheels and generate a .deps/ tree of them, filtering whl/pypi source files from srcs
-    whls = []
-    for lib in ctx.attr.wheels:
-        for f in lib.data_runfiles.files.to_list():
-            whls.append(f)
-
-    # TODO: also handle ctx.attr.src.data_runfiles.symlinks
-    srcs = [
-        f for f in ctx.attr.src.default_runfiles.files.to_list()
-    ]
-
-    # Find the list of directories to add to sys
-    import_roots = [
-        r.replace(ctx.workspace_name + "/", "", 1)
-        for r in ctx.attr.src[PyInfo].imports.to_list()
-    ]
-    for r0 in import_roots:
-        for r1 in import_roots:
-            if r0 == r1:
-                continue
-            elif r0.startswith(r1):
-                fail("Import root conflict between %s and %s" % r0, r1)
-
-    # Dealing with main
-    main_py_file = ctx.files.main
-    main_py_ref = ctx.attr.entry_point
-    if main_py_ref and main_py_file:
-        fail("Only one of `main` or `entry_point` should be specified")
-    elif main_py_ref:
-        # Compute a main module
-        main_py_file = main_py_ref.split(":")[0].replace(".", "/") + ".py"
-    elif main_py_file:
-        # Compute a main module reference
-        if len(main_py_file) > 1:
-            fail("Expected exactly one .py file, found these: %s" % main_py_file)
-        main_py_file = main_py_file[0]
-        if main_py_file not in ctx.attr.src.data_runfiles.files.to_list():
-            fail("Main entry point [%s] not listed in srcs" % main_py_file, "main")
-
-        # Compute the -m <> equivalent for the 'main' module
-        main_py_ref = _store_path(main_py_file.path, ctx, import_roots).replace(".py", "").replace("/", ".")
-
-    # Make a manifest of files to store in the .zapp file.  The
-    # runfiles manifest is not quite right, so we make our own.
-    sources_map = {}
-
-    # Now add the regular (source and generated) files
-    for input_file in srcs:
-        stored_path = _store_path(input_file.short_path, ctx, import_roots)
-        if stored_path:
-            local_path = input_file.path
-            if stored_path in sources_map and sources_map[stored_path] != '':
-                fail("File path conflict between %s and %s" % sources_map[stored_path], local_path)
-
-            sources_map[stored_path] = local_path
-
-    _check_script(main_py_ref, sources_map)
-    for p in ctx.attr.prelude_points:
-        _check_script(p, sources_map)
-
-    if "__main__.py" in sources_map:
-        fail("__main__.py conflict:",
-             sources_map["__main__.py"],
-             "conflicts with required generated __main__.py")
-
-    # Write the list to the manifest file
-    manifest_file = ctx.actions.declare_file(ctx.label.name + ".zapp-manifest.json")
-    ctx.actions.write(
-        output = manifest_file,
-        content = json.encode({
-            "shebang": ctx.attr.shebang,
-            "sources": sources_map,
-            "zip_safe": ctx.attr.zip_safe,
-            "prelude_points": ctx.attr.prelude_points,
-            "entry_point": main_py_ref,
-            "wheels": {w.path.split("/")[-1]: {"hashes": [], "source": w.path} for w in whls},
-        }),
-        is_executable = False,
-    )
-
-    # Run compiler
-    ctx.actions.run(
-        inputs = [
-            manifest_file,
-        ] + srcs + whls,
-        tools = [],
-        outputs = [ctx.outputs.executable],
-        progress_message = "Building zapp file %s" % ctx.label,
-        executable = ctx.executable.compiler,
-        arguments = [
-            "--debug",
-            "-o", ctx.outputs.executable.path,
-            manifest_file.path
-        ],
-        mnemonic = "PythonCompile",
-        use_default_shell_env = True,
-    )
-
-    # .zapp file itself has no runfiles and no providers
-    return []
-
-
-zapp = rule(
-    attrs = {
-        "src": attr.label(mandatory = True),
-        "main": attr.label(allow_single_file = True),
-        "wheels": attr.label_list(),
-        "entry_point": attr.string(),
-        "prelude_points": attr.string_list(),
-        "compiler": attr.label(
-            default = Label(DEFAULT_COMPILER),
-            executable = True,
-            cfg = "host",
-        ),
-        "shebang": attr.string(default = "/usr/bin/env python3"),
-        "zip_safe": attr.bool(default = True),
-        "root_import": attr.bool(default = False),
-    },
-    executable = True,
-    implementation = _zapp_impl,
-)
-
-
-def zapp_binary(name,
-                main=None,
-                entry_point=None,
-                prelude_points=[],
-                deps=[],
-                imports=[],
-                test=False,
-                compiler=None,
-                zip_safe=True,
-                **kwargs):
-    """A self-contained, single-file Python program, with a .zapp file extension.
-
-    Args:
-      Same as py_binary, but accepts some extra args -
-
-      entry_point:
-        The script to run as the main.
-
-      prelude_points:
-        Additional scripts (zapp middlware) to run before main.
-
-      compiler:
-        Lable identifying the zapp compiler to use. You shouldn't need to change this.
-
-      zip_safe:
-        Whether to import Python code and read datafiles directly from the zip
-        archive. Otherwise, if False, all files are extracted to a temporary
-        directory on disk each time the zapp file executes.
-    """
-
-    srcs = kwargs.pop("srcs", [])
-    if main and main not in srcs:
-        srcs.append(main)
-
-    whls = []
-    src_deps = []
-    for d in deps:
-        if d.find("//pypi__") != -1:
-            whls.append(d + ":whl")
-        else:
-            src_deps.append(d)
-
-    py_library(
-        name = name + ".whls",
-        data = whls,
-    )
-
-    py_library(
-        name = name + ".lib",
-        srcs = srcs,
-        deps = (src_deps or []) + [DEFAULT_RUNTIME],
-        imports = imports,
-        **kwargs
-    )
-
-    zapp(
-        name = name,
-        src = name + ".lib",
-        compiler = compiler,
-        main = main,
-        entry_point = entry_point,
-        prelude_points = prelude_points,
-        zip_safe = zip_safe,
-        wheels = [name + ".whls"],
-    )
-
-
-def zapp_test(name, **kwargs):
-    """Same as zapp_binary, just sets the test=True bit."""
-
-    kwargs.pop("test")
-    zapp_binary(name, test=True, **kwargs)
diff --git a/projects/zapp/zapp.jpg b/projects/zapp/zapp.jpg
deleted file mode 100644
index 29e5286..0000000
Binary files a/projects/zapp/zapp.jpg and /dev/null differ
diff --git a/tools/build_rules/prelude_bazel b/tools/build_rules/prelude_bazel
index 4d8b204..c0a8f55 100644
--- a/tools/build_rules/prelude_bazel
+++ b/tools/build_rules/prelude_bazel
@@ -25,3 +25,7 @@ load("//tools/build_rules:cp.bzl",
 load("//tools/build_rules:webp.bzl",
      "webp_image",
 )
+
+load("@rules_zapp//zapp:zapp.bzl",
+     "zapp_binary",
+)