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

Updated PR: Added --global option to perform actions for all users (#754) #1281

Merged
merged 25 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
45541e9
The bare minimum implementation of --global
Oct 13, 2023
bb73423
Fixing failing windows tests
Oct 14, 2023
b3048b5
Cleaned up the changes made to implement --global
Oct 14, 2023
627b421
Fixed some stuff missed in the merge
Dec 25, 2023
8335f03
Fix rebase issues
Jendker Mar 6, 2024
967f644
Add missing mkdir calls, fix test_upgrade
Jendker Mar 7, 2024
d5471a9
Add feature note to changelog.d
Jendker Mar 7, 2024
0b7ed40
Use decorator for test skips on windows
Jendker Mar 9, 2024
366c727
Global tests update, PR cleanup
Jendker Mar 9, 2024
453e74c
Add note about --global switch in installation docs
Jendker Mar 9, 2024
aa26496
Revert unnecessary formatter changes
Jendker Mar 9, 2024
2d15e73
Satisfy mypy import
Jendker Mar 9, 2024
1fa2943
Add note about --global argument to how-pipx-works
Jendker Mar 9, 2024
11a6a51
--global documentation improvements
Jendker Mar 9, 2024
8f47ec8
Update home_exists in make_global
Jendker Mar 10, 2024
613db8d
Run test_fetch_missing_python with setting local
Jendker Mar 10, 2024
0c0a6fb
Add make_local at the end of all global tests
Jendker Mar 12, 2024
fe24c8f
Fix ensurepath
Jendker Mar 13, 2024
a918feb
make_local on fixture cleanup
Jendker Mar 13, 2024
82badc7
Revert "Fix ensurepath"
Jendker Mar 13, 2024
3e965b1
Remove explicit make_local from tests
Jendker Mar 13, 2024
740b775
Inform user about sudo pipx ensurepath --global
Jendker Mar 14, 2024
9597273
Make test_cli test less tolerant
Jendker Mar 15, 2024
30a891c
Improve grammar
Jendker Mar 15, 2024
14a90a8
Merge branch 'main' into rebase_main
Jendker Mar 17, 2024
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: 3 additions & 0 deletions changelog.d/754.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add `--global` option to `pipx` commands.

This will run the action in a global scope and affect environment for all system users.
1 change: 1 addition & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pipx install --include-deps jupyter
pipx install --pip-args='--pre' poetry
pipx install --pip-args='--index-url=<private-repo-host>:<private-repo-port> --trusted-host=<private-repo-host>:<private-repo-port>' private-repo-package
pipx install --index-url https://test.pypi.org/simple/ --pip-args='--extra-index-url https://pypi.org/simple/' some-package
pipx --global install pycowsay
```

## `pipx run` examples
Expand Down
2 changes: 2 additions & 0 deletions docs/how-pipx-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ When installing a package and its binaries on linux (`pipx install package`) pip
- as long as `~/.local/bin/` is on your PATH, you can now invoke the new binaries globally
- on operating systems which have the `man` command, as long as `~/.local/share/man` is a recognized search path of man,
you can now view the new manual pages globally
- adding `--global` flag to any `pipx` command will execute the action in global scope which will expose app to all
Gitznik marked this conversation as resolved.
Show resolved Hide resolved
users - [reference](installation.md#global-installation). Note that this is not available on Windows.

When running a binary (`pipx run BINARY`), pipx will

Expand Down
10 changes: 10 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ sudo PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin PIPX_MAN_DIR=/usr/local/sha
> `user_data_dir()`, `user_cache_dir()` and `user_log_dir()` resolve to appropriate platform-specific user data, cache and log directories.
> See the [platformdirs documentation](https://platformdirs.readthedocs.io/en/latest/api.html#platforms) for details.

### Global installation

Pipx also comes with a `--global` argument which helps to execute actions in global scope which exposes the app to
all system users. By default the global binary location is set to `/usr/local/bin` and can be overridden with the
environment variable `PIPX_GLOBAL_BIN_DIR`. Default global manual page location is `/usr/local/share/man`. This
can be overridden with environment variable `PIPX_GLOBAL_MAN_DIR`. Finally, default global virtual environment location
is `/opt/pipx`, can be overridden with environment variable `PIPX_GLOBAL_HOME`.

Note that `--global` argument is not supported on Windows.
Jendker marked this conversation as resolved.
Show resolved Hide resolved

## Upgrade pipx

On macOS:
Expand Down
10 changes: 5 additions & 5 deletions src/pipx/commands/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
import userpath # type: ignore
from packaging.utils import canonicalize_name

from pipx import constants
from pipx import paths
from pipx.colors import bold, red
from pipx.constants import MAN_SECTIONS, PIPX_STANDALONE_PYTHON_CACHEDIR, WINDOWS
from pipx.constants import MAN_SECTIONS, WINDOWS
from pipx.emojis import hazard, stars
from pipx.package_specifier import parse_specifier_for_install, valid_pypi_name
from pipx.pipx_metadata_file import PackageInfo
Expand Down Expand Up @@ -231,7 +231,7 @@ def get_venv_summary(

exposed_app_paths = get_exposed_paths_for_package(
venv.bin_path,
constants.LOCAL_BIN_DIR,
paths.ctx.bin_dir,
[add_suffix(app, package_metadata.suffix) for app in apps],
)
exposed_binary_names = sorted(p.name for p in exposed_app_paths)
Expand All @@ -242,7 +242,7 @@ def get_venv_summary(
for man_section in MAN_SECTIONS:
exposed_man_paths |= get_exposed_man_paths_for_package(
venv.man_path / man_section,
constants.LOCAL_MAN_DIR / man_section,
paths.ctx.man_dir / man_section,
man_pages,
)
exposed_man_pages = sorted(str(Path(p.parent.name) / p.name) for p in exposed_man_paths)
Expand All @@ -252,7 +252,7 @@ def get_venv_summary(
python_version = venv.pipx_metadata.python_version if venv.pipx_metadata.python_version is not None else ""
source_interpreter = venv.pipx_metadata.source_interpreter
is_standalone = (
str(source_interpreter).startswith(str(PIPX_STANDALONE_PYTHON_CACHEDIR.resolve()))
str(source_interpreter).startswith(str(paths.ctx.standalone_python_cachedir.resolve()))
if source_interpreter
else False
)
Expand Down
4 changes: 2 additions & 2 deletions src/pipx/commands/ensure_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import userpath # type: ignore

from pipx import constants
from pipx import paths
from pipx.constants import EXIT_CODE_OK, ExitCode
from pipx.emojis import hazard, stars
from pipx.util import pipx_wrap
Expand Down Expand Up @@ -97,7 +97,7 @@ def ensure_path(location: Path, *, force: bool) -> Tuple[bool, bool]:

def ensure_pipx_paths(force: bool) -> ExitCode:
"""Returns pipx exit code."""
bin_paths = {constants.LOCAL_BIN_DIR}
chrysle marked this conversation as resolved.
Show resolved Hide resolved
bin_paths = {paths.ctx.bin_dir}

pipx_user_bin_path = get_pipx_user_bin_path()
if pipx_user_bin_path is not None:
Expand Down
33 changes: 11 additions & 22 deletions src/pipx/commands/environment.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
import os

from pipx.constants import (
EXIT_CODE_OK,
LOCAL_BIN_DIR,
LOCAL_MAN_DIR,
PIPX_HOME,
PIPX_LOCAL_VENVS,
PIPX_LOG_DIR,
PIPX_SHARED_LIBS,
PIPX_STANDALONE_PYTHON_CACHEDIR,
PIPX_TRASH_DIR,
PIPX_VENV_CACHEDIR,
ExitCode,
)
from pipx import paths
from pipx.constants import EXIT_CODE_OK, ExitCode
from pipx.emojis import EMOJI_SUPPORT
from pipx.interpreter import DEFAULT_PYTHON
from pipx.util import PipxError
Expand All @@ -30,15 +19,15 @@ def environment(value: str) -> ExitCode:
"USE_EMOJI",
]
derived_values = {
"PIPX_HOME": PIPX_HOME,
"PIPX_BIN_DIR": LOCAL_BIN_DIR,
"PIPX_MAN_DIR": LOCAL_MAN_DIR,
"PIPX_SHARED_LIBS": PIPX_SHARED_LIBS,
"PIPX_LOCAL_VENVS": PIPX_LOCAL_VENVS,
"PIPX_LOG_DIR": PIPX_LOG_DIR,
"PIPX_TRASH_DIR": PIPX_TRASH_DIR,
"PIPX_VENV_CACHEDIR": PIPX_VENV_CACHEDIR,
"PIPX_STANDALONE_PYTHON_CACHEDIR": PIPX_STANDALONE_PYTHON_CACHEDIR,
"PIPX_HOME": paths.ctx.home,
"PIPX_BIN_DIR": paths.ctx.bin_dir,
"PIPX_MAN_DIR": paths.ctx.man_dir,
"PIPX_SHARED_LIBS": paths.ctx.shared_libs,
"PIPX_LOCAL_VENVS": paths.ctx.venvs,
"PIPX_LOG_DIR": paths.ctx.logs,
"PIPX_TRASH_DIR": paths.ctx.trash,
"PIPX_VENV_CACHEDIR": paths.ctx.venv_cache,
"PIPX_STANDALONE_PYTHON_CACHEDIR": paths.ctx.standalone_python_cachedir,
"PIPX_DEFAULT_PYTHON": DEFAULT_PYTHON,
"USE_EMOJI": str(EMOJI_SUPPORT).lower(),
}
Expand Down
6 changes: 3 additions & 3 deletions src/pipx/commands/inject.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import List, Optional

from pipx import constants
from pipx import paths
from pipx.colors import bold
from pipx.commands.common import package_name_from_spec, run_post_install_actions
from pipx.constants import EXIT_CODE_INJECT_ERROR, EXIT_CODE_OK, ExitCode
Expand Down Expand Up @@ -71,8 +71,8 @@ def inject_dep(
run_post_install_actions(
venv,
package_name,
constants.LOCAL_BIN_DIR,
constants.LOCAL_MAN_DIR,
paths.ctx.bin_dir,
paths.ctx.man_dir,
venv_dir,
include_dependencies,
force=force,
Expand Down
4 changes: 2 additions & 2 deletions src/pipx/commands/install.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path
from typing import List, Optional

from pipx import constants
from pipx import paths
from pipx.commands.common import package_name_from_spec, run_post_install_actions
from pipx.constants import (
EXIT_CODE_INSTALL_VENV_EXISTS,
Expand Down Expand Up @@ -46,7 +46,7 @@ def install(

for package_name, package_spec in zip(package_names, package_specs):
if venv_dir is None:
venv_container = VenvContainer(constants.PIPX_LOCAL_VENVS)
venv_container = VenvContainer(paths.ctx.venvs)
venv_dir = venv_container.get_venv_dir(f"{package_name}{suffix}")

try:
Expand Down
6 changes: 3 additions & 3 deletions src/pipx/commands/interpreter.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from pathlib import Path
from typing import List

from pipx import constants
from pipx import constants, paths
from pipx.pipx_metadata_file import PipxMetadata
from pipx.util import is_paths_relative, rmdir
from pipx.venv import Venv, VenvContainer


def get_installed_standalone_interpreters() -> List[Path]:
return [python_dir for python_dir in constants.PIPX_STANDALONE_PYTHON_CACHEDIR.iterdir() if python_dir.is_dir()]
return [python_dir for python_dir in paths.ctx.standalone_python_cachedir.iterdir() if python_dir.is_dir()]


def get_venvs_using_standalone_interpreter(venv_container: VenvContainer) -> List[Venv]:
Expand All @@ -35,7 +35,7 @@ def list_interpreters(
interpreters = get_installed_standalone_interpreters()
venvs = get_venvs_using_standalone_interpreter(venv_container)
output: list[str] = []
output.append(f"Standalone interpreters are in {constants.PIPX_STANDALONE_PYTHON_CACHEDIR}")
output.append(f"Standalone interpreters are in {paths.ctx.standalone_python_cachedir}")
for interpreter in interpreters:
output.append(f"Python {interpreter.name}")
used_in = get_interpreter_users(interpreter, venvs)
Expand Down
6 changes: 3 additions & 3 deletions src/pipx/commands/list_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Any, Collection, Dict, Tuple

from pipx import constants, shared_libs
from pipx import paths, shared_libs
from pipx.colors import bold
from pipx.commands.common import VenvProblems, get_venv_summary, venv_health_check
from pipx.constants import EXIT_CODE_LIST_PROBLEM, EXIT_CODE_OK, ExitCode
Expand Down Expand Up @@ -45,8 +45,8 @@ def list_short(venv_dirs: Collection[Path]) -> VenvProblems:

def list_text(venv_dirs: Collection[Path], include_injected: bool, venv_root_dir: str) -> VenvProblems:
print(f"venvs are in {bold(venv_root_dir)}")
print(f"apps are exposed on your $PATH at {bold(str(constants.LOCAL_BIN_DIR))}")
print(f"manual pages are exposed at {bold(str(constants.LOCAL_MAN_DIR))}")
print(f"apps are exposed on your $PATH at {bold(str(paths.ctx.bin_dir))}")
print(f"manual pages are exposed at {bold(str(paths.ctx.man_dir))}")

all_venv_problems = VenvProblems()
for venv_dir in venv_dirs:
Expand Down
6 changes: 3 additions & 3 deletions src/pipx/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from packaging.requirements import InvalidRequirement, Requirement

from pipx import constants
from pipx import paths
from pipx.commands.common import package_name_from_spec
from pipx.constants import TEMP_VENV_EXPIRATION_THRESHOLD_DAYS, WINDOWS
from pipx.emojis import hazard
Expand Down Expand Up @@ -288,7 +288,7 @@ def _get_temporary_venv_path(requirements: List[str], python: str, pip_args: Lis
m.update("".join(pip_args).encode())
m.update("".join(venv_args).encode())
venv_folder_name = m.hexdigest()[:15] # 15 chosen arbitrarily
return Path(constants.PIPX_VENV_CACHEDIR) / venv_folder_name
return Path(paths.ctx.venv_cache) / venv_folder_name


def _is_temporary_venv_expired(venv_dir: Path) -> bool:
Expand All @@ -308,7 +308,7 @@ def _prepare_venv_cache(venv: Venv, bin_path: Optional[Path], use_cache: bool) -


def _remove_all_expired_venvs() -> None:
for venv_dir in Path(constants.PIPX_VENV_CACHEDIR).iterdir():
for venv_dir in Path(paths.ctx.venv_cache).iterdir():
if _is_temporary_venv_expired(venv_dir):
logger.info(f"Removing expired venv {str(venv_dir)}")
rmdir(venv_dir)
Expand Down
14 changes: 7 additions & 7 deletions src/pipx/commands/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import List, Optional, Sequence

from pipx import commands, constants
from pipx import commands, paths
from pipx.colors import bold, red
from pipx.commands.common import expose_resources_globally
from pipx.constants import EXIT_CODE_OK, ExitCode
Expand Down Expand Up @@ -50,24 +50,24 @@ def _upgrade_package(
if package_metadata.include_apps:
expose_resources_globally(
"app",
constants.LOCAL_BIN_DIR,
paths.ctx.bin_dir,
package_metadata.app_paths,
force=force,
suffix=package_metadata.suffix,
)
expose_resources_globally("man", constants.LOCAL_MAN_DIR, package_metadata.man_paths, force=force)
expose_resources_globally("man", paths.ctx.man_dir, package_metadata.man_paths, force=force)

if package_metadata.include_dependencies:
for _, app_paths in package_metadata.app_paths_of_dependencies.items():
expose_resources_globally(
"app",
constants.LOCAL_BIN_DIR,
paths.ctx.bin_dir,
app_paths,
force=force,
suffix=package_metadata.suffix,
)
for _, man_paths in package_metadata.man_paths_of_dependencies.items():
expose_resources_globally("man", constants.LOCAL_MAN_DIR, man_paths, force=force)
expose_resources_globally("man", paths.ctx.man_dir, man_paths, force=force)

if old_version == new_version:
if upgrading_all:
Expand Down Expand Up @@ -113,8 +113,8 @@ def _upgrade_venv(
venv_args=[],
package_names=None,
package_specs=[str(venv_dir).split(os.path.sep)[-1]],
local_bin_dir=constants.LOCAL_BIN_DIR,
local_man_dir=constants.LOCAL_MAN_DIR,
local_bin_dir=paths.ctx.bin_dir,
local_man_dir=paths.ctx.man_dir,
python=python,
pip_args=pip_args,
verbose=verbose,
Expand Down
43 changes: 4 additions & 39 deletions src/pipx/constants.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,15 @@
import os
import sys
import sysconfig
from pathlib import Path
from textwrap import dedent
from typing import NewType, Optional
from typing import NewType

from platformdirs import user_cache_path, user_data_path, user_log_path


def load_dir_from_environ(dir_name: str, default: Path) -> Path:
env = os.environ.get(dir_name, default)
return Path(os.path.expanduser(env)).resolve()


DEFAULT_PIPX_HOME = user_data_path("pipx")
FALLBACK_PIPX_HOME = Path.home() / ".local/pipx"
DEFAULT_PIPX_BIN_DIR = Path.home() / ".local/bin"
DEFAULT_PIPX_MAN_DIR = Path.home() / ".local/share/man"
MAN_SECTIONS = ["man%d" % i for i in range(1, 10)]

if FALLBACK_PIPX_HOME.exists() or os.environ.get("PIPX_HOME") is not None:
PIPX_HOME = load_dir_from_environ("PIPX_HOME", FALLBACK_PIPX_HOME)
PIPX_LOCAL_VENVS = PIPX_HOME / "venvs"
PIPX_STANDALONE_PYTHON_CACHEDIR = PIPX_HOME / "py"
PIPX_LOG_DIR = PIPX_HOME / "logs"
DEFAULT_PIPX_SHARED_LIBS = PIPX_HOME / "shared"
PIPX_TRASH_DIR = PIPX_HOME / ".trash"
PIPX_VENV_CACHEDIR = PIPX_HOME / ".cache"
else:
PIPX_HOME = DEFAULT_PIPX_HOME
PIPX_LOCAL_VENVS = PIPX_HOME / "venvs"
PIPX_STANDALONE_PYTHON_CACHEDIR = PIPX_HOME / "py"
PIPX_LOG_DIR = user_log_path("pipx")
DEFAULT_PIPX_SHARED_LIBS = PIPX_HOME / "shared"
PIPX_TRASH_DIR = PIPX_HOME / "trash"
PIPX_VENV_CACHEDIR = user_cache_path("pipx")

PIPX_SHARED_LIBS = load_dir_from_environ("PIPX_SHARED_LIBS", DEFAULT_PIPX_SHARED_LIBS)
PIPX_SHARED_PTH = "pipx_shared.pth"
LOCAL_BIN_DIR = load_dir_from_environ("PIPX_BIN_DIR", DEFAULT_PIPX_BIN_DIR)
LOCAL_MAN_DIR = load_dir_from_environ("PIPX_MAN_DIR", DEFAULT_PIPX_MAN_DIR)
FETCH_MISSING_PYTHON = os.environ.get("PIPX_FETCH_MISSING_PYTHON", False)
TEMP_VENV_EXPIRATION_THRESHOLD_DAYS = 14
MINIMUM_PYTHON_VERSION = "3.8"
MAN_SECTIONS = ["man%d" % i for i in range(1, 10)]
FETCH_MISSING_PYTHON = os.environ.get("PIPX_FETCH_MISSING_PYTHON", False)


ExitCode = NewType("ExitCode", int)
# pipx shell exit codes
Expand All @@ -57,8 +24,6 @@ def load_dir_from_environ(dir_name: str, default: Path) -> Path:
EXIT_CODE_REINSTALL_INVALID_PYTHON = ExitCode(1)
EXIT_CODE_SPECIFIED_PYTHON_EXECUTABLE_NOT_FOUND = ExitCode(1)

pipx_log_file: Optional[Path] = None


def is_windows() -> bool:
return sys.platform == "win32"
Expand Down
Loading