From 513bbd5fa3f7f10d11a808a66498e985b6ee69ab Mon Sep 17 00:00:00 2001 From: Adriane Boyd Date: Wed, 8 Nov 2023 17:35:24 +0100 Subject: [PATCH] Add preferred use of build for package CLI (#13109) Build with `build` if available. Warn and fall back to previous `setup.py`-based builds if `build` build fails. --- spacy/cli/package.py | 59 ++++++++++++++++++++++++--- website/docs/usage/saving-loading.mdx | 4 +- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/spacy/cli/package.py b/spacy/cli/package.py index 12f195be1..9421199f1 100644 --- a/spacy/cli/package.py +++ b/spacy/cli/package.py @@ -1,5 +1,7 @@ +import os import re import shutil +import subprocess import sys from collections import defaultdict from pathlib import Path @@ -11,6 +13,7 @@ from thinc.api import Config from wasabi import MarkdownRenderer, Printer, get_raw_input from .. import about, util +from ..compat import importlib_metadata from ..schemas import ModelMetaSchema, validate from ._util import SDIST_SUFFIX, WHEEL_SUFFIX, Arg, Opt, app, string_to_list @@ -35,7 +38,7 @@ def package_cli( specified output directory, and the data will be copied over. If --create-meta is set and a meta.json already exists in the output directory, the existing values will be used as the defaults in the command-line prompt. - After packaging, "python setup.py sdist" is run in the package directory, + After packaging, "python -m build --sdist" is run in the package directory, which will create a .tar.gz archive that can be installed via "pip install". If additional code files are provided (e.g. Python files containing custom @@ -78,9 +81,17 @@ def package( input_path = util.ensure_path(input_dir) output_path = util.ensure_path(output_dir) meta_path = util.ensure_path(meta_path) - if create_wheel and not has_wheel(): - err = "Generating a binary .whl file requires wheel to be installed" - msg.fail(err, "pip install wheel", exits=1) + if create_wheel and not has_wheel() and not has_build(): + err = ( + "Generating wheels requires 'build' or 'wheel' (deprecated) to be installed" + ) + msg.fail(err, "pip install build", exits=1) + if not has_build(): + msg.warn( + "Generating packages without the 'build' package is deprecated and " + "will not be supported in the future. To install 'build': pip " + "install build" + ) if not input_path or not input_path.exists(): msg.fail("Can't locate pipeline data", input_path, exits=1) if not output_path or not output_path.exists(): @@ -184,12 +195,37 @@ def package( msg.good(f"Successfully created package directory '{model_name_v}'", main_path) if create_sdist: with util.working_dir(main_path): - util.run_command([sys.executable, "setup.py", "sdist"], capture=False) + # run directly, since util.run_command is not designed to continue + # after a command fails + ret = subprocess.run( + [sys.executable, "-m", "build", ".", "--sdist"], + env=os.environ.copy(), + ) + if ret.returncode != 0: + msg.warn( + "Creating sdist with 'python -m build' failed. Falling " + "back to deprecated use of 'python setup.py sdist'" + ) + util.run_command([sys.executable, "setup.py", "sdist"], capture=False) zip_file = main_path / "dist" / f"{model_name_v}{SDIST_SUFFIX}" msg.good(f"Successfully created zipped Python package", zip_file) if create_wheel: with util.working_dir(main_path): - util.run_command([sys.executable, "setup.py", "bdist_wheel"], capture=False) + # run directly, since util.run_command is not designed to continue + # after a command fails + ret = subprocess.run( + [sys.executable, "-m", "build", ".", "--wheel"], + env=os.environ.copy(), + ) + if ret.returncode != 0: + msg.warn( + "Creating wheel with 'python -m build' failed. Falling " + "back to deprecated use of 'wheel' with " + "'python setup.py bdist_wheel'" + ) + util.run_command( + [sys.executable, "setup.py", "bdist_wheel"], capture=False + ) wheel_name_squashed = re.sub("_+", "_", model_name_v) wheel = main_path / "dist" / f"{wheel_name_squashed}{WHEEL_SUFFIX}" msg.good(f"Successfully created binary wheel", wheel) @@ -209,6 +245,17 @@ def has_wheel() -> bool: return False +def has_build() -> bool: + # it's very likely that there is a local directory named build/ (especially + # in an editable install), so an import check is not sufficient; instead + # check that there is a package version + try: + importlib_metadata.version("build") + return True + except importlib_metadata.PackageNotFoundError: # type: ignore[attr-defined] + return False + + def get_third_party_dependencies( config: Config, exclude: List[str] = util.SimpleFrozenList() ) -> List[str]: diff --git a/website/docs/usage/saving-loading.mdx b/website/docs/usage/saving-loading.mdx index 26f59750b..9a6791d5e 100644 --- a/website/docs/usage/saving-loading.mdx +++ b/website/docs/usage/saving-loading.mdx @@ -405,7 +405,7 @@ available to spaCy, all you need to do is install the package in your environment: ```bash -$ python setup.py develop +$ python -m pip install . ``` spaCy is now able to create the pipeline component `"snek"` – even though you @@ -673,7 +673,7 @@ $ python -m spacy package ./en_example_pipeline ./packages ``` This command will create a pipeline package directory and will run -`python setup.py sdist` in that directory to create a binary `.whl` file or +`python -m build` in that directory to create a binary `.whl` file or `.tar.gz` archive of your package that can be installed using `pip install`. Installing the binary wheel is usually more efficient.