Skip to content

Add bootstrap script/wheel module for flit_core #511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions doc/bootstrap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://pypi.org/project/build/>`_ and
`installer <https://pypi.org/project/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
<https://pypi.org/project/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 <https://pypi.org/project/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.
48 changes: 48 additions & 0 deletions flit_core/bootstrap_install.py
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 10 additions & 1 deletion flit_core/flit_core/tests/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions flit_core/flit_core/wheel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import argparse
from base64 import urlsafe_b64encode
import contextlib
from datetime import datetime
Expand All @@ -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
Expand Down Expand Up @@ -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()
3 changes: 3 additions & 0 deletions flit_core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ dynamic = ["version"]

[project.urls]
Source = "https://github.com/pypa/flit"

[tool.flit.sdist]
include = ["bootstrap_install.py", "build_dists.py"]