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 22d6125
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 0 deletions.
137 changes: 137 additions & 0 deletions bootstrap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# SPDX-License-Identifier: MIT

import atexit
import logging
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile

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


class Package(NamedTuple):
srcdir: pathlib.Path
module_path: pathlib.Path
module_sources: Collection[str]


ROOT = pathlib.Path(__file__).parent.parent
WORKING_DIR = ROOT / '.bootstrap'
MODULES = WORKING_DIR / 'modules'
TMPDIR = WORKING_DIR / 'tmp'
EXTERNAL = ROOT / 'external'

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

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


# copy sources to module dir and inject it into sys.path
MODULES.mkdir(parents=True)
for package in PACKAGES.values():
for path in package.module_sources:
shutil.copytree(
package.module_path / path,
MODULES / path,
)
atexit.register(shutil.rmtree, MODULES)
sys.path.insert(0, str(MODULES))


# import what we need from the inject modules
import build # noqa: E402
import build.env # noqa: E402
import pep517 # noqa: E402


_logger = logging.getLogger(__name__)


def log(msg):
_logger.info(msg)


# 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 install_package_egginfo(name: str) -> None:
package = PACKAGES[name]
subprocess.check_call(
[sys.executable, 'setup.py', 'egg_info'],
env=os.environ | PACKAGE_PATH_ENV,
cwd=package.srcdir,
)
shutil.copytree(
package.module_path / f'{name}.egg-info',
MODULES / f'{name}.egg-info',
)


def build_package(name: str, outdir: pathlib.Path) -> pathlib.Path:
log(f'Building {name}...')
srcdir = PACKAGES[name].srcdir
builder = build.ProjectBuilder(str(srcdir), runner=custom_runner)
wheel = builder.build('wheel', str(outdir))
return pathlib.Path(wheel)
63 changes: 63 additions & 0 deletions bootstrap/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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)

# first we install the setuptools egg-info, then wheel do that setuptools
# thinks they are installed
bootstrap.install_package_egginfo('setuptools')
bootstrap.install_package_egginfo('wheel')
# then we can build everything
artifacts = {
package: bootstrap.build_package(package, outdir)
for package in bootstrap.PACKAGES
}
# and finally, we generate the 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 22d6125

Please sign in to comment.