diff --git a/doc/bootstrap.rst b/doc/bootstrap.rst
index 02da8945..19d7d923 100644
--- a/doc/bootstrap.rst
+++ b/doc/bootstrap.rst
@@ -13,24 +13,29 @@ from source?
The key piece is ``flit_core``. This is a package which can build itself using
nothing except Python and the standard library. From an unpacked source archive,
-you can run ``python build_dists.py``, of which the crucial part is::
+you can make a wheel by running::
- from flit_core import buildapi
- whl_fname = buildapi.build_wheel('dist/')
- print(os.path.join('dist', whl_fname))
+ python -m flit_core.wheel
-This produces a ``.whl`` wheel file, which you can unzip into your
-``site-packages`` folder (or equivalent) to make ``flit_core`` available for
-building other packages. (You could also just copy ``flit_core`` from the
-source directory, but without the ``.dist-info`` folder, tools like pip won't
-know that it's installed.)
+And then you can install this wheel with the ``bootstrap_install.py`` script
+included in the sdist (or by unzipping it to the correct directory)::
+
+ # Install to site-packages for this Python:
+ python bootstrap_install.py dist/flit_core-*.whl
+
+ # Install somewhere else:
+ python bootstrap_install.py --installdir /path/to/site-packages dist/flit_core-*.whl
As of version 3.6, flit_core bundles the ``tomli`` TOML parser, to avoid a
dependency cycle. If you need to unbundle it, you will need to special-case
installing flit_core and/or tomli to get around that cycle.
-I recommend that you get the `build `_ and
-`installer `_ packages (and their
-dependencies) installed as the goal of the bootstrapping phase. These tools
-together can be used to install any other Python packages: ``build`` to create
-wheels and ``installer`` to install them.
+After ``flit_core``, I recommend that you get `installer
+`_ set up. You can use
+``python -m flit_core.wheel`` again to make a wheel, and then use installer
+itself (from the source directory) to install it.
+
+After that, you probably want to get `build `_
+and its dependencies installed as the goal of the bootstrapping phase. You can
+then use ``build`` to create wheels of any other Python packages, and
+``installer`` to install them.
diff --git a/flit_core/bootstrap_install.py b/flit_core/bootstrap_install.py
new file mode 100644
index 00000000..e04a007e
--- /dev/null
+++ b/flit_core/bootstrap_install.py
@@ -0,0 +1,48 @@
+"""Install flit_core without using any other tools.
+
+Normally, you would install flit_core with pip like any other Python package.
+This script is meant to help with 'bootstrapping' other packaging
+systems, where you may need flit_core to build other packaging tools.
+
+Use 'python -m flit_core.wheel' to make a wheel, then:
+
+ python bootstrap_install.py flit_core-3.6.0-py3-none-any.whl
+
+To install for something other than the Python running the script, pass a
+site-packages or equivalent directory with the --installdir option.
+"""
+import argparse
+import sys
+import sysconfig
+from pathlib import Path
+from zipfile import ZipFile
+
+def extract_wheel(whl_path, dest):
+ print("Installing to", dest)
+ with ZipFile(whl_path) as zf:
+ zf.extractall(dest)
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'wheel',
+ type=Path,
+ help=f'flit_core wheel to install (.whl file)',
+ )
+ purelib = Path(sysconfig.get_path('purelib')).resolve()
+ parser.add_argument(
+ '--installdir',
+ '-i',
+ type=Path,
+ default=purelib,
+ help=f'installdir directory (defaults to {purelib})',
+ )
+
+ args = parser.parse_args()
+
+ if not args.wheel.name.startswith('flit_core-'):
+ sys.exit("Use this script only for flit_core wheels")
+ if not args.installdir.is_dir():
+ sys.exit(f"{args.installdir} is not a directory")
+
+ extract_wheel(args.wheel, args.installdir)
diff --git a/flit_core/flit_core/tests/test_wheel.py b/flit_core/flit_core/tests/test_wheel.py
index 51d6def7..310f9c6c 100644
--- a/flit_core/flit_core/tests/test_wheel.py
+++ b/flit_core/flit_core/tests/test_wheel.py
@@ -3,7 +3,7 @@
from testpath import assert_isfile
-from flit_core.wheel import make_wheel_in
+from flit_core.wheel import make_wheel_in, main
samples_dir = Path(__file__).parent / 'samples'
@@ -31,6 +31,15 @@ def test_zero_timestamp(tmp_path, monkeypatch):
assert zf.getinfo('module1a.py').date_time == (1980, 1, 1, 0, 0, 0)
+def test_main(tmp_path):
+ main(['--outdir', str(tmp_path), str(samples_dir / 'pep621')])
+ wheels = list(tmp_path.glob('*.whl'))
+ assert len(wheels) == 1
+ # Minimum value for zip timestamps is 1980-1-1
+ with ZipFile(wheels[0], 'r') as zf:
+ assert 'module1a.py' in zf.namelist()
+
+
def test_data_dir(tmp_path):
info = make_wheel_in(samples_dir / 'with_data_dir' / 'pyproject.toml', tmp_path)
assert_isfile(info.file)
diff --git a/flit_core/flit_core/wheel.py b/flit_core/flit_core/wheel.py
index d921f7cc..08cb70ae 100644
--- a/flit_core/flit_core/wheel.py
+++ b/flit_core/flit_core/wheel.py
@@ -1,3 +1,4 @@
+import argparse
from base64 import urlsafe_b64encode
import contextlib
from datetime import datetime
@@ -8,6 +9,7 @@
import os.path as osp
import stat
import tempfile
+from pathlib import Path
from types import SimpleNamespace
from typing import Optional
import zipfile
@@ -228,3 +230,30 @@ def make_wheel_in(ini_path, wheel_directory, editable=False):
log.info("Built wheel: %s", wheel_path)
return SimpleNamespace(builder=wb, file=wheel_path)
+
+
+def main(argv=None):
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'srcdir',
+ type=Path,
+ nargs='?',
+ default=Path.cwd(),
+ help='source directory (defaults to current directory)',
+ )
+
+ parser.add_argument(
+ '--outdir',
+ '-o',
+ help='output directory (defaults to {srcdir}/dist)',
+ )
+ args = parser.parse_args(argv)
+ outdir = args.srcdir / 'dist' if args.outdir is None else Path(args.outdir)
+ print("Building wheel from", args.srcdir)
+ pyproj_toml = args.srcdir / 'pyproject.toml'
+ outdir.mkdir(parents=True, exist_ok=True)
+ info = make_wheel_in(pyproj_toml, outdir)
+ print("Wheel built", outdir / info.file.name)
+
+if __name__ == "__main__":
+ main()
diff --git a/flit_core/pyproject.toml b/flit_core/pyproject.toml
index ca3f46b2..3652f9a3 100644
--- a/flit_core/pyproject.toml
+++ b/flit_core/pyproject.toml
@@ -19,3 +19,6 @@ dynamic = ["version"]
[project.urls]
Source = "https://github.com/pypa/flit"
+
+[tool.flit.sdist]
+include = ["bootstrap_install.py", "build_dists.py"]