Skip to content

Commit

Permalink
bootstrap: add initial implementation
Browse files Browse the repository at this point in the history
Has some rough edges, but seems to be working.

Signed-off-by: Filipe Laíns <[email protected]>
  • Loading branch information
FFY00 committed Oct 24, 2021
1 parent dea3859 commit 6f1205a
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 0 deletions.
126 changes: 126 additions & 0 deletions bootstrap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# SPDX-License-Identifier: MIT

import logging
import os
import pathlib
import subprocess
import sys
import tempfile

from collections.abc import Iterable, Mapping, Sequence
from typing import NamedTuple, Optional, Tuple, Union


class Package(NamedTuple):
srcdir: pathlib.Path
module_path: pathlib.Path


ROOT = pathlib.Path(__file__).parent.parent
TMPDIR = ROOT / '.bootstrap'
EXTERNAL = ROOT / 'external'

PACKAGES = {
# what we need
'build': Package(
EXTERNAL / 'build',
EXTERNAL / 'build' / 'src',
),
'installer': Package(
EXTERNAL / 'installer',
EXTERNAL / 'installer' / 'src',
),
# dependencies
'setuptools': Package(
EXTERNAL / 'setuptools',
EXTERNAL / 'setuptools',
),
'flit': Package(
EXTERNAL / 'flit',
EXTERNAL / 'flit',
),
'flit_core': Package(
EXTERNAL / 'flit' / 'flit_core',
EXTERNAL / 'flit' / 'flit_core',
),
'wheel': Package(
EXTERNAL / 'wheel',
EXTERNAL / 'wheel',
),
'tomli': Package(
EXTERNAL / 'tomli',
EXTERNAL / 'tomli',
),
'pep517': Package(
EXTERNAL / 'pep517',
EXTERNAL / 'pep517',
),
}

EXTRA_PATH = [str(package.module_path) for package in PACKAGES.values()]
PACKAGE_PATH_ENV = {
'PYTHONPATH': os.path.pathsep.join(EXTRA_PATH),
}


_logger = logging.getLogger(__name__)


# inject extra sources into sys.path and import what we need
sys.path = EXTRA_PATH + sys.path
import build # noqa: E402
import build.env # noqa: E402
import pep517 # noqa: E402


# not needed after https://github.com/pypa/build/pull/361
def create_isolated_env_venv_no_pip(path: str) -> Tuple[str, str]:
import venv

venv.EnvBuilder(symlinks=build.env._fs_supports_symlink()).create(path)
executable, script_dir, purelib = build.env._find_executable_and_scripts(path)

return executable, script_dir


def custom_runner(
cmd: Sequence[str],
cwd: Optional[str] = None,
extra_environ: Optional[Mapping[str, str]] = None,
) -> None:
extra_environ = dict(extra_environ) if extra_environ else {}
extra_environ.update(PACKAGE_PATH_ENV)
pep517.default_subprocess_runner(cmd, cwd, extra_environ)


def build_package(name: str, outdir: pathlib.Path) -> pathlib.Path:
_logger.info(f'Building {name}...')
srcdir = PACKAGES[name].srcdir
builder = build.ProjectBuilder(srcdir, runner=custom_runner)
wheel = builder.build('wheel', str(outdir))
return pathlib.Path(wheel)


def build_package_setuptools(
outdir: pathlib.Path,
wheel_whl: pathlib.Path,
) -> pathlib.Path:
# setuptools needs wheel installed
_logger.info('Building setuptools...')
srcdir = PACKAGES['setuptools'].srcdir
TMPDIR.mkdir(exist_ok=True)
with tempfile.TemporaryDirectory(prefix='setuptools-venv-', dir=TMPDIR) as venv_path:
executable, scripts_dir = create_isolated_env_venv_no_pip(venv_path)

# install wheel
subprocess.check_call(
[executable, '-m', 'installer', str(wheel_whl)],
env=os.environ | PACKAGE_PATH_ENV,
)

# build setuptools
builder = build.ProjectBuilder(srcdir, runner=custom_runner)
builder.python_executable = executable
builder.scripts_dir = scripts_dir
wheel = builder.build('wheel', str(outdir))
return pathlib.Path(wheel)
64 changes: 64 additions & 0 deletions bootstrap/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# SPDX-License-Identifier: MIT

import argparse
import json
import logging
import pathlib
import shutil
import sys

from typing import Dict, Optional
from collections.abc import Sequence

import bootstrap


def main_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument(
'--outdir',
'-o',
type=str,
default='dist',
help='output directory (defaults to `dist`)',
)
return parser


def main(cli_args: Sequence[str], prog: Optional[str] = None):
parser = main_parser()
if prog:
parser.prog = prog
args, unknown = parser.parse_known_args(cli_args)

logging.basicConfig(level=logging.INFO)

outdir = pathlib.Path(args.outdir).absolute()
if outdir.exists():
if not outdir.is_dir():
raise NotADirectoryError(f"{str(outdir)} exists and it's not a directory")
shutil.rmtree(outdir)
outdir.mkdir(parents=True)

artifacts: Dict[str, pathlib.Path] = {}
# build wheel first because setuptools needs it installed
artifacts['wheel'] = bootstrap.build_package('wheel', outdir)
# build setuptools via the custom builder
bootstrap.build_package_setuptools(outdir, artifacts['wheel'])
# build the rest
for package in bootstrap.PACKAGES:
if package in ('wheel', 'setuptools'):
continue
artifacts[package] = bootstrap.build_package(package, outdir)
# write artifact metadata
outdir.joinpath('artifacts.json').write_text(json.dumps({
package: path.name for package, path in artifacts.items()
}))

print(f'Written wheels to `{str(args.outdir)}`:' + ''.join(sorted(
f'\n\t{artifact.name}' for artifact in artifacts.values()
)))


if __name__ == '__main__':
main(sys.argv[1:], 'python -m bootstra.build')
12 changes: 12 additions & 0 deletions bootstrap/install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: MIT

import sys

import bootstrap # noqa: F401

# bootstrap injects the extra sources
import installer.__main__ # noqa: E402


if __name__ == '__main__':
installer.__main__.main(sys.argv[1:], 'python -m bootstrap.install')

0 comments on commit 6f1205a

Please sign in to comment.