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

Support for exposing manual pages #1047

Merged
merged 73 commits into from
Dec 2, 2023
Merged

Conversation

peterkuma
Copy link
Contributor

@peterkuma peterkuma commented Aug 24, 2023

This is a pull request implementing issue #857 (Handling manpages). It implements a similar function as symlinking of apps in the local bin directory, but for unix manual pages.

  • Can install and uninstall a simple package with manual pages, with our without --include-deps.
  • Passes tests-3.11.

The changes can be tested with Python packages which install manual pages in share/man/man<i>. For example sympy or yt-dlp.

  • I have added an entry to docs/changelog.md

Summary of changes

This pull request modifies several commands and some other modules. It closely mirrors existing code for app symlinking but for manual pages (where it makes sense). It adds a new environment variable PIPX_MAN_DIR, in analogy to PIPX_BIN_DIR. Some functions implementing app symlinking are generalized to support manual page symlinking as well.

On Linux and macOS it exposes manual pages in $HOME/.local/share/man/man<i>.

On Windows it exposes manual pages in C:\Users\<user>\.local\share\man, even though Windows does not have any manual page reader in its default installation. I am not sure if it makes sense to expose the manual pages on this OS at all.

Test plan

  • Tested on Devuan GNU/Linux 5 with the default system Python (3.11.2-1 b1)
  • Tested on macOS 13.5.1 (x64) with the default system Python (3.9.6)
  • Tested on Windows 11 (x64) with the official Python distribution (3.11.5)

Tested by running

$ pipx install sympy
  installed package sympy 1.12, installed using Python 3.11.2
  These apps are now globally available
    - isympy
  These manual pages are now globally available
    - man1/isympy.1
done! ✨ 🌟 ✨
$ man isympy
$ pipx uninstall sympy

$ pipx install yt-dlp
  installed package yt-dlp 2023.9.24, installed using Python 3.11.2
  These apps are now globally available
    - yt-dlp
  These manual pages are now globally available
    - man1/yt-dlp.1
done! ✨ 🌟 ✨
$ man yt-dlp
$ pipx uninstall yt-dlp

- Can install and uninstall a simple package with manual pages on Linux.
- Not tested on macOS or Windows.
- Tests not updated yet.
A function call to expose_resources_globally with a duplicate argument.
@peterkuma peterkuma mentioned this pull request Aug 24, 2023
Comment on lines 264 to 274
exposed_man_paths = []
for i in range(1, 10):
man_section = "man%d" % i
exposed_man_paths = get_exposed_man_paths_for_package(
venv.man_path / man_section,
constants.LOCAL_MAN_DIR / man_section,
man_pages,
)
exposed_man_pages = sorted(
str(Path(p.parent.name) / p.name) for p in exposed_man_paths
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this kind of structure appears often, could you integrate the loop in and return the exposed_man_pages from get_exposed_man_paths_for_package()?

Copy link
Contributor Author

@peterkuma peterkuma Aug 25, 2023

Choose a reason for hiding this comment

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

It gets slightly comlicated because get_exposed_man_paths_for_package is called from multiple places, among which is _get_venv_resource_paths. _get_venv_resource_paths is currently called for each man<i> directory separately. The reason is because it checks if a symlink can be created in the directory (can_symlink(local_resource_dir)), which shouldn't be done for the main man directory (~/.local/share/man), but for each man<i> subdirectory. _get_venv_resource_paths then calls get_exposed_man_paths_for_package indirectly through _get_package_man_paths. If the man_section loop is located in get_exposed_man_paths_for_package, there will have to be another man_section loop in _get_venv_resource_paths for the can_symlink checks. It is of course possible, but there isn't a neat and simple way of getting rid of the loops.

Another option how to make it slightly nicer would be to add a constant such as MAN_SECTIONS = ["man%d" % i for i in range(1, 10)] in constants.py and then loop over the list in other places.

Comment on lines 118 to 125
def get_man_pages(dist: metadata.Distribution, man_path: Path) -> List[str]:
man_pages = []
for i in range(1, 10):
man_section = "man%d" % i
names = get_resources("man", dist, man_path / man_section)
for name in names:
man_pages = [str(Path(man_section) / name)]
return man_pages
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to merge this with get_resources() and return a nested list?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I have changed that in the latest commit (49b13fb). Also I have created a new constant MAN_SECTIONS and changed the loops to iterate over MAN_SECTIONS instead of the more complicated way with range(1, 10).

- Add a new MAN_SECTIONS constant.
- Loop over MAN_SECTIONS instead of range(1, 10) when iterating man sections.
- _dfs_package_resources and get_resources now return a nested list for apps
  and man pages together.
This fixes something which might be a small bug in the original code, but also
affecting the man pages exposition. Post-installation, pipx prints "These apps
are now globally available", followed by a list of app names. However, if all
of the apps are unavailable (`exposed_binary_names` is empty and
`unavailable_binary_names` is non-empty), "These apps are now globally
available" is not printed, when it probably should. In this case, the list of
unavailable apps is printed regardless and appears somewhat out of context.
The same issue affects printing of exposed and unavailable man pages.
When symbolic links are not available (e.g. on Windows),
`get_exposed_man_paths_for_package` failed to produce results because it called
`get_exposed_paths_for_package` with an incorrect list of man pages. Items in
this list errornously included the `man<i>` path prefix.
@chrysle
Copy link
Contributor

chrysle commented Sep 24, 2023

This looks good to me. Could you fix all the tests and mark this as ready for review?

This is to satisfy the linter. The new functions are:

- get_apps_from_entry_points
- get_resources_from_dist_files
- get_resources_from_inst_files

get_resources simply collectes the results from the above functions. The
functionality is unchanged.
@peterkuma peterkuma marked this pull request as ready for review October 3, 2023 19:49
@peterkuma
Copy link
Contributor Author

peterkuma commented Oct 3, 2023

tests-3.11 passes now, but ubuntu-latest 3.7, 3.8 and windows-latest 3.11 fail. I will fix them soon.

The man pages code used the str.removeprefix method which does not exist in
older Python versions.
can_symlink could prevously be called without the directory existing yet,
resulting in an error. Also the symlink check was performed on the "man"
directory instead of individually on the "man/man<i>" subdirectories.

_copy_package_resources is now _copy_package_resource and called per path from
expose_resources_globally.

_symlink_package_resources is removed, and instead _symlink_package_resource
is called per path directly from expose_resources_globally.

expose_resources_globally creates the destination directory before calling
can_symlink, which fixes the error.
can_symlink is called in _get_venv_resource_paths on local_resource_dir which
might not exist. Test first if the directory exists.
@peterkuma
Copy link
Contributor Author

Now it passes all of the tests in the GitHub workflow.

CHANGELOG.md Outdated Show resolved Hide resolved
peterkuma and others added 2 commits October 5, 2023 11:07
- Add new bullet points to the "How it Works" section.
- Add description of how to use data_files with manual pages in setup.py to the
  "Developing for pipx" section.
- Add description to the pipx install command help.
@uranusjr
Copy link
Member

uranusjr commented Oct 6, 2023

Looks good in general. Some tests on the main feature would be nice.

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.

A few wording suggestions.

src/pipx/main.py Outdated Show resolved Hide resolved
src/pipx/main.py Outdated Show resolved Hide resolved
docs/how-pipx-works.md Outdated Show resolved Hide resolved
docs/how-pipx-works.md Outdated Show resolved Hide resolved
@peterkuma
Copy link
Contributor Author

@dukecat0

What environment is this on (OS, or anything else specific)? The commands you posted work fine for me on Debian.

@peterkuma
Copy link
Contributor Author

@uranusjr @chrysle How do you deal with package specifications in tests/package_info.py? Installation of many of the packages was failing even in the main branch, which I suppose is due to changes in some of the dependencies. I updated the affected package specifications, but then I realised that testdata/tests_packages/*.txt would also need to be updated, and that seems like it would be quite complicated for so many combinations of OSes and Python versions. Alternatively I could keep the same package versions and just update the lists of apps and app paths. Shall I be concerned about the "slow" tests failing?

@dukecat0
Copy link
Member

dukecat0 commented Nov 1, 2023

@dukecat0

What environment is this on (OS, or anything else specific)? The commands you posted work fine for me on Debian.

It's Github codespace, and Github has set the environment variable PIPX_HOME by default. I guess this error will occur for all users who have set PIPX_HOME to other locations

I just made another test on macOS without setting PIPX_HOME, and everything works well.

@dukecat0
Copy link
Member

dukecat0 commented Nov 1, 2023

@uranusjr @chrysle How do you deal with package specifications in tests/package_info.py? Installation of many of the packages was failing even in the main branch, which I suppose is due to changes in some of the dependencies. I updated the affected package specifications, but then I realised that testdata/tests_packages/*.txt would also need to be updated, and that seems like it would be quite complicated for so many combinations of OSes and Python versions. Alternatively I could keep the same package versions and just update the lists of apps and app paths. Shall I be concerned about the "slow" tests failing?

We use a Github Action to generate those lists. You can trigger it from https://github.com/peterkuma/pipx/actions/workflows/create_tests_package_lists.yml and follow the instruction here: https://github.com/pypa/pipx/blob/main/testdata/tests_packages/README.md#generating-the-platform-specific-lists-from-the-master-list

@chrysle
Copy link
Contributor

chrysle commented Nov 1, 2023

I find it easier to just use the nox workflow create_test_package_list.

@peterkuma
Copy link
Contributor Author

@dukecat0
What environment is this on (OS, or anything else specific)? The commands you posted work fine for me on Debian.

It's Github codespace, and Github has set the environment variable PIPX_HOME by default. I guess this error will occur for all users who have set PIPX_HOME to other locations

I just made another test on macOS without setting PIPX_HOME, and everything works well.

The reason for this appears to be that sympy is already installed in the default GitHub Codespaces image, i.e. the files .local/bin/isympy and .local/share/man/man1/isympy.1 already exist in the system. Using a custom PIPX_HOME works fine on my local Debian.

@peterkuma
Copy link
Contributor Author

The pull request is done on my part unless you have any more requests. I have added some tests for manual pages. The standard tests are passing. Some of the --all-package tests are failing, but that does not seem to be related to the changes. I could not update all of the testdata/tests_packages/*.txt files because the GitHub Action create_tests_package_lists does not produce lists for macos20 and macos21.

@dukecat0
Copy link
Member

The reason for this appears to be that sympy is already installed in the default GitHub Codespaces image, i.e. the files .local/bin/isympy and .local/share/man/man1/isympy.1 already exist in the system. Using a custom PIPX_HOME works fine on my local Debian.

Thanks for the investigation. I didn't notice this before. 😅

Copy link
Contributor

@gaborbernat gaborbernat left a comment

Choose a reason for hiding this comment

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

Needs merge conflicts fixed.

Copy link
Contributor

@gaborbernat gaborbernat left a comment

Choose a reason for hiding this comment

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

@gaborbernat gaborbernat merged commit 44e538d into pypa:main Dec 2, 2023
9 checks passed
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.

None yet

6 participants