Skip to content

Commit cc00b8b

Browse files
committed
Set build_isolation and use_pep517 correctly
- Fix how `use_pep517` and `build_isolation` are read from the environment -- introduce a new environment helper to detect `<PREFIX>_<SETTING>` and `<PREFIX>_NO_<SETTING>`, check for booleans and return appropriately boolean, str, or None types - Check for `False` values when adding `--no-use-pep517` and `--no-build-isolation` during resolution rather than falsey values - Change environment variable name from `PIP_PYTHON_VERSION` to `PIPENV_REQUESTED_PYTHON_VERSION` to avoid causing `pip` to fail due to accidentally percieving the `python_version` flag as being set -- this is an artifact from attempting to resolve outside of the virtualenv - Add `pipenv` to the path of patched `notpip.__main__` to accommodate updated import fully qualified module names - Update `pip` and `piptools` patches - Add test packages for each of two known failure modes: outdated `setuptools` with a missing `build-backend` (which `pip` forces to `build_meta:__legacy__` & which doesn't exist before `40.8`), and `import Cython` statements in `setup.py` in packages with properly defined `pyproject.toml` `build-backend` lines. - Fixes #4231 - Replaces, includes, and closes #4242 Signed-off-by: Dan Ryan <dan.ryan@canonical.com> Add integration tests for #4231 Signed-off-by: Dan Ryan <dan.ryan@canonical.com>
1 parent 7059a26 commit cc00b8b

File tree

17 files changed

+475
-15
lines changed

17 files changed

+475
-15
lines changed

pipenv/environments.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,36 @@ def _is_env_truthy(name):
2424
return os.environ.get(name).lower() not in ("0", "false", "no", "off")
2525

2626

27+
def get_from_env(arg, prefix="PIPENV", check_for_negation=True):
28+
"""
29+
Check the environment for a variable, returning its truthy or stringified value
30+
31+
For example, setting ``PIPENV_NO_RESOLVE_VCS=1`` would mean that
32+
``get_from_env("RESOLVE_VCS", prefix="PIPENV")`` would return ``False``.
33+
34+
:param str arg: The name of the variable to look for
35+
:param str prefix: The prefix to attach to the variable, defaults to "PIPENV"
36+
:param bool check_for_negation: Whether to check for ``<PREFIX>_NO_<arg>``, defaults
37+
to True
38+
:return: The value from the environment if available
39+
:rtype: Optional[Union[str, bool]]
40+
"""
41+
negative_lookup = "NO_{0}".format(arg)
42+
positive_lookup = arg
43+
if prefix:
44+
positive_lookup = "{0}_{1}".format(prefix, arg)
45+
negative_lookup = "{0}_{1}".format(prefix, negative_lookup)
46+
if positive_lookup in os.environ:
47+
if _is_env_truthy(positive_lookup):
48+
return bool(os.environ[positive_lookup])
49+
return os.environ[positive_lookup]
50+
if negative_lookup in os.environ:
51+
if _is_env_truthy(negative_lookup):
52+
return not bool(os.environ[negative_lookup])
53+
return os.environ[negative_lookup]
54+
return None
55+
56+
2757
PIPENV_IS_CI = bool("CI" in os.environ or "TF_BUILD" in os.environ)
2858

2959
# HACK: Prevent invalid shebangs with Homebrew-installed Python:

pipenv/patched/notpip/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
# Resulting path is the name of the wheel itself
1212
# Add that to sys.path so we can import pipenv.patched.notpip
1313
path = os.path.dirname(os.path.dirname(__file__))
14+
pipenv = os.path.dirname(os.path.dirname(path))
1415
sys.path.insert(0, path)
16+
sys.path.insert(0, pipenv)
1517

1618
from pipenv.patched.notpip._internal.cli.main import main as _main # isort:skip # noqa
1719

pipenv/patched/piptools/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def simplify_markers(ireq):
7676
def clean_requires_python(candidates):
7777
"""Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes."""
7878
all_candidates = []
79-
py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3]))))
79+
py_version = parse_version(os.environ.get('PIPENV_REQUESTED_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3]))))
8080
for c in candidates:
8181
if getattr(c, "requires_python", None):
8282
# Old specifications had people setting this to single digits

pipenv/resolver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ def resolve(packages, pre, project, sources, clear, system, requirements_dir=Non
771771

772772

773773
def _main(pre, clear, verbose, system, write, requirements_dir, packages, parse_only=False):
774-
os.environ["PIP_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]])
774+
os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = ".".join([str(s) for s in sys.version_info[:3]])
775775
os.environ["PIP_PYTHON_PATH"] = str(sys.executable)
776776
if parse_only:
777777
parse_packages(

pipenv/utils.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -236,14 +236,14 @@ def __init__(self, python_version, python_path):
236236
def __enter__(self):
237237
# Only inject when the value is valid
238238
if self.python_version:
239-
os.environ["PIP_PYTHON_VERSION"] = str(self.python_version)
239+
os.environ["PIPENV_REQUESTED_PYTHON_VERSION"] = str(self.python_version)
240240
if self.python_path:
241241
os.environ["PIP_PYTHON_PATH"] = str(self.python_path)
242242

243243
def __exit__(self, *args):
244244
# Restore original Python version information.
245245
try:
246-
del os.environ["PIP_PYTHON_VERSION"]
246+
del os.environ["PIPENV_REQUESTED_PYTHON_VERSION"]
247247
except KeyError:
248248
pass
249249

@@ -682,25 +682,21 @@ def pip_command(self):
682682
self._pip_command = self._get_pip_command()
683683
return self._pip_command
684684

685-
def prepare_pip_args(self, use_pep517=True, build_isolation=True):
685+
def prepare_pip_args(self, use_pep517=False, build_isolation=True):
686686
pip_args = []
687687
if self.sources:
688688
pip_args = prepare_pip_source_args(self.sources, pip_args)
689-
if not use_pep517:
689+
if use_pep517 is False:
690690
pip_args.append("--no-use-pep517")
691-
if not build_isolation:
691+
if build_isolation is False:
692692
pip_args.append("--no-build-isolation")
693693
pip_args.extend(["--cache-dir", environments.PIPENV_CACHE_DIR])
694694
return pip_args
695695

696696
@property
697697
def pip_args(self):
698-
use_pep517 = False if (
699-
os.environ.get("PIP_NO_USE_PEP517", None) is not None
700-
) else (True if os.environ.get("PIP_USE_PEP517", None) is not None else None)
701-
build_isolation = False if (
702-
os.environ.get("PIP_NO_BUILD_ISOLATION", None) is not None
703-
) else (True if os.environ.get("PIP_BUILD_ISOLATION", None) is not None else None)
698+
use_pep517 = environments.get_from_env("USE_PEP517", prefix="PIP")
699+
build_isolation = environments.get_from_env("BUILD_ISOLATION", prefix="PIP")
704700
if self._pip_args is None:
705701
self._pip_args = self.prepare_pip_args(
706702
use_pep517=use_pep517, build_isolation=build_isolation
@@ -790,6 +786,7 @@ def get_resolver(self, clear=False, pre=False):
790786
self._resolver = PiptoolsResolver(
791787
constraints=self.parsed_constraints, repository=self.repository,
792788
cache=DependencyCache(environments.PIPENV_CACHE_DIR), clear_caches=clear,
789+
# TODO: allow users to toggle the 'allow unsafe' flag to resolve setuptools?
793790
prereleases=pre, allow_unsafe=False
794791
)
795792

tasks/vendoring/patches/patched/pip20.patch

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,17 @@ index 65e41bc7..9eabf28e 100644
589589

590590

591591
class AdjacentTempDirectory(TempDirectory):
592+
diff --git a/pipenv/patched/pip/__main__.py b/pipenv/patched/pip/__main__.py
593+
index 56f669fa..3c216189 100644
594+
--- a/pipenv/patched/pip/__main__.py
595+
+++ b/pipenv/patched/pip/__main__.py
596+
@@ -11,7 +11,9 @@ if __package__ == '':
597+
# Resulting path is the name of the wheel itself
598+
# Add that to sys.path so we can import pip
599+
path = os.path.dirname(os.path.dirname(__file__))
600+
+ pipenv = os.path.dirname(os.path.dirname(path))
601+
sys.path.insert(0, path)
602+
+ sys.path.insert(0, pipenv)
603+
604+
from pip._internal.cli.main import main as _main # isort:skip # noqa
605+

tasks/vendoring/patches/patched/piptools.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ index 7733447..e6f232f 100644
745745
+def clean_requires_python(candidates):
746746
+ """Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes."""
747747
+ all_candidates = []
748-
+ py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3]))))
748+
+ py_version = parse_version(os.environ.get('PIPENV_REQUESTED_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3]))))
749749
+ for c in candidates:
750750
+ if getattr(c, "requires_python", None):
751751
+ # Old specifications had people setting this to single digits
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[build-system]
2+
requires = ["setuptools >= 40.6.0", "setuptools-scm", "cython"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[tool.black]
6+
line-length = 90
7+
target_version = ['py27', 'py35', 'py36', 'py37', 'py38']
8+
include = '\.pyi?$'
9+
exclude = '''
10+
/(
11+
\.eggs
12+
| \.git
13+
| \.hg
14+
| \.mypy_cache
15+
| \.tox
16+
| \.pyre_configuration
17+
| \.venv
18+
| _build
19+
| buck-out
20+
| build
21+
| dist
22+
)
23+
'''
24+
25+
[tool.towncrier]
26+
package = 'cython-import-package'
27+
package_dir = 'src'
28+
filename = 'CHANGELOG.rst'
29+
directory = 'news/'
30+
title_format = '{version} ({project_date})'
31+
issue_format = '`#{issue} <https://github.com/sarugaku/cython_import_package/issues/{issue}>`_'
32+
template = 'tasks/CHANGELOG.rst.jinja2'
33+
34+
[[tool.towncrier.type]]
35+
directory = 'feature'
36+
name = 'Features'
37+
showcontent = true
38+
39+
[[tool.towncrier.type]]
40+
directory = 'bugfix'
41+
name = 'Bug Fixes'
42+
showcontent = true
43+
44+
[[tool.towncrier.type]]
45+
directory = 'trivial'
46+
name = 'Trivial Changes'
47+
showcontent = false
48+
49+
[[tool.towncrier.type]]
50+
directory = 'removal'
51+
name = 'Removals and Deprecations'
52+
showcontent = true
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[metadata]
2+
name = cython_import_package
3+
package_name = cython-import-package
4+
description = A fake python package.
5+
url = https://github.com/sarugaku/cython_import_package
6+
author = Dan Ryan
7+
author_email = dan@danryan.co
8+
long_description = file: README.rst
9+
license = ISC License
10+
keywords = fake package test
11+
classifier =
12+
Development Status :: 1 - Planning
13+
License :: OSI Approved :: ISC License (ISCL)
14+
Operating System :: OS Independent
15+
Programming Language :: Python :: 2
16+
Programming Language :: Python :: 2.6
17+
Programming Language :: Python :: 2.7
18+
Programming Language :: Python :: 3
19+
Programming Language :: Python :: 3.4
20+
Programming Language :: Python :: 3.5
21+
Programming Language :: Python :: 3.6
22+
Programming Language :: Python :: 3.7
23+
Topic :: Software Development :: Libraries :: Python Modules
24+
25+
[options.extras_require]
26+
tests =
27+
pytest
28+
pytest-xdist
29+
pytest-cov
30+
pytest-timeout
31+
readme-renderer[md]
32+
twine
33+
dev =
34+
black;python_version>="3.6"
35+
flake8
36+
flake8-bugbear;python_version>="3.5"
37+
invoke
38+
isort
39+
mypy;python_version>="3.5"
40+
parver
41+
pre-commit
42+
rope
43+
wheel
44+
45+
[options]
46+
zip_safe = true
47+
python_requires = >=2.6,!=3.0,!=3.1,!=3.2,!=3.3
48+
install_requires =
49+
attrs
50+
vistir
51+
52+
[bdist_wheel]
53+
universal = 1
54+
55+
[egg_info]
56+
tag_build =
57+
tag_date = 0
58+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import ast
2+
import os
3+
4+
from setuptools import setup, find_packages
5+
from setuptools.command.test import test as TestCommand
6+
7+
# ORDER MATTERS
8+
# Import this after setuptools or it will fail
9+
from Cython.Build import cythonize # noqa: I100
10+
import Cython.Distutils
11+
12+
13+
14+
ROOT = os.path.dirname(__file__)
15+
16+
PACKAGE_NAME = 'cython_import_package'
17+
18+
VERSION = None
19+
20+
with open(os.path.join(ROOT, 'src', PACKAGE_NAME.replace("-", "_"), '__init__.py')) as f:
21+
for line in f:
22+
if line.startswith('__version__ = '):
23+
VERSION = ast.literal_eval(line[len('__version__ = '):].strip())
24+
break
25+
if VERSION is None:
26+
raise EnvironmentError('failed to read version')
27+
28+
29+
# Put everything in setup.cfg, except those that don't actually work?
30+
setup(
31+
# These really don't work.
32+
package_dir={'': 'src'},
33+
packages=find_packages('src'),
34+
35+
# I don't know how to specify an empty key in setup.cfg.
36+
package_data={
37+
'': ['LICENSE*', 'README*'],
38+
},
39+
setup_requires=["setuptools_scm", "cython"],
40+
41+
# I need this to be dynamic.
42+
version=VERSION,
43+
)

0 commit comments

Comments
 (0)