Skip to content
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

Pull out high-level interfaces as an extended example #83

Merged
merged 10 commits into from
Sep 3, 2020
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
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ jobs:
cache: pip

install:
- pip install tox tox-venv
- pip install -U virtualenv
- pip install tox

script: tox
48 changes: 11 additions & 37 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,21 @@ provides:
the current process.
- Fallbacks for the optional hooks, so that frontends can call the hooks without
checking which are defined.
- Higher-level functions which install the build dependencies into a
temporary environment and build a wheel/sdist using them.
- Functions to load the build system table from ``pyproject.toml``, with
optional fallback to setuptools.

Run the tests with ``pytest`` or `tox <https://pypi.org/project/tox>`_.

High level usage, with build requirements handled:
Usage:

.. code-block:: python

import os
from pep517.envbuild import build_wheel, build_sdist
from pep517 import Pep517HookCaller
from pep517.pyproject import load_system

src = 'path/to/source' # Folder containing 'pyproject.toml'
destination = 'also/a/folder'
whl_filename = build_wheel(src, destination)
assert os.path.isfile(os.path.join(destination, whl_filename))

targz_filename = build_sdist(src, destination)
assert os.path.isfile(os.path.join(destination, targz_filename))

Lower level usage—you are responsible for ensuring build requirements are
available:

.. code-block:: python

import os
import toml
from pep517.wrappers import Pep517HookCaller

src = 'path/to/source' # Folder containing 'pyproject.toml'
with open(os.path.join(src, 'pyproject.toml')) as f:
build_sys = toml.load(f)['build-system']
build_sys = load_system(src)

print(build_sys['requires']) # List of static requirements

Expand All @@ -57,18 +40,9 @@ available:
whl_filename = hooks.build_wheel(destination, config_options)
assert os.path.isfile(os.path.join(destination, whl_filename))

To test the build backend for a project, run in a system shell:

.. code-block:: shell

python3 -m pep517.check path/to/source # source dir containing pyproject.toml

To build a backend into source and/or binary distributions, run in a shell:

.. code-block:: shell

python -m pep517.build path/to/source # source dir containing pyproject.toml
The caller is responsible for installing build dependencies.
The static requirements should be installed before trying to call any hooks.

This 'build' module should be considered experimental while the PyPA `decides
on the best place for this functionality
<https://github.com/pypa/packaging-problems/issues/219>`_.
The ``buildtool_demo`` package in this repository gives a more complete
example of how to use the hooks. This is an example, and doesn't get installed
with the ``pep517`` package.
5 changes: 5 additions & 0 deletions examples/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Examples of using the ``pep517`` library.

* ``buildtool`` is about the simplest possible PEP 517 frontend, which can use
PEP 517 backends to build packages. It installs build dependencies into a
temporary but non-isolated environment using pip.
Empty file added examples/buildtool/__init__.py
Empty file.
46 changes: 2 additions & 44 deletions pep517/build.py → examples/buildtool/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,16 @@
import argparse
import logging
import os
import toml
import shutil

from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
from pep517 import Pep517HookCaller
from pep517.pyproject import load_system, validate_system
from .dirtools import tempdir, mkdir_p
from .compat import FileNotFoundError

log = logging.getLogger(__name__)


def validate_system(system):
"""
Ensure build system has the requisite fields.
"""
required = {'requires', 'build-backend'}
if not (required <= set(system)):
message = "Missing required fields: {missing}".format(
missing=required-set(system),
)
raise ValueError(message)


def load_system(source_dir):
"""
Load the build system from a source dir (pyproject.toml).
"""
pyproject = os.path.join(source_dir, 'pyproject.toml')
with open(pyproject) as f:
pyproject_data = toml.load(f)
return pyproject_data['build-system']


def compat_system(source_dir):
"""
Given a source dir, attempt to get a build system backend
and requirements from pyproject.toml. Fallback to
setuptools but only if the file was not found or a build
system was not indicated.
"""
try:
system = load_system(source_dir)
except (FileNotFoundError, KeyError):
system = {}
system.setdefault(
'build-backend',
'setuptools.build_meta:__legacy__',
)
system.setdefault('requires', ['setuptools', 'wheel'])
return system


def _do_build(hooks, env, dist, dest):
get_requires_name = 'get_requires_for_build_{dist}'.format(**locals())
get_requires = getattr(hooks, get_requires_name)
Expand Down
2 changes: 1 addition & 1 deletion pep517/check.py → examples/buildtool/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from .colorlog import enable_colourful_output
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
from pep517 import Pep517HookCaller

log = logging.getLogger(__name__)

Expand Down
File renamed without changes.
File renamed without changes.
37 changes: 36 additions & 1 deletion pep517/envbuild.py → examples/buildtool/envbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import sys
from sysconfig import get_paths
from tempfile import mkdtemp
import threading

from .wrappers import Pep517HookCaller, LoggerWrapper
from pep517 import Pep517HookCaller

log = logging.getLogger(__name__)

Expand All @@ -26,6 +27,40 @@ def _load_pyproject(source_dir):
)


class LoggerWrapper(threading.Thread):
"""
Read messages from a pipe and redirect them
to a logger (see python's logging module).
"""

def __init__(self, logger, level):
threading.Thread.__init__(self)
self.daemon = True

self.logger = logger
self.level = level

# create the pipe and reader
self.fd_read, self.fd_write = os.pipe()
self.reader = os.fdopen(self.fd_read)

self.start()

def fileno(self):
return self.fd_write

@staticmethod
def remove_newline(msg):
return msg[:-1] if msg.endswith(os.linesep) else msg

def run(self):
for line in self.reader:
self._write(self.remove_newline(line))

def _write(self, message):
self.logger.log(self.level, message)


class BuildEnvironment(object):
"""Context manager to install build deps in a simple temporary environment

Expand Down
4 changes: 2 additions & 2 deletions pep517/meta.py → examples/buildtool/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
from zipp import Path

from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller, quiet_subprocess_runner
from pep517 import Pep517HookCaller, quiet_subprocess_runner
from pep517.pyproject import validate_system, load_system, compat_system
from .dirtools import tempdir, mkdir_p, dir_to_zipfile
from .build import validate_system, load_system, compat_system

log = logging.getLogger(__name__)

Expand Down
Empty file.
62 changes: 62 additions & 0 deletions examples/buildtool/tests/samples/buildsys_pkgs/buildsys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""This is a very stupid backend for testing purposes.

Don't use this for any real code.
"""

from glob import glob
from os.path import join as pjoin
import shutil
import tarfile
from zipfile import ZipFile


def get_requires_for_build_wheel(config_settings):
return ['wheelwright']


def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
for distinfo in glob('*.dist-info'):
shutil.copytree(distinfo, pjoin(metadata_directory, distinfo))


def prepare_build_wheel_files(build_directory, config_settings):
shutil.copy('pyproject.toml', build_directory)
for pyfile in glob('*.py'):
shutil.copy(pyfile, build_directory)
for distinfo in glob('*.dist-info'):
shutil.copytree(distinfo, pjoin(build_directory, distinfo))


def build_wheel(wheel_directory, config_settings, metadata_directory=None):
whl_file = 'pkg1-0.5-py2.py3-none-any.whl'
with ZipFile(pjoin(wheel_directory, whl_file), 'w') as zf:
for pyfile in glob('*.py'):
zf.write(pyfile)
for metadata in glob('*.dist-info/*'):
zf.write(metadata)
return whl_file


def get_requires_for_build_sdist(config_settings):
return ['frog']


class UnsupportedOperation(Exception):
pass


def build_sdist(sdist_directory, config_settings):
if config_settings.get('test_unsupported', False):
raise UnsupportedOperation

target = 'pkg1-0.5.tar.gz'
with tarfile.open(pjoin(sdist_directory, target), 'w:gz',
format=tarfile.PAX_FORMAT) as tf:
def _add(relpath):
tf.add(relpath, arcname='pkg1-0.5/' + relpath)

_add('pyproject.toml')
for pyfile in glob('*.py'):
_add(pyfile)

return target
34 changes: 34 additions & 0 deletions examples/buildtool/tests/samples/buildsys_pkgs/buildsys_minimal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Test backend defining only the mandatory hooks.

Don't use this for any real code.
"""
from glob import glob
from os.path import join as pjoin
import tarfile
from zipfile import ZipFile


def build_wheel(wheel_directory, config_settings, metadata_directory=None):
whl_file = 'pkg2-0.5-py2.py3-none-any.whl'
with ZipFile(pjoin(wheel_directory, whl_file), 'w') as zf:
for pyfile in glob('*.py'):
zf.write(pyfile)
for metadata in glob('*.dist-info/*'):
zf.write(metadata)
return whl_file


def build_sdist(sdist_directory, config_settings):
target = 'pkg2-0.5.tar.gz'
with tarfile.open(pjoin(sdist_directory, target), 'w:gz',
format=tarfile.PAX_FORMAT) as tf:
def _add(relpath):
tf.add(relpath, arcname='pkg2-0.5/' + relpath)

_add('pyproject.toml')
for pyfile in glob('*.py'):
_add(pyfile)
for distinfo in glob('*.dist-info'):
_add(distinfo)

return target
21 changes: 21 additions & 0 deletions examples/buildtool/tests/samples/pkg1/pkg1-0.5.dist-info/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2017 Thomas Kluyver

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Metadata-Version: 1.2
Name: pkg1
Version: 0.5
Summary: Sample package for tests
Home-page: https://github.com/takluyver/pep517
License: UNKNOWN
Author: Thomas Kluyver
Author-email: thomas@kluyver.me.uk
Classifier: License :: OSI Approved :: MIT License
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pkg1.py,sha256=ZawKBtrxtdGEheOCWvwzGZsO8Q1OSzEzecGNsRz-ekc,52
pkg1-0.5.dist-info/LICENSE,sha256=GyKwSbUmfW38I6Z79KhNjsBLn9-xpR02DkK0NCyLQVQ,1081
pkg1-0.5.dist-info/WHEEL,sha256=jxKvNaDKHDacpaLi69-vnLKkBSynwBzmMS82pipt1T0,100
pkg1-0.5.dist-info/METADATA,sha256=GDliGDwDPM11hoO79KhjyJuFgcm-TOj30gewsPNjkHw,251
pkg1-0.5.dist-info/RECORD,,
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: buildsys 0.1
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
3 changes: 3 additions & 0 deletions examples/buildtool/tests/samples/pkg1/pkg1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Sample package for tests"""

__version__ = '0.5'
3 changes: 3 additions & 0 deletions examples/buildtool/tests/samples/pkg1/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["eg_buildsys"]
build-backend = "buildsys"
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from mock import patch, call # Python 2 fallback
import zipfile

from pep517.envbuild import build_sdist, build_wheel, BuildEnvironment
from ..envbuild import build_sdist, build_wheel, BuildEnvironment

SAMPLES_DIR = pjoin(dirname(abspath(__file__)), 'samples')
BUILDSYS_PKGS = pjoin(SAMPLES_DIR, 'buildsys_pkgs')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from pep517 import meta
from .. import meta


pep517_needs_python_3 = pytest.mark.xfail(
Expand Down
2 changes: 2 additions & 0 deletions pep517/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
"""

__version__ = '0.8.2'

from .wrappers import * # noqa: F401,F403
Loading