-
Notifications
You must be signed in to change notification settings - Fork 28
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
Api mode cffi compile #169
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll be amused to know that just recently moved away from api mode (517169a) largely because it was too hard for people to install correctly.
I'm not opposed to supporting both modes, but we need to do it in a backwards compatible way, and probably have tests to make sure that when things are installed in either mode they still work.
module/ffi_build.py
Outdated
) | ||
|
||
if __name__ == "__main__": | ||
ffi.compile(verbose=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file rename looks like an API break, which will break downstream users, so we should figure out some way to avoid that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
due to the move? There was some oddity in that the ffi py name was shadowed by the ffi object in the module - which is why the rename - it seems like no one would have been able to access ffi.py without some module import path import lib magic. But I would definitely like it to be as compatible as possible. I believe in init.py I pull that back in as ffi in non api mode to make the change transparent externally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that sounds good to me, thanks.
@@ -0,0 +1,3 @@ | |||
[build-system] | |||
requires = ["setuptools >= 64"] | |||
build-backend = "setuptools.build_meta" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on why this is necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pyproject.toml is the defacto standard now and all projects are supposed to have one to select which build system is used - without it editable installs complain and will soon be disallowed (because the editable part needs to know which build system to use to install editable).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh, I had never heard of "editable installs", TIL, thanks. Is there any chance we can completely get rid of setup.py? Last I had looked at pyproject.toml it wasn't fully feature parity with setup.py.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately in order to support stuff like api mode it's required, migration from .cfg to pyproject.toml is pretty doable, but feels like it might want to be separate to this PR.
I didn't;t know it used to be that way but can certainly understand that it's a bit of a pain - would love to provide the high performance option whilst preserving the easier route for folks by default. Will respond to the rest inline. |
Ok, if you rebase this you shouldn't have the language-python based failures any more, I just wrote the code to drop our dependency on it. In no particular order, here are some things I'd like to see before landing this:
If we can do that, I think I'm happy to merge this. One question is: should we have the env var at all? What about just figuring out if the user has a C compiler available and using it? |
Great. I'll try and take a look at this in the next few days. I'll try and find out if we need to add pyproject.toml to the manifest. It's possible not if the generated portion remains in setup.cfg. They can both be used and maybe modifying that would be best as a separate change? It's definitely possible to just determine if the compiler is available - however I was concerned with the way installs work with pip - by default they will pull all dependencies fresh and build against the non installed version. I guess that would still work either way, perhaps better as it could ensure builds of the dependencies were made in api mode. I can experiment. Will rebase against your changes! The install mode global is a great idea. I'll propagate that onwards to cairocffi pangocffi and pangocairocffi. |
386aa5a
to
2d650b0
Compare
Following up:
I now have a pair commits and it's rebased off yours.
Checked on this and we should not. https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html says it's included by default and would have to be explicitly excluded in MANIFEST.in. I have modified that to exclude the ABI mode product (see below)
Agreed - I'm a little confused about how to go about this - and also found difficulties in controlling setup.py in a fallback way (see below). It's possible we could reintroduce an env var to force a particular mode for testing?
At first I considered trying to have After taking a step back I realized that ABI mode can also be precompiled which saves on import time. This works like so:
We also define some constants What remains is to figure out a multi-mode test strategy. |
6af8cda
to
012b318
Compare
I've noticed some issues with this approach (not so much for xcffib but for libraries wanting to build onto like cairocffi. I will take a look at resolving these in a way that allows those to be build api mode - it basically hangs on being able to grab the non compiled ffi from ffi_build but i don't want that to parse cdefs multiple times. Just a little fiddly, but i'm sure doable. |
f177735
to
08e767e
Compare
I had originally tried defining constants in the complex xcffi library. Which is doable but in so doing I had redefined the ffi for each mode which was inefficient. I also realized I could do away with that entirely and just determine which is which in init mode. After a bit of restructuring I think I have a workable solution which allows packages which depend on xcffib to pull the api version of the ffi to use as an include on their ffi. It seems to work well. I haven't added tests yet. Current state is in the PR. I'll do further checks on cairocffi to make sure this works as needed downstream. If you have suggestions on how to write tests for all modes I'd be interested. This should be pretty transparent for users. It should even work without running setup from the unpacked source. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is looking pretty nice, a few small comments.
In terms of testing, however we can make sure CI runs both targets is fine with me. Here is one (though perhaps not the best/only) way I can imagine it working:
- change the
xcffib
build target to also runxcffib/ffi_build.py
- change CI to install a C compiler, then have it do
make check && apt-get autoremove <the C compiler> && make clean && make check
That way we can test both modes. Ideally make check
would check both and we wouldn't have to uninstall the C compiler, but I'm not really sure how to mask it when it's still installed. maybe CC=/bin/false
is enough? not sure.
module/ffi_build.py
Outdated
ffi_api.compile(verbose=True) | ||
return ffi_api | ||
except Exception: | ||
warn("Falling back to precompiled python mode") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be a warn()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the warning seems useful from a debug perspective - it's actually hard to get things to show up from the build subprocesses and by default nothing will show for pip install
unless pip install -v
is used. Can still change if desired.
It turns out I'm not actually sure if we are running pip install on the package before testing - so I suspect we are already falling back to out of line abi mode. In which case the simplest may be to do a series of pytest with a build in-between of the ffi using |
I've modified the makefile to check all modes. |
on newer pythons, which makes sense, since:
seems to happen on older pythons. I see:
So I wonder if we can directly import it somehow? I played around a bit and couldn't figure it out. |
Evidently the thing to do is to import setup tools which we already rely on and sub imports / shims those errors. Updated the PR. |
18f7c05
to
3976e04
Compare
Ive fixed the derived imports which change name slightly. This appear to now work. |
CI checks are failing for reasons I don't understand - looks like missing xcb and cffi? |
Makefile
Outdated
python3 -c "import xcffib; assert xcffib.cffi_mode == 'abi_precompiled'" | ||
pytest-3 -v --durations=3 -n auto | ||
# check api mode | ||
python3 xcffib/ffi_build.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it generates stuff in the root directory, shouldn't it really live in module/? If not, why not? Can we add it to the clean
target if not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does. As does the setup which would install those libs). But it is doable to have them built in xcffib/
and to adjust adjust the import paths in __init__.py
. I’ll do that and write back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now compiles in temp and copies in. The extensions will be under xcffib/
Yeah, very odd. The pytest-3 call right above that works, which should run the very same import line, IIUC. |
I've modified the PR to include a more isolated environment and try to set the PYTHON var used for python in the workflow. |
Yeah, I think this test failure is my fault, let me see if I can figure it out. |
Bit of an embarrassing bug :). Can you see if #172 fixes it for you? |
Fantastic! Resolved the comments and have squashed the commits for a clean history. |
600f3f9
to
5689a2d
Compare
I think it'll fail on pushd & popd too, or at least it did for me locally:
I think it's fine to make this into a script that we call, vs. having line-continuation-make nonsense. |
I have an easy fix and will upload |
latest fails may be prior to my change to have the outputs copied individually? I don't see the copy lines in the logs. Wondering if I'm missing something. |
I think it's because both check-api and check-abi really have a dependency on xcffib, but it's not declared in the current makefile? or at least, I hope that's it :) |
good catch - thanks, have fixed |
There's still a race in the Makefile which I'm tracking down, it's the root of the failures. Have recreated here. |
Gah! How frustrating. This works locally. I'm fairly convinced this is a race. But I'm not quite sure why. I'm going to run this under Debian and try and figure it out. |
Classic computers. I'm afk for the next few days, but I can take a look early next week too. |
attempt to compile xcffib by default. This makes a pre-parsed ffi we can import without parsing cdefs at import time. If c compilation API mode fails, fall back to ABI mode but still precompile. Finally if the compiled library is missing for some reason fall back to import time ABI mode. xcffib.cffi_mode reflects which option was used.
Thanks for your efforts on this! |
Yeah, sorry it's taken me a while to get to it. I hope I'm pretty close... let me know what you think of that patch and I can fold it in and clean it up as appropriate once I get it working. |
It looks great to me - and I think using sh will be a lot cleaner and avoid the race I had! |
b529d72
to
c2ce3b7
Compare
...this way we can actually compile for api mode again. Signed-off-by: Tycho Andersen <[email protected]>
Ok, looks like we still have one race, but I don't really want to fight it that hard. If I can't figure it out, I'll just rip out parallel test execution, it's not that bad to have them go serially. One question I have, that maybe we talked about before but I have since forgotten is: under what circumstances does |
In particular, I would like to test it, but I don't understand how. |
I'm so confused as to why it clashes like that. Thanks for looking into it. abi_precompiled corresponds to this doc https://cffi.readthedocs.io/en/stable/overview.html#out-of-line-abi-level a It works by making a python lib that can be imported and uses ffilib which is slower than c. It's precompiled in the sense that the Cdef parsing has been done and made into opaque structures in the python lib which gets imported. abi mode is this one https://cffi.readthedocs.io/en/stable/overview.html#simple-example-abi-level-in-line should make it possible to download the library fresh and import it without install - in which case there would be no share lib made (no setup tools install has run). I think a fresh checkout (with python gen run) would do the trick and an import on that library. It will parse cdefs at import time. |
I am curious: how would one do that? When we ship the thing via pypi, the setuptools cffi bits will (presumably) always run, and so this mode is really only for development, IIUC? If so, since we're not really using it for local testing any more, I wonder if we should fail here and declare it as explicitly unsupported since we can't test it with the new install-to-test scheme. |
Cffi code is much faster when compiled with a c compiler (API mode) rather than using libfffi (ABI mode) - however this requires a compiler to be installed .
This change allows an install to be made using
XCFFIB_API_MODE=1 pip3 install xcffib
Various other projects (cairocffi, pangocffi and pangocairocffi) can then benefit from similar changes.
During installation
XCFFIB_API_MODE=0
orXCFFIB_API_MODE
not set defaults to the previous ABI mode install which incurs lever overhead on load, runs the ffi_build as before and will dynamically translate arguments to C using the general libffi.During installation
XCFFIB_API_MODE=1
compiles a shared library / C extension which dynamically depends on xcd.At runtime if the shared library / C extension is present it will be used (unless
XCFFIB_API_MODE=0
). The user does not have to arrange forXCFFIB_API_MODE=1
to be set. If the extension is not present, the old behavior is used.To make this work I modified the CPU checks to be portable, and added the relevant FFI calls / setup.py calls to have the C extension be built.
The language-python requirement changes may not be needed - I was working around a happy bug / clash which I think you've noted in a report for haskell.
I don't suggest this be default, but API mode is a potential substantial performance improvement (especially for cairo, which needs xcffib to be build in API mode to work, C and py ffi can't be mixed with ffi.include())