wheel icon indicating copy to clipboard operation
wheel copied to clipboard

Binary Wheels not linking against libpython

Open agronholm opened this issue 8 years ago • 16 comments

Originally reported by: Armin Ronacher (Bitbucket: mitsuhiko, GitHub: mitsuhiko)


I'm currently using various different hacks that sometimes fail to create wheels with the following qualities:

  • they are manylinux1 and mac wheels
  • they use cffi to load shared libraries
  • they do not link against libpython

Sadly they cannot be generated currently. The only way that sort of works is to put the shared libraries into the purelib root and it's not really purelib, is it.

Ideally there was a way to say: this wheel is py2.py3, has a manylinux/mac platform but no python abi.


  • Bitbucket: https://bitbucket.org/pypa/wheel/issue/185

agronholm avatar Jun 08 '17 14:06 agronholm

Original comment by Daniel Holth (Bitbucket: dholth, GitHub: dholth):


I was looking at the get_tag() function. It appears you can already specify --plat-name= while building a 'pure' wheel if you want. This is of course less convenient than having wheel find the current platform name automatically, and is 'less pure' than using platlib.

agronholm avatar Jul 08 '17 02:07 agronholm

Original comment by Daniel Holth (Bitbucket: dholth, GitHub: dholth):


It would probably look a bit like https://bitbucket.org/pypa/wheel/pull-requests/69/add-py-limited-api-flag-to-control-abi3/diff

I'm surprised we haven't done this already. Seems like it has come up in the past. I'm personally a big fan of cffi.

agronholm avatar Jun 12 '17 20:06 agronholm

@mitsuhiko Could you point me to a project I could use to verify that the issue is still relevant?

agronholm avatar Aug 06 '17 11:08 agronholm

Can we automatically somehow detect cases such as this?

agronholm avatar Oct 30 '17 16:10 agronholm

@agronholm you can look at getsentry/symbolic which runs into this issue. We solved it by patching the hell out of bdist_wheel through our getsentry/milksnake library.

mitsuhiko avatar Oct 30 '17 17:10 mitsuhiko

Ok so let me just ask: what would you like me to change in wheel so I could close this issue?

agronholm avatar Nov 01 '17 19:11 agronholm

@agronholm it needs a mode where you can define that your wheel contains binary data but is not linked against libpython. Effectively have something that lets me do what this does: https://github.com/getsentry/milksnake/blob/8351a8aa4ac800c586733602a55c1c02f0d19923/milksnake/setuptools_ext.py#L261-L278

mitsuhiko avatar Nov 01 '17 20:11 mitsuhiko

Hm, how about a universal_binary option (can anyone think of a better name?)? All it would do is add the architecture tag to the wheel. It would only take effect if no (traditional) C extensions are present.

I also thought about automatic detection of this condition but it would be too error prone.

agronholm avatar Nov 05 '17 12:11 agronholm

@agronholm would that also work with app that bundle .so and load them with ctypes?

Currently, I'm bundle the .so as package_data and relying on @mitsuhiko trick to flag the wheel as not-pure.

But it doesn't trick auditwheel tool and it will continue to raise WheelToolsError('Cannot add platforms to pure wheel')

proppy avatar Nov 13 '17 08:11 proppy

@proppy sadly there are limitations in distutils itself which means this flag can only be flagged by building a c extension. So what milksnake does not is building an empty C file and then later overrides the .so with the one from the milksnake build process.

mitsuhiko avatar Nov 13 '17 08:11 mitsuhiko

@mitsuhiko is that something that could be patched up in wheel or does it have to be fixed in distutils?

proppy avatar Nov 14 '17 04:11 proppy

@agronholm @mitsuhiko after looking at distutils source code and stealing a lot of milksnake awesome magic, I ended up the following workaround:

class UniversalBinaryDistribution(Distribution):
    def __init__(self, *args, **kwargs):
        super(UniversalBinaryDistribution, self).__init__(*args, **kwargs)
        base_bdist_wheel = self.cmdclass.get('bdist_wheel', bdist_wheel)
        class UniversalBdistWheel(base_bdist_wheel):
          def get_tag(self):
              rv = base_bdist_wheel.get_tag(self)
              return ('py2.py3', 'none',) + rv[2:]
        self.cmdclass['bdist_wheel'] = UniversalBdistWheel
        base_build_ext = self.cmdclass.get('build_ext', build_ext)
        class UniversalBuildExt(base_build_ext):
            def build_extension(self, ext):
                ext_filename = self.get_ext_filename(ext.name)
                shutil.copy2(ext_filename,
                             os.path.join(self.build_lib, ext_filename))
            def get_ext_filename(self, ext_name):
                ext_path = ext_name.split('.')
                ext_suffix = new_compiler().shared_lib_extension
                return os.path.join(*ext_path) + ext_suffix
        self.cmdclass['build_ext'] = UniversalBuildExt


setup(...
    ext_modules=[
        Extension('module.path.to..precompiledlib.without.so',
                  sources = [])
    ],
    distclass=UniversalBinaryDistribution,
)

It replaces the C compilation process with a shutil.copy2 and the resulting wheels seems to pass auditwheel validation.

Is that something we could consolidate somewhere? (either in distutils or wheels)

proppy avatar Nov 14 '17 06:11 proppy

One problem is that a lot of code just checks for .extension_modules or what that attribute is called. If it's empty it assumes pure eggs/wheels. There is a ton of code that just does that. All the workarounds I found break in fancy ways sadly. Even if auditwheel passes there is still embedded metadata which is kinda wrong.

mitsuhiko avatar Nov 14 '17 11:11 mitsuhiko

What metadata does that affect? IIRC wheel itself has the final say over the resulting wheel filename, so that's something I can fix here.

agronholm avatar Nov 14 '17 11:11 agronholm

@agronholm the metadata that changed for me was: Root-Is-Purelib: false in /WHEEL

@mitsuhiko are you referring to the ext_modules parameter for the setup() call or build_ext.extensions internal variable?

proppy avatar Nov 15 '17 04:11 proppy

The extensions parameter that is set on build_ext and others I think. I would need to look into it again though.

mitsuhiko avatar Nov 15 '17 07:11 mitsuhiko