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

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

merged 25 commits into from
Mar 18, 2024

Conversation

Jendker
Copy link
Contributor

@Jendker Jendker commented Mar 7, 2024

  • I have added a news fragment under changelog.d/ (if the patch affects the end users)

Summary of changes

Adds a --global option to perform actions for all users. This is a resubmission of PR #1088 by https://github.com/haxwithaxe which I rebased on the latest main.

Test plan

Passes your CI process on github (via your unaltered actions in my fork) apart from MacOS test which needs to be investigated. Help from your side is highly appreciated. Test run from my fork: https://github.com/Jendker/pipx/actions/runs/8182365101

To verify the changes do what they are supposed to

python3 src/pipx --global environment

To test all the relevant commands for regressions.

python3 src/pipx --global ensurepath
sudo python3 src/pipx --global install pycowsay
sudo python3 src/pipx --global reinstall pycowsay
sudo python3 src/pipx --global inject pycowsay black
sudo python3 src/pipx --global uninject pycowsay black
sudo python3 src/pipx --global runpip pycowsay freeze
sudo python3 src/pipx --global list
sudo python3 src/pipx --global uninstall pycowsay
python3 src/pipx environment
python3 src/pipx ensurepath
python3 src/pipx install pycowsay
python3 src/pipx reinstall pycowsay
python3 src/pipx inject pycowsay black
python3 src/pipx uninject pycowsay black
python3 src/pipx runpip pycowsay freeze
python3 src/pipx list
python3 src/pipx uninstall pycowsay

@Jendker
Copy link
Contributor Author

Jendker commented Mar 7, 2024

Pinging @Gitznik who offered help in the original PR.

Copy link
Contributor

@chrysle chrysle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up! However we'll definitely need to add some more documentation, and extend the "How pipx works" documentation.

tests/test_uninstall.py Outdated Show resolved Hide resolved
tests/test_upgrade.py Show resolved Hide resolved
tests/test_upgrade.py Outdated Show resolved Hide resolved
tests/test_environment.py Outdated Show resolved Hide resolved
src/pipx/paths.py Outdated Show resolved Hide resolved
src/pipx/paths.py Outdated Show resolved Hide resolved
src/pipx/paths.py Outdated Show resolved Hide resolved
src/pipx/main.py Outdated Show resolved Hide resolved
Copy link
Contributor

@Gitznik Gitznik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up!

tests/test_list.py Outdated Show resolved Hide resolved
tests/test_uninstall.py Outdated Show resolved Hide resolved
@Jendker
Copy link
Contributor Author

Jendker commented Mar 9, 2024

Thank you for the review and all helpful comments! I had a look into https://pipx.pypa.io/stable/how-pipx-works/ but I am not sure if I will be able to adjust the doc without blurring everything given my limited knowledge of pipx codebase. Some help here from your side would be highly appreciated!

For now, I have updated the docs in docs/installation.md which already mention the env variables so I could add a concise and (hopefully) understandable note regarding --global flag there.

@Jendker Jendker requested review from chrysle and Gitznik March 9, 2024 07:50
@chrysle
Copy link
Contributor

chrysle commented Mar 9, 2024

I had a look into https://pipx.pypa.io/stable/how-pipx-works/ but I am not sure if I will be able to adjust the doc without blurring everything given my limited knowledge of pipx codebase.

Probably adding a bullet point mentioning the --global flag and linking to the new paragraph under Installation (which I think looks good) will be enough. This doc is also missing information on how to override the common paths, so providing the link should serve a dual purpose.

tests/helpers.py Outdated Show resolved Hide resolved
@Jendker Jendker requested a review from chrysle March 9, 2024 08:25
Copy link
Contributor

@Gitznik Gitznik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just docs stuff, otherwise lgtm 👍

docs/installation.md Outdated Show resolved Hide resolved
docs/installation.md Outdated Show resolved Hide resolved
docs/how-pipx-works.md Show resolved Hide resolved
docs/installation.md Outdated Show resolved Hide resolved
@Jendker Jendker requested a review from Gitznik March 9, 2024 18:03
@Jendker
Copy link
Contributor Author

Jendker commented Mar 9, 2024

Is there anything I can do to get the tests to pass on MacOS? I am not sure why I get permission denied accessing /opt/pipx on MacOS but it works fine on Ubuntu.

@Gitznik
Copy link
Contributor

Gitznik commented Mar 9, 2024

Is there anything I can do to get the tests to pass on MacOS? I am not sure why I get permission denied accessing /opt/pipx on MacOS but it works fine on Ubuntu.

/opt does not seem to be a thing on MacOS. It is not even mentioned in their file system docs.

You can use platformdirs site_data_dir to generate the appropriate shared path instead I think. You can use it for both Linux and Mac, staying consistent with the current way of doing things (even though it may have to be changed during #1257).

@chrysle
Copy link
Contributor

chrysle commented Mar 10, 2024

You can use platformdirs site_data_dir to generate the appropriate shared path instead I think. You can use it for both Linux and Mac, staying consistent with the current way of doing things (even though it may have to be changed during #1257).

That sounds good for MacOS, but I think we should stick to /opt/ on Linux, as I am fairly sure /usr/local/share/ is mostly a location for system administrators to install their own software.

@Gitznik
Copy link
Contributor

Gitznik commented Mar 10, 2024

That sounds good for MacOS, but I think we should stick to /opt/ on Linux, as I am fairly sure /usr/local/share/ is mostly a location for system administrators to install their own software.

There's a lot of discourse around it online (spend about 20 minutes yesterday reading conflicting opinions on that topic). From what I gathered either would be fine, really. BSD derivatives favor usr/local/share (as with MacOS, /opt is not really a thing there) while Linux itself slightly favors /opt.

IMO - stick to the decision platformdirs has made, as we're not just supporting Linux - we're also supporting BSD derivatives.

@chrysle
Copy link
Contributor

chrysle commented Mar 10, 2024

IMO - stick to the decision platformdirs has made, as we're not just supporting Linux - we're also supporting BSD derivatives.

Good, but if downstream packagers complain because they feel the need to patch this behaviour I'll direct them to you ;-)

@Gitznik
Copy link
Contributor

Gitznik commented Mar 10, 2024

Good, but if downstream packagers complain because they feel the need to patch this behaviour I'll direct them to you ;-)

If you think that's a realistic scenario, let's go for /opt/ on linux, and /usr/local/share/ for Mac & BSD? We currently don't differentiate between those anywhere AFAIK, so some new logic will be required there.

@Jendker
Copy link
Contributor Author

Jendker commented Mar 10, 2024

I tested it locally on my Mac machine and writing to '/opt' works both on Intel and ARM Mac but I have to run commands as a root user with sudo. I’d expect the same on Ubuntu where '/opt' is only writeable for root so I’m surprised that the tests are passing on Ubuntu without any sudo calls.

@Jendker
Copy link
Contributor Author

Jendker commented Mar 10, 2024

To follow up on my last comment I just run tests in with python docker image (debian based) with a non-root user and I got very similar output like in MacOS tests:

________________________________________________________________________________________ test_fetch_missing_python _________________________________________________________________________________________

src = PosixPath('/tmp/tmp9nfdvti7/download/python'), dst = PosixPath('/opt/pipx/py/3.12'), copy_function = <function copy2 at 0x7f454efcdc60>

    def move(src, dst, copy_function=copy2):
        """Recursively move a file or directory to another location. This is
        similar to the Unix "mv" command. Return the file or directory's
        destination.

        If dst is an existing directory or a symlink to a directory, then src is
        moved inside that directory. The destination path in that directory must
        not already exist.

        If dst already exists but is not a directory, it may be overwritten
        depending on os.rename() semantics.

        If the destination is on our current filesystem, then rename() is used.
        Otherwise, src is copied to the destination and then removed. Symlinks are
        recreated under the new name if os.rename() fails because of cross
        filesystem renames.

        The optional `copy_function` argument is a callable that will be used
        to copy the source or it will be delegated to `copytree`.
        By default, copy2() is used, but any function that supports the same
        signature (like copy()) can be used.

        A lot more could be done here...  A look at a mv.c shows a lot of
        the issues this implementation glosses over.

        """
        sys.audit("shutil.move", src, dst)
        real_dst = dst
        if os.path.isdir(dst):
            if _samefile(src, dst) and not os.path.islink(src):
                # We might be on a case insensitive filesystem,
                # perform the rename anyway.
                os.rename(src, dst)
                return

            # Using _basename instead of os.path.basename is important, as we must
            # ignore any trailing slash to avoid the basename returning ''
            real_dst = os.path.join(dst, _basename(src))

            if os.path.exists(real_dst):
                raise Error("Destination path '%s' already exists" % real_dst)
        try:
>           os.rename(src, real_dst)
E           FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmp9nfdvti7/download/python' -> '/opt/pipx/py/3.12'

/usr/local/lib/python3.12/shutil.py:886: FileNotFoundError

During handling of the above exception, another exception occurred:

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f454caa10d0>, mocked_github_api = None

    def test_fetch_missing_python(monkeypatch, mocked_github_api):
        def which(name):
            return None

        monkeypatch.setattr(shutil, "which", which)

        major = sys.version_info.major
        minor = sys.version_info.minor
        target_python = f"{major}.{minor}"

        if target_python == "3.8":
            # 3.8 is not available in the standalone python project
            with pytest.raises(InterpreterResolutionError) as e:
                find_python_interpreter(target_python, fetch_missing_python=True)
                assert "not found" in str(e)
        else:
>           python_path = find_python_interpreter(target_python, fetch_missing_python=True)

tests/test_interpreter.py:191:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/pipx/interpreter.py:67: in find_python_interpreter
    standalone_executable = download_python_build_standalone(python_version)
src/pipx/standalone_python.py:90: in download_python_build_standalone
    shutil.move(extracted_dir, install_dir)
/usr/local/lib/python3.12/shutil.py:902: in move
    copytree(src, real_dst, copy_function=copy_function,
/usr/local/lib/python3.12/shutil.py:600: in copytree
    return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
/usr/local/lib/python3.12/shutil.py:498: in _copytree
    os.makedirs(dst, exist_ok=dirs_exist_ok)
<frozen os>:215: in makedirs
    ???
<frozen os>:215: in makedirs
    ???
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

name = '/opt/pipx', mode = 511, exist_ok = False

>   ???
E   PermissionError: [Errno 13] Permission denied: '/opt/pipx'

The same tests passes with a root user.

I'm checking for the core issue but from what it looks like it's not the problem of /opt path existance on MacOS as many global tests are passing apart from this single test.

EDIT: From what it looks like the is_global is set in one of the previous tests and it has the influence on the following test_fetch_missing_python test which shouldn't be affected. Running just test_fetch_missing_python makes it pass, but running all in series causes test_fetch_missing_python to fail :(

@Jendker
Copy link
Contributor Author

Jendker commented Mar 10, 2024

On a separate branch just to test what I said above I have added a call in the failing MacOS test to set paths to local (which resets what previous tests did) and now MacOS tests are passing.

Commit: https://github.com/Jendker/pipx/commit/969f0390df81197a99a871c32153a6355e79c65a
Test run: https://github.com/Jendker/pipx/actions/runs/8224676966

It doesn't solve the core issue with pytest, is only a workaround. I don't have enough expertise in pytest to understand why that's needed in the first place but if you think that this test workaround is good enough for now I'll add this commit to PR branch and I think we can merge.

haxwithaxe and others added 10 commits March 11, 2024 11:15
* Added --global switch that sets the relevant paths to point to
  locations that should be accessable to all users.
* Added --global specific tests
* The implementation is crude.
...by not having --global in windows

The behavior intended by --global doesn't have a precedent in windows
when using pip (as in running pip with sudo). Leaving windows support
to another time after some discussion.
* Moved path management to `pipx.paths`
* Path "constants" available via a context object `pipx.paths.ctx`
@Gitznik
Copy link
Contributor

Gitznik commented Mar 13, 2024

I have tried moving the make_local into conftest but that isn't enough. Failing test: https://github.com/Jendker/pipx/actions/runs/8267713069/job/22618840110 On rebase_main (PR branch) I have added the make_local on fixture cleanup commit while still keeping the make_local at the end of all --global tests and that obviously still works https://github.com/Jendker/pipx/actions/runs/8267933048.

Do you have any suggestions how to improve the situation?

test_environment test_cli_global does not use pipx_temp_env, so that will still leak into one other test. Maybe adding pipx_temp_env there will fix the tests?

This reverts commit fe24c8f.
@Jendker
Copy link
Contributor Author

Jendker commented Mar 13, 2024

That's a good point but that's still something the developer needs to add to avoid tests leaking so there is no substantial difference between adding make_local or pipx_temp_env to each test so it doesn't fix the core issue. Please correct me if I'm missing something.

@Gitznik
Copy link
Contributor

Gitznik commented Mar 13, 2024

That's a good point but that's still something the developer needs to add to avoid tests leaking so there is no substantial difference between adding make_local or pipx_temp_env to each test so it doesn't fix the core issue. Please correct me if I'm missing something.

Yes you're right. A lot of test have pipx_temp_env anyways, so having it there just allows you to not worry about make_local - most of the time. But yeah, having it in the tests that make use of the --global flag makes it very obvious that it needs to be done.

I'm fine with either version

@Jendker
Copy link
Contributor Author

Jendker commented Mar 13, 2024

I have removed all the explicit make_local calls in the tests and used pipx_temp_env in all tests with --global. I had to update test_cli_global test to reflect new paths from pipx_temp_env. Please let me know if you have any suggestions.

Copy link
Contributor

@Gitznik Gitznik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a quick note to the ensurepath docs that you need to run it with --global to add the global location to the PATH. Aside from that - looks good to me know. Great work :)

@Jendker
Copy link
Contributor Author

Jendker commented Mar 14, 2024

Please add a quick note to the ensurepath docs that you need to run it with --global to add the global location to the PATH. Aside from that - looks good to me know. Great work :)

Done in 740b775 :)

README.md Show resolved Hide resolved
docs/installation.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
@mairas
Copy link

mairas commented Mar 15, 2024

I'm a bit late with the comment, but /opt is a thing on Macos, at least in practice. Specifically, Homebrew does install itself on /opt/homebrew. You need to create /opt/pipx with sudo and then set the permissions, but other than that, it works fine and I've already installed some packages there with the environment variable hacks described in Issue #754.

Homebrew used to install itself in /usr/local but switched the location some years ago. Don't remember the specifics but I think Macos wasn't happy with relaxing permisssions of /usr/local like Homebrew did. Risking a bit of cargo cult programming here, my preference would be to stick with /opt/pipx.

@Jendker
Copy link
Contributor Author

Jendker commented Mar 18, 2024

If all looks good to you can this be merged? I am worried about the possible conflicts with other PR which may emerge if it doesn’t get merged into master some time soon ☺️

@Gitznik Gitznik enabled auto-merge (squash) March 18, 2024 09:13
@Gitznik Gitznik merged commit 24e8e29 into pypa:main Mar 18, 2024
12 checks passed
@Jendker
Copy link
Contributor Author

Jendker commented Mar 18, 2024

Thank you @Gitznik and @chrysle for your help in improving this PR and merging!

@Gitznik
Copy link
Contributor

Gitznik commented Mar 18, 2024

Thanks for picking this up and following all the way through @Jendker 🚀

@gaby
Copy link

gaby commented Mar 19, 2024

@Gitznik Is there an ETA for the next release? I can't wait to try this feature.

@Gitznik
Copy link
Contributor

Gitznik commented Mar 19, 2024

@Gitznik Is there an ETA for the next release? I can't wait to try this feature.

@gaby There is not concrete ETA, but we plan to have a release very soon :)

@chrysle
Copy link
Contributor

chrysle commented Mar 19, 2024

And until I've refurbished the release pipeline to do so, please feel free to install directly from the main branch. Pre-release testing is always welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants