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

Build isolation can be a problem with the python-for-android cross compile environment, but disabling it also disables build-system.requires breaking some package installs #6718

Open
ghost opened this issue Jul 15, 2019 · 48 comments
Labels
C: PEP 517 impact Affected by PEP 517 processing state: needs discussion This needs some more discussion type: feature request Request for a new feature

Comments

@ghost
Copy link

ghost commented Jul 15, 2019

Environment

  • pip version: latest on pypi right now - installed via -U pip - should be 19.1.1
  • Python version: 3.7
  • OS: ubuntu in docker, host is fedora 29 x64

Description
I have run into a case where when I specify --no-build-isolation, pip will install dependencies out of order and if any of these dependencies has a pyproject.toml, apparently pip may try to obtain wheel metadata of that dependency (by running its setup.py) without ensuring pyproject.toml's build-system.requires packages are around. This seems like a bug to me, because isn't the entire point of build-system.requires that these packages need to be around for the package to build or even run the setup.py correctly, no matter if the build is isolated or not?

I am also quite practically impacted by this bug or behavior because 1. I need to use pyproject.toml in dependencies due to setup_requires not liking Cython (the easy_install sandbox messes with the Cython compiler) and I use Cython .pxd deps which install_requires doesn't guarantee to be around at build, and 2. I am in a cross compilation environment (python-for-android) where --no-build-isolation is required for all builds because some packages cannot be installed without custom patching, so it's not safe for pip to temporarily reinstall some things as is inherently happening with the build isolation

Expected behavior
no setup.py is ever run even if just for obtaining wheel metadata without everything in build_requires being installed, even if --no-build-isolation is used

How to Reproduce

I don't have an example package right now, really sorry 😢 (because this all happened in python-for-android) but this should work:

  1. Create new project using setup.py in a new folder /my/project/path
  2. Specify a package as a dep that needs another one at build time, e.g. like pip install git+git://github.com/wobblui/wobblui@562daf6e796fdf8d8a50733b532f9cacae4d4df5 (later commits have a workaround!) - wobblui depends on https://github.com/JonasT/nettools for building the wheel and specifies this in its pyproject.toml/with build-system.requires
  3. Install your new project using cd /my/project/path && pip install --user -U --no-build-isolation .
  4. Keep fingers crossed that pip chooses an unlucky order for the deps, and indeed tries to e.g. generate wobblui wheel metadata before it tried to install nettools (which is what happened for me)

Output

(really sorry this looks so weird, it's in python-for-android)


[INFO]:    Launching package install...
[DEBUG]:   -> running bash -c '/home/userhome/.local/share/python-for-android/build/venv/bin/pip' install --no-build-isolation -c ._tmp_p4a_recipe_constraints.txt .
[DEBUG]:   	Processing /home/userhome/workspace
[DEBUG]:   	    Preparing wheel metadata ... done
[DEBUG]:   	Requirement already satisfied: Cython==0.29.12 in /home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages (from -c ._tmp_p4a_recipe_constraints.txt (line 2)) (0.29.12)
[DEBUG]:   	Requirement already satisfied: Pillow==5.2.0 in /home/userhome/.local/share/python-for-android/build/python-installs/unnamed_dist_1 (from -c ._tmp_p4a_recipe_constraints.txt (line 3)) (5.2.0)
[DEBUG]:   	Collecting sfxscan@ git+https://github.com/JonasT/sfxscan.git# from git+https://github.com/JonasT/sfxscan.git (from myapp==0.1)
[DEBUG]:   	  Cloning https://github.com/JonasT/sfxscan.git to /tmp/pip-install-oyi75skp/sfxscan
[DEBUG]:   	  Running command git clone -q https://github.com/JonasT/sfxscan.git /tmp/pip-install-oyi75skp/sfxscan
[DEBUG]:   	  Running command git submodule update --init --recursive -q
[DEBUG]:   	    Preparing wheel metadata ... done
[DEBUG]:   	Collecting wobblui@ https://github.com/wobblui/wobblui/archive/master.zip# from https://github.com/wobblui/wobblui/archive/master.zip (from myapp==0.1)
[DEBUG]:   	  Downloading https://github.com/wobblui/wobblui/archive/master.zip (1.4MB)
     |################################| 1.4MB 390kB/s
[DEBUG]:   	    Preparing wheel metadata ... error
[DEBUG]:   	    ERROR: Complete output from command /home/userhome/.local/share/python-for-android/build/venv/bin/python3 /home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/pip/_vendor/pep517/_in_process.py prepare_metadata_for_build_wheel /tmp/tmpz5xnpef1:
[DEBUG]:   	    ERROR: Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/image.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/image.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/filedialog.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/filedialog.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/button.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/button.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/woblog.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/woblog.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/label.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/label.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/perf.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/perf.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/texture.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/texture.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/timer.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/timer.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/modal.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/modal.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/textedit.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/textedit.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/osinfo.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/osinfo.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/color.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/color.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/box.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/box.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/list.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/list.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/dragselection.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/dragselection.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/cache.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/cache.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/__init__.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/__init__.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget_base_borderdraw.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget_base_borderdraw.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/gfx.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/gfx.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/scrollbarwidget.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/scrollbarwidget.pyx
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/window.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/window.pyx
[DEBUG]:   	
[DEBUG]:   	    Error compiling Cython file:
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	    ...
[DEBUG]:   	    import sys
[DEBUG]:   	    import time
[DEBUG]:   	    import traceback
[DEBUG]:   	    import weakref
[DEBUG]:   	
[DEBUG]:   	    from nettools.cssparse cimport parse as cssparse
[DEBUG]:   	    ^
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	
[DEBUG]:   	    src/wobblui/widget_base.pyx:33:0: 'nettools/cssparse.pxd' not found
[DEBUG]:   	
[DEBUG]:   	    Error compiling Cython file:
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	    ...
[DEBUG]:   	    import sys
[DEBUG]:   	    import time
[DEBUG]:   	    import traceback
[DEBUG]:   	    import weakref
[DEBUG]:   	
[DEBUG]:   	    from nettools.cssparse cimport parse as cssparse
[DEBUG]:   	    ^
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	
[DEBUG]:   	    src/wobblui/widget_base.pyx:33:0: 'nettools/cssparse/parse.pxd' not found
[DEBUG]:   	
[DEBUG]:   	    Error compiling Cython file:
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	    ...
[DEBUG]:   	            if self._cached_custom_css_ruleset is not None:
[DEBUG]:   	                return self._cached_custom_css_ruleset
[DEBUG]:   	            if self._own_custom_css is None or \
[DEBUG]:   	                    len(self._own_custom_css.strip()) == 0:
[DEBUG]:   	                return None
[DEBUG]:   	            self._cached_custom_css_ruleset = cssparse(
[DEBUG]:   	                                             ^
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	
[DEBUG]:   	    src/wobblui/widget_base.pyx:382:42: 'cssparse' is not a constant, variable or function identifier
[DEBUG]:   	
[DEBUG]:   	    Error compiling Cython file:
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	    ...
[DEBUG]:   	            if self._cached_custom_css_ruleset_for_children is not None:
[DEBUG]:   	                return self._cached_custom_css_ruleset_for_children
[DEBUG]:   	            if self._children_custom_css is None or \
[DEBUG]:   	                    len(self._children_custom_css.strip()) == 0:
[DEBUG]:   	                return
[DEBUG]:   	            self._cached_custom_css_ruleset_for_children = cssparse(
[DEBUG]:   	                                                          ^
[DEBUG]:   	    ------------------------------------------------------------
[DEBUG]:   	
[DEBUG]:   	    src/wobblui/widget_base.pyx:394:55: 'cssparse' is not a constant, variable or function identifier
[DEBUG]:   	    Compiling /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget_base.pyx because it changed.
[DEBUG]:   	    [1/1] Cythonizing /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget_base.pyx
[DEBUG]:   	    Traceback (most recent call last):
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/pip/_vendor/pep517/_in_process.py", line 207, in <module>
[DEBUG]:   	        main()
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/pip/_vendor/pep517/_in_process.py", line 197, in main
[DEBUG]:   	        json_out['return_val'] = hook(**hook_input['kwargs'])
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/pip/_vendor/pep517/_in_process.py", line 69, in prepare_metadata_for_build_wheel
[DEBUG]:   	        return hook(metadata_directory, config_settings)
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/other_builds/hostpython3/desktop/hostpython3/native-build/Lib/site-packages/setuptools/build_meta.py", line 154, in prepare_metadata_for_build_wheel
[DEBUG]:   	        self.run_setup()
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/other_builds/hostpython3/desktop/hostpython3/native-build/Lib/site-packages/setuptools/build_meta.py", line 140, in run_setup
[DEBUG]:   	        exec(compile(code, __file__, 'exec'), locals())
[DEBUG]:   	      File "setup.py", line 215, in <module>
[DEBUG]:   	        ext_modules = extensions(),
[DEBUG]:   	      File "setup.py", line 87, in extensions
[DEBUG]:   	        'linetrace': DEBUG_BUILD,
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/Cython/Build/Dependencies.py", line 1096, in cythonize
[DEBUG]:   	        cythonize_one(*args)
[DEBUG]:   	      File "/home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/Cython/Build/Dependencies.py", line 1219, in cythonize_one
[DEBUG]:   	        raise CompileError(None, pyx_file)
[DEBUG]:   	    Cython.Compiler.Errors.CompileError: /tmp/pip-install-oyi75skp/wobblui/src/wobblui/widget_base.pyx
[DEBUG]:   	    ----------------------------------------
[DEBUG]:   	ERROR: Command "/home/userhome/.local/share/python-for-android/build/venv/bin/python3 /home/userhome/.local/share/python-for-android/build/venv/lib/python3.6/site-packages/pip/_vendor/pep517/_in_process.py prepare_metadata_for_build_wheel /tmp/tmpz5xnpef1" failed with error code 1 in /tmp/pip-install-oyi75skp/wobblui
[DEBUG]:

^ as you can see it runs wobblui's Cythonization, which happens because wobblui uses cythonize() to put together its extensions so this automatically runs when the setup.py is invoked (see here: https://github.com/wobblui/wobblui/blob/84bc49f34a329b7fccdd2593fba9d91d30379f20/setup.py#L69 ) even if just to get the metadata and not actually build the wheel. This wouldn't be an issue if nettools was installed first as specified here: https://github.com/wobblui/wobblui/blob/84bc49f34a329b7fccdd2593fba9d91d30379f20/pyproject.toml#L2 - which it is in build isolation, but not with --no-build-isolation, apparently

@triage-new-issues triage-new-issues bot added the S: needs triage Issues/PRs that need to be triaged label Jul 15, 2019
@ghost ghost changed the title With --no-build-isolation pip may try to obtain wheel metadata without ensuring pyproject.toml's build-system.build_requires is around With --no-build-isolation pip may try to obtain wheel metadata without ensuring pyproject.toml's build-system.requires is around Jul 15, 2019
@ghost
Copy link
Author

ghost commented Jul 15, 2019

Sorry, I didn't specify something important clearly: obviously I could just make wobblui's setup.py run fine without the .pxd deps, as long as build_ext isn't actually invoked. (which should work fine for metadata thanks to get_requires_for_build_wheel, which can get metadata for modern setuptools without actually building the wheel)

So it's not like this is an unavoidable problem or anything. However, I think this behavior of pip is simply counter-intuitive given what the intended spirit of build-system.requires appears to be, since I don't think the average package creator will expect build-system.requires to ever not be around when pip runs the setup.py

@ghost
Copy link
Author

ghost commented Jul 16, 2019

Ok, now that I've put in the workaround pip also tries to actually fully build the wheel without build-system.requires being around in any way. (it's part of the Building wheels for collected packages: list, but pip or whatever does that actual wheel build just does them in arbitrary order and doesn't build those first that are in the build-system.requires list of other packages.) That now I can't work around or wouldn't know how, and seems like somewhat of a big problem... 🤔

@ghost ghost changed the title With --no-build-isolation pip may try to obtain wheel metadata without ensuring pyproject.toml's build-system.requires is around With --no-build-isolation pip may try to obtain wheel metadata without ensuring pyproject.toml's build-system.requires is around / wheel build order ignores build-system.requires entirely, apparently Jul 16, 2019
@pfmoore
Copy link
Member

pfmoore commented Jul 16, 2019

no setup.py is ever run even if just for obtaining wheel metadata without everything in build_requires being installed, even if --no-build-isolation is used

I may be missing something here (I haven't fully understood the fairly complex example you gave) but surely using --no-build-isolation (which is documented as "Disable isolation when building a modern source distribution. Build dependencies specified by PEP 518 must be already installed if this option is used.") means that you've accepted the responsibility of installing any necessary build requirements yourself?

@ghost
Copy link
Author

ghost commented Jul 16, 2019

... means that you've accepted the responsibility of installing any necessary build requirements yourself?

@pfmoore ok that explains it. In this case, please treat this as a feature request!

Why disabled build isolation appears to be required for pip to work right in a cross-compile environment:

I tested in python-for-android and pip's build isolation will currently often break in its cross compile environment. The reason is python-for-android needs to preinstall some packages in a different way to make them work, and pip cannot just reinstall them for build isolation, it just doesn't work technically and the install fails if it attempts to do so. Summed up, I don't know what to do! I don't see how this could work

setup_requires would work fine because it doesn't use/enable build isolation, but it's deprecated, and breaks with Cython sometimes, and also python-for-android can't decide for any package not to use pyproject.toml - so not really a solution

Edit: sorry this context might be missing, I am one of the contributors of https://github.com/kivy/python-for-android making its setup.py integration work. So that is why I ended up here, trying to make things work reliably for build deps of packages. Basically all that is technically doable I can in theory make python-for-android do, it's just I don't even see any feasible way how this could work

Edit 2: trimmed this down a little to the core point

@pfmoore
Copy link
Member

pfmoore commented Jul 16, 2019

@pfmoore ok that explains it. In this case, please treat this as a feature request!

You'd need to explain the request you're making better. (And if it's an issue limited to cross-compiling environments, it would still probably not be high on anyone else's priority list, as I don't believe there are many people working on cross-compilation at the moment - so you may need to provide a PR yourself, or be prepared for a long wait).

Currently, pip has two "modes" of operation (described essentially in PEPs 517 and 518, although the implementation details aren't part of the standards). It either does nothing, expecting the user to set up the build environment, and then pip just runs the builds in the global environment, or it isolates individual builds in an environment with the build_dependencies specified in pyproject.toml preinstalled. There's no support (currently, or planned) for requesting build isolation on a per-build step basis, or for any other level of isolation. Your "feature request" is essentially about asking for another option, as I understand it - but in that case you'll need to specify what that option is, in a way that someone could code it.

I just don't know what to do! I don't see how this could work.

If you don't, then I doubt anyone else is likely to be able to 😢 Sorry, but this sounds like a pretty specialised problem, and it'll need domain knowledge to know what would solve it.

@ghost
Copy link
Author

ghost commented Jul 16, 2019

The problem is pretty simple actually: setup_requires is deprecated and broken (for Cython deps specifically, the install just plain crashes for some), and I need a replacement. And --build-isolation can per design not work in cross compile environments where package installs need to be customized and some packages cannot be "just reinstalled".

Is that really so complicated to understand? I think the base problem really is quite simple...

Basically I need something like setup_requires that actually works, without forcing me to use build isolation since that can't work. (again just per design, the whole idea of letting pip reinstall things on its own just doesn't work if some packages need to be patched in custom ways that right now cannot be feasibly taught to pip since it is way too specialized) Just to explain why no build isolation works, we basically install the affected to-be-patched packages first so pip already finds them present and doesn't attempt to reinstall them and it'll be fine - unless build isolation is active, which is the big problem.

@ghost ghost changed the title With --no-build-isolation pip may try to obtain wheel metadata without ensuring pyproject.toml's build-system.requires is around / wheel build order ignores build-system.requires entirely, apparently Build isolation is fundamentally incompatible with some cross compile environments, but disabling it also disables build-system.requires and the alternative of setup_requires is deprecated and buggy - what should I use now for build deps? Jul 16, 2019
@ghost
Copy link
Author

ghost commented Jul 16, 2019

I renamed the ticket, hopefully that should explain the problem better. It really all comes down again to setup_requires being deprecated, and build-system.requires supposedly to be used instead, but its core feature of "build isolation" just cannot work for us (unless build-system.requires is expanded to work differently and e.g. without build isolation, or we get another option like undeprecated setup_requires, or all of install_requires always installed before a wheel is built so it can be misused as wheel build time deps - not nice, but practical)

@pfmoore
Copy link
Member

pfmoore commented Jul 16, 2019

So is this just something that has never worked? As far as I am aware, Python has never formally supported cross compilation. If there's something that used to work, but has been broken by recent developments, that's one thing. But if you're trying something that has never worked, and no-one has ever really expected it to, then that's likely to be a much harder problem.

(I'm not trying to say that it shouldn't work, or that we don't want to support it, just that it's possible that no-one has ever even looked at this problem before now, and there may be more fundamental issues that you need to solve, rather than assuming that things are expected to work and it's just a matter of getting shallow issues fixed).

@ghost
Copy link
Author

ghost commented Jul 16, 2019

If there's something that used to work, but has been broken by recent developments, that's one thing.

Basically every setup_requires package moving to build-system.requires will break for us (=python-for-android) if it really depends on its build time deps. For these packages that would cause huge regressions for us, going from most of them working other than some Cython dep ones being broken, to none of them working without patching them all, which obviously we prefer not to do since it doesn't scale well

And the thing is, it makes sense to move away from setup_requires unless it is fixed. I get it, it does actually break for some things and if it is deprecated nobody wants to fix it. So I understand every package maintainer who does move away

But to me it looks like this will indirectly cause quite some regressions (for everyone heeding this setup_requires is deprecated call) which right now I see no way for us to avoid - unless the package maintainers are the ones taking on the burden of all this mess, and patching around with ugly hacks to make setup-requires work anyway and sticking to it even though it is deprecated. setup_requires can be made to always work when overriding build_ext and calling Cython manually in a separate process by the way, but this is horrible to do and no package maintainer will want to do this

@ghost
Copy link
Author

ghost commented Jul 16, 2019

Like, just to explain, my own package used setup_requires and worked fine - other than the Cython crash eventually showing up. At first I had an ugly workaround for that, then heard the "use build-system.requires" call and went with it - and now the package does always install right outside of python-for-android, but breaks in python-for-android. So basically I ran into the thing everyone with a package with build deps will run into when going this route, they will all break. (unless pip just happens to shuffle the install order right which is essentially just luck and it may just not do so, or none of the "wrong" deps are in neither install_requires nor build-system.requires so that build isolation can be left enabled and just happens to not reinstall something that can't just be reinstalled. But python-for-android can't know if build isolation is ever better left enabled or not for any package, and both variants may not ever reliably work)

Edit: and basically I can change python-for-android in whatever way to make this work, i just don't know how I could unless build isolation is not mandatory for using the new format. Hence this ticket

@ghost
Copy link
Author

ghost commented Jul 16, 2019

Did any of this make sense? I'd be up for a text chat or anything to explain all the details of this better if anything is still unclear. This is really a fundamental problem for us at python-for-android I would think (at least I can't see any obvious way to work around it given where pip is heading now) so I really want to help you understand what the issue is so hopefully we at p4a, or you at pip, can find a way to make it work, without all build deps using packages doing nasty hacks or ending up patched by us

@dstufft
Copy link
Member

dstufft commented Jul 16, 2019

I think the feature request here is for something akin to what setup_requires used to do, which was sort of like half build isolation, half not. Currently we have two modes pip can be in:

  • The default, build isolation mode which installs ALL dependencies into an isolated environment to build a wheel.
  • No isolation, which requires you to install ALL dependencies manually prior to the pip invocation.

On the other hand, setup_requires was implemented sort of in between these two options, it would install anything it needed to install into what was effectively a temporary semi-isolated environment, however if something was already installed it would use that instead of installing it itself.

So I think this feature request is basically to make it possible to still temporarily install things in build-system.requires, but to do it only if the dependency isn't already satisfied, and to use what's already installed into the system if it is.

Is my understanding of what you're asking for correct?

@ghost
Copy link
Author

ghost commented Jul 16, 2019

@dstufft yes, that is exactly what would fix our issue. The nicest way in terms of design and user expectation (ignoring what a nightmare that may be to implement) might IMHO be just make pip honor build-system.requires with --no-build-isolation instead of e.g. adding back another alternative like setup_requires. Otherwise we might get to a scenario of every packager wondering "ok do I want cool build isolation, or python-for-android to work"? That doesn't sound like a call any package creator should have to make, or a trade-off one would want to have

@pfmoore
Copy link
Member

pfmoore commented Jul 16, 2019

might IMHO be just make pip honor build-system.requires with --no-build-isolation

Given that this would mean that installs could make arbitrary changes to the user's global package list, that would need to be opt-in, and probably handled very carefully. setup_requires, while it used easy_install and had other issues, at least didn't affect the user's global environment. I think that's the point of @dstufft's statement "make it possible to still temporarily install things..." (my emphasis).

But that's a detail issue that could be thrashed out as part of a detailed design of a PR covering this.

@ghost
Copy link
Author

ghost commented Jul 16, 2019

Oh huh, curious - how would it affect the global environment? We kind of have a locked away virtualenv situation with different cross compilers set, so I'm asking because that sounds like a potential problem if pip suddenly tries to write to the system-wide packages instead of virtualenv site-packages (read-only access wouldn't be an issue, like setup_requires used to do)

It also sounds to me like it would be the most intuitive to make --no-build-isolation just enable it, but you make it sound like there is a caveat I am missing which I'm now very curious about

Edit: oh I think I get it, you mean setup_requires just temporarily installed things for build and not permanently unlike install_requires? Interesting, i didn't know that. That may be useful to maintain, yes, I don't think users would expect it to stick around when it's just in build-system.requires (although for us/python-for-android I think it really wouldn't matter either way)

@dstufft
Copy link
Member

dstufft commented Jul 16, 2019

Right, the only way I could see us honoring build-system.requires without --no-build-isolation is to basically still do everything we do with build isolation turned on, just keep the system site-packages on sys.path, and don't reinstall something that already exists in the system site-packages.

Whether that's a good idea or not I'm not sure. I'm really loathe to introduce yet another option as I think more options means both more mental overhead to using pip AND it becomes harder to test the combinatorial explosion of possible option configurations and we have more and more interactions untested.

I wonder if it would make sense to, in the --no-build-isolation case, make it work basically like setup_requires did, try to satisfy the requirement from the current environment, and if it can't then we install into the temporary location that we would normally use for isolation. That's turning an error case into a success case, but it might be watering down what --no-build-isolation means and making it harder for people who are currently trying to use that (or maybe everyone using it are similiar to @Jonast where they just want to use some preinstalled version of X, but don't care about the rest).

@ghost
Copy link
Author

ghost commented Jul 16, 2019

@dstufft ok I thought about this for a second, and as I see it, --no-build-isolation is likely to be used where people want either 1. to avoid the re-compilation time until wheel caching is implemented, 2. situations where build isolation doesn't work (which seems unlikely outside of our limited problem and other really specialized environments where people would need to know what they're doing anyway)

And for use case 1 it would actually be beneficial to not use a temporary environment but keep it around. And use case 2 is something the average user would never do or need, so I think how it behaves in this detail really doesn't matter (since no end user involved that could expect either variant and end up surprised)

So to me it makes more sense to not copy setup_requires here and just install it permanently as install_requires would do, and avoid any sort of temporary environment layer for build-system.requires - at least from my spontaneous thoughts

Edit: just to explain this better, python-for-android would specify --no-build-isolation, not the user. This would all be quite wrapped away for the end user in the end, so from our side there is really no requirement to match setup_requires behavior to avoid surprises

@dstufft
Copy link
Member

dstufft commented Jul 16, 2019

It's not really possible (or well, reasonable I guess is a better word) to do that, because it's entirely possible that the thing we're building isn't going to end up installed at all. We might build it, and then determine we can't install it and we need to install a different version and throw it away.

@ghost
Copy link
Author

ghost commented Jul 16, 2019

@dstufft hm interesting point. Okay that might be unexpected then, I agree. Maybe temporary environment transferred to real site-packages if install goes through then? (Otherwise people expecting to avoid the excessive build time would be kind of left in the rain if even after successful install it's thrown away, I am admittedly also interested in that angle of it myself since this is also a quite big issue with no setup_requires for Cython packages in the corner case of .pxd imports)

@pfmoore
Copy link
Member

pfmoore commented Jul 16, 2019

Rather than installing things into the user's environment that are neither requested items nor runtime dependencies (because those are the only two things pip ever installs under normal operation), in this new mode pip could report what build dependencies it needed to install, and then the user could manually install them as a one-off extra step? Or maybe this new mode is only ever going to be useful for this one scenario, so installing implicit build dependencies alongside runtime dependencies is OK? I'm just wary of making a general solution less usable by allowing it to have "unusual" side effects.

@dstufft
Copy link
Member

dstufft commented Jul 16, 2019

I'm pretty -1 on pip installing build-system.requires to anything but a temporary environment.

As far as what to do with them in a --no-build-isolation context beyond that, I really don't know. I don't have a use case that needs it so I'm not sure what makes the most sense generally. I'm just wary of things that add extra options for specific edge cases because of the earlier mentioned reasons. That doesn't mean we can't add more options if that's truly the best way to solve it, but it'd be great if we can figure out something we can just always do when --no-build-isolation is specified.

@ghost
Copy link
Author

ghost commented Jul 16, 2019

in this new mode pip could report what build dependencies it needed to install, and then the user could manually install them as a one-off extra step

Not sure how that would work for python-for-android unless this is a stable API rather than a text message because otherwise how is python-for-android going to know what to do? And so far pip doesn't really have such an API, right? Because python-for-android would need to tell automatically that it'd need it, otherwise I might as well just make it manually parse pyproject.toml but that seems ugly

As far as what to do with them in a --no-build-isolation context beyond that, I really don't know.

I think there are arguments both for throwing away the temp env later and keeping it around. (Keeping it around because build time saving, throwing it away to keep it clean and potential user expectations) So either I think you just need to pick/decide something or actually add a switch. To me keeping it sounds more useful as a default but I depend so much on the build deps working at all that I'd be really be happy with any outcome here

@dstufft
Copy link
Member

dstufft commented Jul 16, 2019

Note that we do already have a wheel cache, and ideally any wheel we build gets stored in that cache so as long as that cache is working we should (hopefully) only ever build a particular sdist once before we switch to just installing the prebuilt artifact. I think that drastically reduces the usefulness of keeping the temporary directory around.

@ghost
Copy link
Author

ghost commented Jul 16, 2019

For what it's worth, this may be relevant:

I am in other tickets petitioning for a way to specify source only build dependencies e.g. for .pxd deps, because otherwise Cython stuff in build-system.requires makes the hooks.get_metadata_for_build_wheel-based approach to list all dependencies without building it super slow. That's again a niche case but very relevant for python-for-android which needs to do this and separately from the build virtualenv so the wheel caching doesn't help. If that ever happens then this would also solve my speed issues, and probably for many others too (like anyone outside of python-for-android who in general just has Cython build deps)

So I agree for sure in the long term it doesn't seem that vital to keep it around (Edit: and in the short term it'll not be a disaster not to have it either, so I pretty much meant to say seems fine to just always throw it away)

@chrahunt chrahunt added C: PEP 517 impact Affected by PEP 517 processing state: needs discussion This needs some more discussion labels Jul 20, 2019
@ghost
Copy link
Author

ghost commented Jul 21, 2019

I just completed a test today with --no-build-isolation and hardcoding the build-system.requires deps to be installed as a previous step manually, and with this the build ran through. With build isolation enabled it will fail, and without manually previously installing the build-system.requires which in a generic way isn't really that practical it will also fail.

With this I conclude that I'm fairly certain making --no-build-isolation not skip on installing build-system.requires, no matter if a local temporary place just for the build or not, would make things work for python-for-android 👍 so this feature would really help us avoid quite some trouble in the future as more people switch to pyproject.toml

@pradyunsg
Copy link
Member

The nice thing about having pyproject.toml be a static file is that you can parse it and carefully manage your build environment manually.

It's more work but then your use case is also pretty niche. :)

@ghost
Copy link
Author

ghost commented Jul 24, 2019

@pradyunsg I get your point, but in general dealing with pip is already a major pain point in python-for-android, to the point where we had internal discussions if it is worth it at all before taking this new issue into account. The thing is build isolation is great but it comes with its own issues, and the current design pushes it onto us with no real alternative other than even more alternative manual handling

Unless you want us to deviate to completely separate packaging things, it would be really nice if there wouldn't be more and more workarounds pushed onto us... there is only so much extra handling code we can reasonably build and maintain to make setup.py work. (I wrote a new packaging lib from scratch we need to maintain due to no libpip. I already had a hard time getting that merged, because it is not a nice thing to more or less unintentionally maintain)

It's more work but then your use case is also pretty niche. :)

So I'd say in overall this is a bit of a worrying view from where we stand. You can see it like that but it simply adds up. The world is already pretty complicated and nobody else in our team probably even understands this issue well enough to be able to make this ticket. There really is a limit to how much we can reasonably do 🤷‍♀️

setup.py and standard solutions are great. But we can't pile on endless workarounds to keep it working, it's just not realistic.

So summed up: please don't make us do this. I'd have to explain we have to add even more code most don't know how to maintain... 😢

@pradyunsg
Copy link
Member

@Jonast Ah! I see where you're coming from.

Do you have a discuss.python.org account? I think it'd be a good idea to have a broader discussion about how python-for-android uses Python Packaging tooling, and how we could make life easier for y'all! :)

@ghost
Copy link
Author

ghost commented Jul 25, 2019

I do, and I wouldn't mind trying to put together a brief overview of how python-for-android currently operates/interacts with pip if anyone is interested (if that is what you had in mind?) - just tell me where to jump in

@pfmoore
Copy link
Member

pfmoore commented Jul 25, 2019

FWIW, I think we could/should be able to handle things better for projects like Python for Android - it's niche at the moment, maybe, but I'd imagine (and hope!) that it's a growing area.

However, nobody outside of the Python for Android community really knows how your processes work, and to my knowledge there's been little validation from either side that the ways you're working are in line with how Python's packaging standards are evolving. So there's a need for flexibility from both sides.

At the moment, a lot of the issues you're hitting are coming across as "the way packaging works is broken, because it can't cope with our workflow, and we have to do things this way so either we work around this or you need to fix packaging", and that's not really helping anyone to understand the best way forward (the title of this issue is a good example, "fundamentally incompatible with some cross-compile environments" is pretty alarmist). I understand your frustration, but failing to get your point across will only end up in you being more frustrated.

I think there are some basic things that need to be covered as groundwork in any thread that gets started on Discourse.

  1. Cross compilation is (as far as I am aware) something that's never really been considered anywhere in the Python ecosystem - core distutils or setuptools in particular. So there's likely a whole load of low-level issues that people are completely unaware of, and may need addressing before getting to higher-level tools like pip. For example, has anyone done work on how tools should set compatibility tags when the runtime environment differs from the build environment? If you're working around that problem at the moment, we're piling workarounds on workarounds, and that won't end well.
  2. How do we ensure that Python for Android processes are represented in future standards discussions? The discussions are (should be) open, so there's no reason interested parties can't get involved, but obviously that's been happening properly in the past (this isn't specific to PfA, getting niche communities involved is a general issue, but now that you've showed your faces, we need to keep you involved).
  3. Who's going to review existing standards to find the places where they don't sit well with PfA/cross compilation processes? And what will then get done about those places?
  4. This sort of work isn't going to happen if it's all left to the pip/setuptools/flit/... developers. It'll need some people from the PfA community submitting PRs, proposing changes to standards (and defending them), etc.

The alternative is probably that we continue as we are, with individual issues being dealt with on a one-by-one basis, in a way that will feel like a series of workarounds, as there's no focus on the bigger picture. That's probably OK for individual tools, but as you say not for you (and not really for the wider PyPA, as we really don't want to see the packaging community fragmenting the way you suggest could happen).

@ghost
Copy link
Author

ghost commented Jul 25, 2019

but now that you've showed your faces, we need to keep you involved

I've just brought this back up with the other contributors, and so far there is great interest in this! I will still need a day or two so we can talk about the total basics like how we're internally thinking about forming our opinion when weighing in on things, but other than that this sounds like a really cool idea!

I am wondering: documenting how python-for-android works here (in terms of packaging-relevant ways), who the human contact could be if you need a quick response on something, how we possibly want to coordinate things - seems like all this could possibly get a little lost in discourse at some point amidst all the discussions happening. Should we maybe put this info somewhere on our own infrastructure and link it, and update it as discussions go on? Or do you have a good place where you keep your own documentation for how to work with niche groups where this could be added in? (no matter if public or not) Discourse is fine too if you trust it's at least the best place to start with, just thought I would ask

Thanks again for offering a deeper communication here, that sounds like a really cool idea! ❤️

@pfmoore
Copy link
Member

pfmoore commented Jul 25, 2019

Honestly, there's not much like this in place yet, everything that's available is public, in the PEPs, the distutils-sig archives and in Discourse. So it'll all be very much making things up as we go along. But I'm assuming that the existing processes (of public discussions, PEPs, PEP revisions, etc) will not need much changing, so there's not much needed on the process side.

But I would just add a note of caution - Python for Android is still a niche situation. You'll find that most people will have little time or interest in learning the details of how your processes work, so you'll have to do the work of understanding the "normal" workflow assumed by the current PEPs, and explaining the differences as part of proposing any changes. It's not the quickest of processes - not because of lack of interest, just simply because people have very limited time, and other priorities. So you'll need to be patient :-)

@pradyunsg
Copy link
Member

pradyunsg commented Jul 25, 2019

I made a topic for this on Discourse.

@Jonast My preference is you have a living document containing the overview, that you link to, but really, it's up to you how/where you want to host that overview. I've mentioned it in that post but really, there are no hurries to actually posting that and starting the conversation IMO.

https://discuss.python.org/t/packaging-and-python-for-android/2036

@ghost ghost changed the title Build isolation is fundamentally incompatible with some cross compile environments, but disabling it also disables build-system.requires and the alternative of setup_requires is deprecated and buggy - what should I use now for build deps? Build isolation can be a problem with the python-for-android cross compile environment, but disabling it also disables build-system.requires breaking some package installs Jul 25, 2019
@ghost
Copy link
Author

ghost commented Jul 25, 2019

Gave the ticket a less alarmist title as per @pfmoore 's suggestion 🙂 and I will be chiming in on discourse shortly after I gathered some python-for-android developer feedback here: kivy/python-for-android#1932

@pradyunsg living document sounds good I'll write something up and link it on discourse!

Thanks again everyone for the opportunity for us to jump into the discussion, that's pretty cool 👍

@ghost
Copy link
Author

ghost commented Jul 31, 2019

I wrote a description now which explains in more detail how python-for-android uses pip: https://github.com/kivy/python-for-android/blob/develop/doc/source/contribute.rst#how-python-for-android-uses-pip which I also linked in the discussion here: https://discuss.python.org/t/packaging-and-python-for-android/2036

I hope this gives some insight for anyone wondering why we (at python-for-android) currently don't know how to get build isolation working, beyond this basic explanation I gave in a comment above. As a result, I hope it's easier to understand why an added feature of being able to turn build isolation off without disabling/skipping build-system.requires entries that won't always just be e.g. setuptools (usually present anyway) would greatly help us

@ghost
Copy link
Author

ghost commented Oct 1, 2019

I have rethought all of this with a bit of a break, and currently I am stuck:

The discussion so far didn't go much further than "maybe pip should fully support cross compiling" as far as I can see from my side which is an admirable long term goal, but sadly not that helpful with the very current issue of build isolation breaking things.

I have now due to the lack of any other workaround pondered the suggestion to manually parse build-system.requires, but this appears to be way more complicated than it sounds like: unless I am mistaken this would need to be done for every single dependency that has a pyproject.toml, and could require even full dependency resolution outside of pip if the dependencies use each other as build requirements (this is not theoretical by the way, my actual real world package needs to do this since easy_install/setup_requires is broken with some Cython corner cases with again no workaround and it probably won't be fixed since there is pyproject.toml now intended to replace it)

This leaves me so far with no idea how to address this, unless there is a way to disable build isolation where pip itself will still process & install build-system.requires before building each package. Or is a temporary non-portable wheel always built for each package and wheel caching available in pip such that with the right folders anything being rebuilt for build isolation can be avoided...? (sorry if that question sounds confusing, I read something about that but I barely know about pip or wheel internals and it sounded like this could be another solution)

Or does anyone happen to have any other workaround ideas beyond that?

Because the only other option is to introduce a python-for-android recipe for my library which I so far managed to avoid, the exact hackish, non-standard workaround which we are trying to reduce in favor of standard packaging tools. (for this I have spent a few months writing code to make it less and less necessary, build isolation kind of being the last roadblock) So this would be quite a step backwards from using standard tooling which would be sad

@chrahunt
Copy link
Member

chrahunt commented Oct 1, 2019

In your original post, you state:

I am in a cross compilation environment (python-for-android) where --no-build-isolation is required for all builds because some packages cannot be installed without custom patching, so it's not safe for pip to temporarily reinstall some things as is inherently happening with the build isolation

Could you re-package the patched packages so that they are proper distributions then either keep them in a server (referenced with --index-url) or locally (referenced with --find-links)? Then the build isolation environments will pick those versions up. With that there should be no need for --no-build-isolation and everything would work as expected.

Edit: Hold on, this is more complicated than that. 😄

@pradyunsg
Copy link
Member

pradyunsg commented Oct 2, 2019

if the dependencies use each other as build requirements

Well, pip doesn't support dependency cycles in build requirements. A needs B to build and B needs A to build and we only have sources for them -- pip will abort building A and error out. One reasonable restriction that pip had, was requiring build requirements to be available as wheels, so that there's no chance of a build cycle. Otherwise, you'd need to catch cycles.

does anyone happen to have any other workaround ideas beyond that?

My suggestion is that you set up an environment containing build-system.requires, before calling pip install.

Let's say you have a helper script "pip-wrapper", where the install command is processed as:

  • Use pip download to download the archives that pip would've used for installation.
  • Once we have the archives, check the source archives, whether they have a pyproject.toml file (and the contents for build-system.requires (ideally you'd be able to do this in- memory)
    • if no, go ahead. There's no setup needed.
    • if yes, then you have to make a technical choice here: whether you want to allow source build dependencies (I'd say don't)
      • if you don't allow source build dependencies, you can do pip install --only-binary :all: <pyproject.toml-build-requires> and that'll be your build environment set up.
      • if you do allow build-system.requires to be source packages... (again, this is probably a bigger can of worms than you want, consider carefully doing builds for build dependencies and then making them available for the next step, instead of going this way) Then you'll need a mechanism for pip-wrapper to detect cycles and you'd call pip-wrapper install <pyproject.toml-build-requires>.
      • make sure to run pip check here, since you'll have multiple requirements installing and having a not-broken dependency graph will help.
  • finally, call pip install --no-build-isolation <files downloaded by pip download>

Hopefully the above reads as something that's feasible for y'all. I imagine pip-wrapper is a drop in replacement for pip, except it does setup the environment as you'd want it to.

@ghost
Copy link
Author

ghost commented Oct 2, 2019

@chrahunt in general we would really want to get rid of all the patching at all, or doing it without pip. However we patch both non-python things and upstream projects that are super Android unaware. Ideally we wouldn't do that anymore or only via pip, but it might take us a long while to get there and I don't know if we ever will, that is one reason I am trying so hard to get this build isolation thing to work better is to avoid a path where we remain with the best option being patching it out of every package using pyproject.toml and build-system.requires entries to avoid even more patching

@pradyunsg

Well, pip doesn't support dependency cycles in build requirements. A needs B to build and B needs A to build and we only have sources for them -- pip will abort building A and error out.

In any case, things will break if installed in arbitrary order which is what your proposed process doesn't seem to address:

  • Imagine A has build-system.requires entries B and C
  • B has a build-system.requires entry of C as well

Now if I just look at A and go like well let's start with B as first requirement then things will blow up, because I'd need C around first. So collecting all packages, then collecting all their build-system.requires entries and installing them in arbitrary order won't work unless I am very mistaken, dependency resolution really isn't optional and not easy to implement, and would also basically reimplement something like pip around pip

Edit: oh right I guess with a recursive manual pip download maybe this would work. but that isn't ideal for reasons below

if yes, then you have to make a technical choice here: whether you want to allow source build dependencies (I'd say don't)

We support almost(all?) types of dependencies right now so this would be a huge regression

Use pip download to download the archives that pip would've used for installation.

I do that in another place but it's a bit not nice since it sometimes gives a wheel, sometimes an archive, which can quite differ in format. And maybe other formats in the future? It's also zipped up and needs to be extracted, so this adds up to a lot of tasks that pip already does, it feels a little like reimplementing pip's functionality again, redundantly, wrapped around pip. It'd be nice to not rely on this too much if there are other ways, but to be fair it does work


Edit/small addition: Just to point this out, I personally use both source build dependencies, and chained build-system.requires in real world packages of my own, and I know other people who use source build dependencies, so part of the problem is indeed that we don't just use the most trivial pip use cases with python-for-android so most super quick shortcuts don't end up working

@pradyunsg
Copy link
Member

dependency resolution really isn't optional

I mean, isn't this covered by just recursion as I suggested?

Also, you'd want to be building depth-first in that case, so C (from B<-A) then B (from A) then A in case of that graph.


Yea, you'd be redoing some of the archive handling stuff that pip does, yes, but well, I don't have a better suggestion here. It's the least amount of work you'd have to do.

@pradyunsg
Copy link
Member

sometimes gives a wheel, sometimes an archive, which can quite differ in format

You can restrict that with --only-binary and --no-binary options.

Anyway, I think the default output is what you'd want to consume (just skip the wheel files, since there's no "build" step in them).

@pradyunsg
Copy link
Member

Also, IMO, if your main concern now is pip download gives me sources and wheels by default, we have a workable solution. ;)

@ghost
Copy link
Author

ghost commented Oct 2, 2019

Well yes what you said would work, I agree. But it would also reimplement some of the archive handling and also either dependency resolution directly, or indirectly via chained wrapper calls. The first seems like a larger but cleaner amount of code maintain (but also kinda more nonsensical to reimplement that) and the last one with a recursive wrapper script confusing to maintain due to the obscured flow of things, on top of all the wrappers we already have.

I will try to get my point across a bit better: I am trying to see this from the view of a packaging beginner, from a newbie outside. Parsing a pyproject.toml file and installing a single list directly? Sure. Pip download to extract before with a switch for formats, lines to extract? A bit of work and lengthy, but you'll probably figure it out. A recursive wrapper script that calls itself or a built up graph? ... that is where it gets nastier.

Do my points make any sense here? I am really not objecting to the feasibility of the solution, or that it's too complicated in itself, just that I already have 500 lines of code like that in our project the others probably barely understand and this just looks like yet another thing barely anyone will ever understand later

Edit: I also realize this is maybe still the best approach for both projects, or maybe it isn't, I'm not sure yet. I'll sleep about it, I am basically just trying to explain why I'm not that thrilled and unsure about where to go with this

Edit 2: I think I can explain better why this is a liability more so than one might think: right now our final install is essentially pip install -r ..., that's it. We already do dependency collection so far (not really ordering, at least not how pip would) which is really slow for our custom patching in advance, and since conditional dependencies (with the ; filter) are possible this might miss things, but usually it works. Usually. Now if we do this to collect & install the build-system.depends deps for everything too, we essentially end up shoveling more vital handling into this brittle dependency collection that is so slow and complicated that I'd rather want to get rid of it anyway, and we'd even throw another recursive wrapper or an ordering on top. Talk about building more onto a chaotic pile that shouldn't be really there to start with, since that pile just reimplements non-trivial pip handling badly. I hope that point makes any sense, and explains why I am having a hard time with this

@pfmoore
Copy link
Member

pfmoore commented Oct 2, 2019

Thinking about all this, I think this is likely a case where we just have to accept that your situation is extremely niche, and pip (as a general tool) can't really be expected to cope with it.

Build isolation is pip's default mode, and it works well for the vast majority of our users. When it doesn't, we provide an "escape hatch" in --no-build-isolation. But the purpose of that is for pip to "get out of the way", and let projects do their own environment management. We don't try to solve those management issues, we just try not to interfere in the solution.

It sounds like you have a complex environment management issue, so your solution will be complex. But it's unique to you, so a custom solution is the right option here. It also sounds like you would find it useful if some of pip's internal mechanisms (such as dependency resolution) were available for you to use in developing that solution. It's unfortunate that we don't expose those internal mechanisms, and yes that does make your job harder, but it's the reality right now and we are working on improving that - it's just that it's a complex issue and takes time.

So in summary, I think --no-build-isolation is the right solution for you. You'll have to write a custom build process around that, and doing so won't be as easy as you might like, unfortunately, but you're adapting general tools to a very unique workflow, so you should expect to have to do a certain level of customisation.

@pradyunsg
Copy link
Member

Do my points make any sense here?

They do and I empathize. I second @pfmoore though.

@ghost
Copy link
Author

ghost commented Oct 2, 2019

Ok. I understand your position.

However, I think calling doing full dependency resolution, especially as complicated and brittle it is right now without a libpip, (and it is, I already implemented it as well as I could and it's not good code) a "certain level of customisation" which we'll "have to write" is a worrying position for us. Because if we are expected to redo dependency resolution and archive extraction, isn't that already half a package manager? And we already have something similar for our own recipes, so we essentially need to maintain this twice.

I also find you saying "Build isolation is pip's default mode" worrying because that didn't use to be the case, and it is my impression it isn't even widely adopted now - but if it ever is and if only this is the default which we can no longer work with, what will you add tomorrow and expect us to do as a "certain level of customisation" to keep up then? How many components of pip will we be expected to reimplement from scratch?

In conclusion, and this is just my personal opinion, while I can understand your position I also find it of questionable worth to focus our efforts on pip then. Maybe it is long term safer to expand our own independent recipe system into a sort of package manager (which it already is at its basics) and stop chasing after pip, because while it may seem still somewhat feasible now your position doesn't convince me that will be the case tomorrow. This will likely push more effort onto package maintainers and users, but there is only so much we can do.

I also understand you would if at all like to solve cross compilation properly one day instead of concessions and extra options for niche cases. That is nice, but if up to that day you want us to rewrite a libpip around pip to keep up then I just don't think this is a good direction for us to go.

I'll think more about this and talk to the others, but this is my honest feeling about this right now

@videlec
Copy link

videlec commented Feb 22, 2022

This issue is a bit old and maybe the situation evolved. I would be happy to learn about any new development and get pointers to solutions that could have been implemented since 2019.

In fredstro/hilbertmodgroup#5 we came up with a similar problem. SageMath is a Python based mathematic library with a lot of non Python softwares and libraries dependencies. On top of SageMath we have user packages such as https://github.com/fredstro/hilbertmodgroup that would ideally be installed via sage -pip install hilbertmodgroup. However this command is facing a similar problem as described in this issue. Build isolation for SageMath is probably not desirable (in theory, we could have a pure Python SageMath package but that would be a 5G file and the linking would probably end up completely wrong). And providing the --no-build-isolation is problematic for at least two reasons

  • we want the simplest solution for our end users. Having them typing sage -pip install --no-build-isolation is not nice.
  • as @ghost and @pradyunsg mentioned we would have to reinvent the wheel by implementing our own pip-wrapper that would install the build requirements

@pfmoore
Copy link
Member

pfmoore commented Feb 22, 2022

As I noted above, this sounds like another specialised case where you might need to build your own solution.

In the case of SageMath, I wonder whether the following would work.

  1. Have SageMath installed in its own directory, which is not on sys.path.
  2. Create an "expose_sage" package that simply adds a .pth file pointing to the directory where SageMath is installed.
  3. In the base environment, install expose_sage.
  4. Packages that need sage for their build can have a build dependency on expose_sage, which effectively installs sage into the build environment, but via an extremely small .pth file rather than a 5G full install.

There's a bunch of details to work out, and it may not even be suitable for your requirements, but this is the sort of "design your own solution/workflow" approach that I had in mind when I said "a custom solution is the right option here" above. Hopefully it suggests some ideas you can try, if nothing else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C: PEP 517 impact Affected by PEP 517 processing state: needs discussion This needs some more discussion type: feature request Request for a new feature
Projects
None yet
Development

No branches or pull requests

5 participants