diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8c257841..83843e7b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -71,6 +71,7 @@ Improved Documentation ---------------------- - remove prefixes on install commands for easy copy/pasting `#4792 `_ +- Officially drop support for Python 2.7 and Python 3.6. `#4261 `_ 2021.5.29 (2021-05-29) diff --git a/news/4831.bugfix.rst b/news/4831.bugfix.rst new file mode 100644 index 00000000..1dc765e5 --- /dev/null +++ b/news/4831.bugfix.rst @@ -0,0 +1 @@ +Fix a bug that ``pipenv run`` doesn't set environment variables correctly. diff --git a/pipenv/core.py b/pipenv/core.py index 5b21a9d8..75fa9793 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -96,7 +96,7 @@ def do_clear(project): raise -def load_dot_env(project): +def load_dot_env(project, as_dict=False): """Loads .env file into sys.environ.""" if not project.s.PIPENV_DONT_LOAD_ENV: # If the project doesn't exist yet, check current directory for a .env file @@ -121,8 +121,11 @@ def load_dot_env(project): ), err=True, ) - dotenv.load_dotenv(dotenv_file, override=True) - project.s.initialize() + if as_dict: + return dotenv.dotenv_values(dotenv_file) + else: + dotenv.load_dotenv(dotenv_file, override=True) + project.s.initialize() def cleanup_virtualenv(project, bare=True): @@ -2384,11 +2387,13 @@ def inline_activate_virtual_environment(project): os.environ["VIRTUAL_ENV"] = vistir.misc.fs_str(root) -def _launch_windows_subprocess(script, path): +def _launch_windows_subprocess(script, env): import subprocess + + path = env.get("PATH", "") command = system_which(script.command, path=path) - options = {"universal_newlines": True} + options = {"universal_newlines": True, "env": env} script.cmd_args[1:] = [expandvars(arg) for arg in script.args] # Command not found, maybe this is a shell built-in? @@ -2408,13 +2413,14 @@ def _launch_windows_subprocess(script, path): return subprocess.Popen(script.cmdify(), shell=True, **options) -def do_run_nt(project, script, path): - p = _launch_windows_subprocess(script, path) +def do_run_nt(project, script, env): + p = _launch_windows_subprocess(script, env) p.communicate() sys.exit(p.returncode) -def do_run_posix(project, script, command, path): +def do_run_posix(project, script, command, env): + path = env.get("PATH") command_path = system_which(script.command, path=path) if not command_path: if project.has_script(command): @@ -2440,8 +2446,10 @@ def do_run_posix(project, script, command, path): err=True, ) sys.exit(1) - os.execl( - command_path, command_path, *(os.path.expandvars(arg) for arg in script.args) + os.execve( + command_path, + [command_path, *(os.path.expandvars(arg) for arg in script.args)], + env ) @@ -2457,25 +2465,25 @@ def do_run(project, command, args, three=None, python=False, pypi_mirror=None): project, three=three, python=python, validate=False, pypi_mirror=pypi_mirror, ) - load_dot_env(project) + env = os.environ.copy() + env.update(load_dot_env(project, as_dict=True)) + env.pop("PIP_SHIMS_BASE_MODULE", None) - previous_pip_shims_module = os.environ.pop("PIP_SHIMS_BASE_MODULE", None) - - path = os.getenv('PATH', '') + path = env.get('PATH', '') if project.virtualenv_location: new_path = os.path.join(project.virtualenv_location, 'Scripts' if os.name == 'nt' else 'bin') paths = path.split(os.pathsep) paths.insert(0, new_path) path = os.pathsep.join(paths) + env["VIRTUAL_ENV"] = project.virtualenv_location + env["PATH"] = path # Set an environment variable, so we know we're in the environment. # Only set PIPENV_ACTIVE after finishing reading virtualenv_location # such as in inline_activate_virtual_environment # otherwise its value will be changed - previous_pipenv_active_value = os.environ.get("PIPENV_ACTIVE") - os.environ["PIPENV_ACTIVE"] = vistir.misc.fs_str("1") - - os.environ.pop("PIP_SHIMS_BASE_MODULE", None) + env["PIPENV_ACTIVE"] = vistir.misc.fs_str("1") + env.pop("PIP_SHIMS_BASE_MODULE", None) try: script = project.build_script(command, args) @@ -2485,20 +2493,13 @@ def do_run(project, command, args, three=None, python=False, pypi_mirror=None): except ScriptEmptyError: click.echo("Can't run script {0!r}-it's empty?", err=True) run_args = [project, script] - run_kwargs = {'path': path} + run_kwargs = {'env': env} if os.name == "nt" or environments.PIPENV_IS_CI: run_fn = do_run_nt else: run_fn = do_run_posix run_kwargs.update({"command": command}) - try: - run_fn(*run_args, **run_kwargs) - finally: - os.environ.pop("PIPENV_ACTIVE", None) - if previous_pipenv_active_value is not None: - os.environ["PIPENV_ACTIVE"] = previous_pipenv_active_value - if previous_pip_shims_module is not None: - os.environ["PIP_SHIMS_BASE_MODULE"] = previous_pip_shims_module + run_fn(*run_args, **run_kwargs) def do_check( diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 3cbd6574..93166072 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -113,18 +113,10 @@ def pytest_runtest_setup(item): pytest.skip('requires github ssh') if item.get_closest_marker('needs_hg') is not None and not WE_HAVE_HG: pytest.skip('requires mercurial') - if item.get_closest_marker('skip_py27_win') is not None and ( - sys.version_info[:2] <= (2, 7) and os.name == "nt" - ): - pytest.skip('must use python > 2.7 on windows') if item.get_closest_marker('skip_py38') is not None and ( sys.version_info[:2] == (3, 8) ): pytest.skip('test not applicable on python 3.8') - if item.get_closest_marker('py3_only') is not None and ( - sys.version_info < (3, 0) - ): - pytest.skip('test only runs on python 3') if item.get_closest_marker('skip_osx') is not None and sys.platform == 'darwin': pytest.skip('test does not apply on OSX') if item.get_closest_marker('lte_py36') is not None and ( diff --git a/tests/integration/test_install_twists.py b/tests/integration/test_install_twists.py index c8df2244..a07c27c7 100644 --- a/tests/integration/test_install_twists.py +++ b/tests/integration/test_install_twists.py @@ -372,7 +372,6 @@ def test_multiple_editable_packages_should_not_race(PipenvInstance, testsroot): @pytest.mark.outdated -@pytest.mark.py3_only def test_outdated_should_compare_postreleases_without_failing(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("install ibm-db-sa-py3==0.3.0") diff --git a/tests/integration/test_install_uri.py b/tests/integration/test_install_uri.py index e5638505..3c6b8a0d 100644 --- a/tests/integration/test_install_uri.py +++ b/tests/integration/test_install_uri.py @@ -257,7 +257,6 @@ def test_get_vcs_refs(PipenvInstance_NoPyPI): @pytest.mark.urls @pytest.mark.install @pytest.mark.needs_internet -@pytest.mark.py3_only def test_vcs_entry_supersedes_non_vcs(PipenvInstance): """See issue #2181 -- non-editable VCS dep was specified, but not showing up in the lockfile -- due to not running pip install before locking and not locking diff --git a/tests/integration/test_run.py b/tests/integration/test_run.py index 3059f88c..cf6a7f79 100644 --- a/tests/integration/test_run.py +++ b/tests/integration/test_run.py @@ -61,3 +61,24 @@ multicommand = "bash -c \"cd docs && make html\"" assert c.returncode == 0 if os.name != "nt": # This doesn't work on CI windows. assert c.stdout.strip() == "WORLD" + + +@pytest.mark.run +@pytest.mark.skip_windows +def test_run_with_usr_env_shebang(PipenvInstance): + with PipenvInstance(chdir=True) as p: + p.pipenv('install') + script_path = os.path.join(p.path, "test_script") + with open(script_path, "w") as f: + f.write( + "#!/usr/bin/env python\n" + "import sys, os\n\n" + "print(sys.prefix)\n" + "print(os.getenv('VIRTUAL_ENV'))\n" + ) + os.chmod(script_path, 0o700) + c = p.pipenv("run ./test_script") + assert c.returncode == 0 + project = Project() + lines = [line.strip() for line in c.stdout.splitlines()] + assert all(line == project.virtualenv_location for line in lines)