tiran/certifi-system-store

Fedora packgaing challenges

Opened this issue · 4 comments

I've tried to package this as an RPM to replace python-certfi.
I know it is a hack and I have no intentions to put it in Fedora proper, this is merely an experiment now.

In this issue, I'll post the challenges I've encountered.

This is the first spec file:

Name:           python-certifi-system-store
Version:        3021.3.13
Release:        1%{?dist}
Summary:        A certifi hack to use system trust store on Linux/FreeBSD
License:        MPLv2.0
URL:            https://github.com/tiran/certifi-system-store
Source0:        %{url}/archive/v%{version}/certifi-system-store-%{version}.tar.gz

BuildArch:      noarch
BuildRequires:  python3-devel
BuildRequires:  pyproject-rpm-macros

%global _description %{expand:
...}

%description %_description


%package -n python3-certifi-system-store
Summary:        %{summary}

%description -n python3-certifi-system-store %_description


%prep
%autosetup -p1 -n certifi-system-store-%{version}


%generate_buildrequires
%pyproject_buildrequires -e %{toxenv}-test


%build
%pyproject_wheel


%install
%pyproject_install
%pyproject_save_files certifi


%check
%tox


%files -n python3-certifi-system-store -f %{pyproject_files}
%doc README.md
%license LICENSE


%changelog
* Tue Mar 16 2021 Miro Hrončok <mhroncok@redhat.com> - 3021.3.13-1
- Initial packaging attempt

First challenge: The autopatching happens in wrong time/place:

I see form the output of tox:

+ /usr/bin/python3 -m tox --current-env -q --recreate -e py39-test
certifi-system store 3021.03.13
Patched certifi.dist-info -> certifi_system_store.dist-info
/etc/ssl/cert.pem
... pytest output here ...
___________________________________ summary ____________________________________
  py39-test: commands succeeded
  congratulations :)

It would really be convenient to see where is the dist info "patched", I needed to track that down myself.
Apparently, it is in $PWD:

$ env LANG=en_US.utf-8 \ls -ld *.dist-info
lrwxrwxrwx. 1 churchyard mock   30 Mar 16 10:34 certifi.dist-info -> certifi_system_store.dist-info
drwxr-xr-x. 2 churchyard mock 4096 Mar 16 10:34 certifi_system_store.dist-info

That's... not good.

I can run the patching myself and set some envvars to fool it to do it in what I otherwise pass as --root e.g. to pip install in %pyproject_install but I don't think the patching command supports that.

Now let's pretendt I haven't noticed this and I install the RPM package that only has the normal dist-info, not the symbolic link:

$ rpm -ql python3-certifi-system-store-3021.3.13-1.fc35.noarch.rpm | grep dist-info
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/INSTALLER
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/LICENSE
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/METADATA
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/WHEEL
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/top_level.txt
/usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/zip-safe

When invoked as regular user:

$ python3.9 -m certifi -v
Traceback (most recent call last):
  File "/usr/lib64/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/usr/lib/python3.9/site-packages/certifi/__main__.py", line 14, in <module>
    _patched = _patch_dist_info()
  File "/usr/lib/python3.9/site-packages/certifi/_patch.py", line 45, in _patch_dist_info
    _relsymlink(target=abs_css_distinfodir, linkname=abs_certifi_distinfodir)
  File "/usr/lib/python3.9/site-packages/certifi/_patch.py", line 14, in _relsymlink
    os.symlink(rel_target, linkname_file, dir_fd=dir_fd)
PermissionError: [Errno 13] Permission denied: 'certifi_system_store-3021.3.13.dist-info' -> 'certifi-3021.3.13.dist-info'

As root:

# python3.9 -m certifi -v
certifi-system store 3021.03.13
Patched certifi.dist-info -> certifi_system_store.dist-info
/etc/ssl/cert.pem
# ls -l /usr/lib/python3.9/site-packages/certifi-3021.3.13.dist-info
lrwxrwxrwx. 1 root root 40 Mar 16 10:46 /usr/lib/python3.9/site-packages/certifi-3021.3.13.dist-info -> certifi_system_store-3021.3.13.dist-info
# rpm -qf /usr/lib/python3.9/site-packages/certifi-3021.3.13.dist-info
file /usr/lib/python3.9/site-packages/certifi-3021.3.13.dist-info is not owned by any package

This is bad from packaging perspective.

In the meantime, let me try to create the symbolic link manually from the spec instead:

%install
%pyproject_install
%pyproject_save_files certifi
ln -sr %{buildroot}%{python3_sitelib}/certifi_system_store-%{version}.dist-info %{buildroot}%{python3_sitelib}/certifi-%{version}.dist-info


%check
%tox


%files -n python3-certifi-system-store -f %{pyproject_files}
%doc README.md
%license LICENSE
%{python3_sitelib}/certifi-%{version}.dist-info

Except now tox fails:

Traceback (most recent call last):
  File "/usr/lib64/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/builddir/build/BUILDROOT/python-certifi-system-store-3021.3.13-1.fc35.x86_64/usr/lib/python3.9/site-packages/certifi/__main__.py", line 14, in <module>
    _patched = _patch_dist_info()
  File "/builddir/build/BUILDROOT/python-certifi-system-store-3021.3.13-1.fc35.x86_64/usr/lib/python3.9/site-packages/certifi/_patch.py", line 34, in _patch_dist_info
    shutil.rmtree(certifi_dist.egg_info)
  File "/usr/lib64/python3.9/shutil.py", line 728, in rmtree
    onerror(os.path.islink, path, sys.exc_info())
  File "/usr/lib64/python3.9/shutil.py", line 726, in rmtree
    raise OSError("Cannot call rmtree on a symbolic link")
OSError: Cannot call rmtree on a symbolic link
ERROR: InvocationError for command /builddir/build/BUILD/certifi-system-store-3021.3.13/.tox/py39-test/bin/python -m certifi -v (exited with code 1)

So I need to create the link after running the tests, which feels dirty:

%install
%pyproject_install
%pyproject_save_files certifi


%check
%tox
ln -sr %{buildroot}%{python3_sitelib}/certifi_system_store-%{version}.dist-info %{buildroot}%{python3_sitelib}/certifi-%{version}.dist-info


%files -n python3-certifi-system-store -f %{pyproject_files}
%doc README.md
%license LICENSE
%{python3_sitelib}/certifi-%{version}.dist-info
$ rpm -qvl python3-certifi-system-store-3021.3.13-1.fc35.noarch.rpm | grep dist-info
lrwxrwxrwx    1 root     root                       40 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi-3021.3.13.dist-info -> certifi_system_store-3021.3.13.dist-info
drwxr-xr-x    2 root     root                        0 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info
-rw-r--r--    1 root     root                        4 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/INSTALLER
-rw-r--r--    1 root     root                    16726 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/LICENSE
-rw-r--r--    1 root     root                     6023 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/METADATA
-rw-r--r--    1 root     root                       92 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/WHEEL
-rw-r--r--    1 root     root                        8 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/top_level.txt
-rw-r--r--    1 root     root                        1 Mar 16 10:56 /usr/lib/python3.9/site-packages/certifi_system_store-3021.3.13.dist-info/zip-safe

But the RPM dist dep generator is not fooled by the linked dist-info:

$ rpm -qp --provides /var/lib/mock/fedora-rawhide-x86_64/result/python3-certifi-system-store-3021.3.13-1.fc35.noarch.rpm
python-certifi-system-store = 3021.3.13-1.fc35
python3-certifi-system-store = 3021.3.13-1.fc35
python3.9-certifi-system-store = 3021.3.13-1.fc35
python3.9dist(certifi-system-store) = 3021.3.13
python3dist(certifi-system-store) = 3021.3.13
tiran commented

Thanks for looking into this, Miro!

The symlink is a nasty/clever hack. For now Python packages cannot declare that they provide an alternative distribution name. Currently pip ignores provides and obsoletes metadata fields. Hopefully https://discuss.python.org/t/packaging-forks/7502 will make the hack obsolete in the future.

PR #9 adds better logging and error handling.

Tox tests should only create the symlink in tox's virtual env. As you mentioned in your last comment, you also have to create the symlink and ship the symlink in the packaged site-packages directory.