Is it allowed to build packages that contain symlinks to external files? #683
-
Let me begin by briefly explaining what I'm trying to do, because it could be that I'm just approaching the whole thing wrong. I'm writing a package that provides the ability to build a database and make queries against it. I recently decided that it'd be best to split these two tasks (building and querying) into separate packages, because (i) each user will usually only be interested in one task or the other, not both, and (ii) both tasks require very different sets of dependencies, and some of those dependencies can be hard/undesirable to install in the environments where the other package would be used. However, the tasks are not fully independent; there's a lot of shared "utility" code that is used for both. My thought was to approach this by creating a single repository with two packages, and symlinking any shared source files from one package to the other. I was hoping that the symlinks would be automatically replaced by real files in sdists/wheels, so I'd end up with two independently distributable packages that share some source code.1 To see if this approach was viable, I made a minimal example of such a setup. The reason I'm writing this question is that, when playing around with this example, I found that some commands ( Minimal example:See the above-linked repository for full details, but basically it contains two packages, one named When I install both packages using $ python -m venv venv_pip
$ . venv_pip/bin/activate
$ pip install ./foo_pkg
...
Successfully installed foo-0.0.0
$ pip install ./bar_pkg
...
Successfully installed bar-0.0.0
$ python -c 'import foo; print(foo.name)'
foo
$ python -c 'import foo.shared; print(foo.shared.name)'
shared
$ python -c 'import bar; print(bar.name)'
bar
$ python -c 'import bar.shared; print(bar.shared.name)'
shared When I try using $ python -m venv venv_build
$ . venv_build/bin/activate
$ pip install build
$ python -m build foo_pkg
...
Successfully built foo-0.0.0.tar.gz and foo-0.0.0-py2.py3-none-any.whl
$ python -m build bar_pkg
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- flit_core >=3.2,<4
* Getting build dependencies for sdist...
* Building sdist...
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- flit_core >=3.2,<4
* Getting build dependencies for wheel...
* Building wheel...
Traceback (most recent call last):
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_build/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 373, in <module>
main()
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_build/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 357, in main
json_out["return_val"] = hook(**hook_input["kwargs"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_build/lib/python3.12/site-packages/pyproject_hooks/_in_process/_in_process.py", line 271, in build_wheel
return _build_backend().build_wheel(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/build-env-4167r9eb/lib/python3.12/site-packages/flit_core/buildapi.py", line 72, in build_wheel
info = make_wheel_in(pyproj_toml, Path(wheel_directory))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/tmp/build-env-4167r9eb/lib/python3.12/site-packages/flit_core/wheel.py", line 224, in make_wheel_in
wb.build(editable)
File "/tmp/build-env-4167r9eb/lib/python3.12/site-packages/flit_core/wheel.py", line 210, in build
self.copy_module()
File "/tmp/build-env-4167r9eb/lib/python3.12/site-packages/flit_core/wheel.py", line 164, in copy_module
self._add_file(full_path, rel_path)
File "/tmp/build-env-4167r9eb/lib/python3.12/site-packages/flit_core/wheel.py", line 111, in _add_file
zinfo = zipfile.ZipInfo.from_file(full_path, rel_path)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/kale/.pyenv/versions/3.12.2/lib/python3.12/zipfile/__init__.py", line 557, in from_file
st = os.stat(filename)
^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'bar/shared.py'
ERROR Backend subprocess exited when trying to invoke build_wheel When I try using $ python -m venv venv_flit
$ . venv_flit/bin/activate
$ pip install flit
$ cd foo_pkg
$ python -m flit build
Found 3 files tracked in git I-flit.sdist
Built sdist: dist/foo-0.0.0.tar.gz I-flit_core.sdist
Copying package file(s) from /tmp/tmp3232v1ow/foo-0.0.0/foo I-flit_core.wheel
Writing metadata files I-flit_core.wheel
Writing the record of files I-flit_core.wheel
Built wheel: dist/foo-0.0.0-py2.py3-none-any.whl I-flit_core.wheel
$ cd ../bar_pkg
$ python -m flit build
Found 3 files tracked in git I-flit.sdist
Built sdist: dist/bar-0.0.0.tar.gz I-flit_core.sdist
Copying package file(s) from /tmp/tmpul817nvr/bar-0.0.0/bar I-flit_core.wheel
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit/__main__.py", line 5, in <module>
main()
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit/__init__.py", line 191, in main
main(args.ini_file, formats=set(args.format or []),
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit/build.py", line 54, in main
wheel_info = make_wheel_in(tmp_ini_file, dist_dir)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit/wheel.py", line 8, in make_wheel_in
return core_wheel.make_wheel_in(ini_path, wheel_directory, editable)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit_core/wheel.py", line 224, in make_wheel_in
wb.build(editable)
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit_core/wheel.py", line 210, in build
self.copy_module()
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit_core/wheel.py", line 164, in copy_module
self._add_file(full_path, rel_path)
File "/home/kale/hacking/snippets/python/multipackage_symlinks/venv_flit/lib/python3.12/site-packages/flit_core/wheel.py", line 111, in _add_file
zinfo = zipfile.ZipInfo.from_file(full_path, rel_path)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/kale/.pyenv/versions/3.12.2/lib/python3.12/zipfile/__init__.py", line 557, in from_file
st = os.stat(filename)
^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/tmpul817nvr/bar-0.0.0/bar/shared.py'
$ python -m flit build --format sdist
Found 3 files tracked in git I-flit.sdist
Built sdist: dist/bar-0.0.0.tar.gz I-flit_core.sdist
$ python -m flit build --format wheel
Copying package file(s) from bar I-flit_core.wheel
Writing metadata files I-flit_core.wheel
Writing the record of files I-flit_core.wheel
Built wheel: dist/bar-0.0.0-py2.py3-none-any.whl I-flit_core.wheel If you got this far, thanks for reading all this! I'd appreciate any insight you can give on what's happening, and what should be happening. Footnotes
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I'd be inclined to say it's a fluke that it worked with pip. I'd guess the difference is that One difficulty here is that the packaging tools don't know what you want a symlink to mean. In your explanation, it's clear that you want the symlink to be materialised in the packaged/installed files, but someone else might put a symlink inside their source code and expect it to still be a symlink when it's installed. It could also potentially be a security risk - if I prepare a git repo or an sdist containing a symlink and get someone else to make a wheel from it, materialising the link, can I steal a secret file? I accept that that sounds far fetched, but find a few bits of unexpected behaviour like this and stick them together, and you could cause all kinds of mischief... I'm not saying symlinks are totally out, but I'd want to think carefully about how they should work and what limitations should apply to them. One possible limitation is not following links outside the package folder, which would prevent your use case from working. Another possible approach for your use case is to make one package with two sets of optional dependencies, so users would install |
Beta Was this translation helpful? Give feedback.
-
Here's a previous discussion about it: #408 |
Beta Was this translation helpful? Give feedback.
I'd be inclined to say it's a fluke that it worked with pip. I'd guess the difference is that
flit build
andpython -m build
both build an sdist first, then unpack that and build the wheel from that, whereas pip probably builds a wheel directly from the source tree you point it to.One difficulty here is that the packaging tools don't know what you want a symlink to mean. In your explanation, it's clear that you want the symlink to be materialised in the packaged/installed files, but someone else might put a symlink inside their source code and expect it to still be a symlink when it's installed. It could also potentially be a security risk - if I prepare a git repo or an sdist containing…