Repository: python-pillow/Pillow

Issue:
Can't build Pillow on Debian armhf
### What did you do?

On a Debian armhf machine, I tried running: (keeping only the relevant parts)

```
$ python setup.py install
...
Looking for zlib
Checking for include file zlib.h in /tmp/pip-rje8ft8s-build/libImaging
Checking for include file zlib.h in /usr/include
Found zlib.h
Couldn't find library z in ['/usr/lib', '/usr/lib']
...
The headers or library files could not be found for zlib,
a required dependency when compiling Pillow from source.

Please see the install instructions at:
   https://pillow.readthedocs.io/en/latest/installation.html

Traceback (most recent call last):
...
```

The problem is that [the code which detects the library dirs to search in](https://github.com/python-pillow/Pillow/blob/master/setup.py#L313-L341) just doesn't work at all on Debian.

It suffers from many problems, but let's start with the ARM-related protion:

```python
                    elif platform_ in ["arm", "armv7l"]:
                        _add_directory(library_dirs,
                                       "/usr/lib/arm-linux-gnueabi")
                        break
```

This is missing the `/usr/lib/arm-linux-gnueabihf` directory for the armhf architecture (ARM 32 bits with hardware floating point)

More fundamentally, `platform_` in this case comes from `arch_tp = (plat.processor(), plat.architecture()[0])`. On a Debian ARM system, that's `('', '32bit')`, which means we never even pass in the above condition anyway.

But in fact, this whole architecture detection code suffers from the same problem, because even on a less exotic system, for example x86_64, then `arch_tp = ('', '64bit')`. Basically Python's `platform.processor()` always seems to return an empty string on Debian.

This means that the conditional below can never evaluate to `True`, even in the case it was designed for:

```python
            if arch_tp == ("x86_64", "32bit"):
                # 32-bit build on 64-bit machine.
                _add_directory(library_dirs, "/usr/lib/i386-linux-gnu")
```

The only reason Pillow builds at all on Debian x86_64 and i386 is because those two conditions are two broad:

```python
                    if platform_ in ["x86_64", "64bit"]:
                        _add_directory(library_dirs, "/lib64")
                        _add_directory(library_dirs, "/usr/lib64")
                        _add_directory(library_dirs,
                                       "/usr/lib/x86_64-linux-gnu")
                        break
                    elif platform_ in ["i386", "i686", "32bit"]:
                        _add_directory(library_dirs, "/usr/lib/i386-linux-gnu")
                        break
```

The `64bit` and `32bit` also match AArch64 and ARM machines, where the above multiarch libdirs really shouldn't be added as they are the wrong ones.

---

For reference, the Debian package of Pillow uses this patch (among others) : 

```patch
diff --git a/setup.py b/setup.py
index 4948faf..02676ba 100755
--- a/setup.py
+++ b/setup.py
@@ -311,61 +311,6 @@ class pil_build_ext(build_ext):
                 _add_directory(include_dirs, "/usr/X11/include")

         elif sys.platform.startswith("linux"):
-            arch_tp = (plat.processor(), plat.architecture()[0])
-            if arch_tp == ("x86_64", "32bit"):
-                # 32-bit build on 64-bit machine.
-                _add_directory(library_dirs, "/usr/lib/i386-linux-gnu")
-            else:
-                for platform_ in arch_tp:
-
-                    if not platform_:
-                        continue
-
-                    if platform_ in ["x86_64", "64bit"]:
-                        _add_directory(library_dirs, "/lib64")
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/x86_64-linux-gnu")
-                        break
-                    elif platform_ in ["i386", "i686", "32bit"]:
-                        _add_directory(library_dirs, "/usr/lib/i386-linux-gnu")
-                        break
-                    elif platform_ in ["aarch64"]:
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/aarch64-linux-gnu")
-                        break
-                    elif platform_ in ["arm", "armv7l"]:
-                        _add_directory(library_dirs,
-                                       "/usr/lib/arm-linux-gnueabi")
-                        break
-                    elif platform_ in ["ppc64"]:
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/ppc64-linux-gnu")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/powerpc64-linux-gnu")
-                        break
-                    elif platform_ in ["ppc"]:
-                        _add_directory(library_dirs, "/usr/lib/ppc-linux-gnu")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/powerpc-linux-gnu")
-                        break
-                    elif platform_ in ["s390x"]:
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/s390x-linux-gnu")
-                        break
-                    elif platform_ in ["s390"]:
-                        _add_directory(library_dirs, "/usr/lib/s390-linux-gnu")
-                        break
-                else:
-                    raise ValueError(
-                        "Unable to identify Linux platform: `%s`" % platform_)
-
-                # XXX Kludge. Above /\ we brute force support multiarch. Here we
-                # try Barry's more general approach. Afterward, something should
-                # work ;-)
                 self.add_multiarch_paths()

                 # termux support for android.
```

That's right, they just completely remove the attempt at automatically detecting things, and directly use the `dpkg-architecture` code path (in `self.add_multiarch_paths()`), which otherwise gets skipped by the bad conditionals.

It seems like this part of `setup.py` needs to be reworked. 😕 


Discussion:
You know, it would be helpful if Debian wouldn't silently patch stuff like this and not file some sort of upstream bug. It might even be helpful to their users when they want to build code not in a deb.

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -312,62 +312,47 @@ def build_extensions(self):
 
         elif sys.platform.startswith("linux"):
             arch_tp = (plat.processor(), plat.architecture()[0])
-            if arch_tp == ("x86_64", "32bit"):
-                # 32-bit build on 64-bit machine.
+            # This should be correct on debian derivatives.
+            if plat.dist()[0].lower() in ('debian', 'ubuntu'):
+                # If this doesn't work, don't just silently patch
+                # downstream because it's going to break when people
+                # try to build pillow from source instead of
+                # installing from the system packages.
+                self.add_multiarch_paths()
+
+            elif arch_tp == ("x86_64", "32bit"):
+                # Special Case: 32-bit build on 64-bit machine.
                 _add_directory(library_dirs, "/usr/lib/i386-linux-gnu")
             else:
+                libdirs = {
+                    'x86_64':  ["/lib64", "/usr/lib64", "/usr/lib/x86_64-linux-gnu"],
+                    '64bit':   ["/lib64", "/usr/lib64", "/usr/lib/x86_64-linux-gnu"],
+                    'i386':    ["/usr/lib/i386-linux-gnu"],
+                    'i686':    ["/usr/lib/i386-linux-gnu"],
+                    '32bit':   ["/usr/lib/i386-linux-gnu"],
+                    'aarch64': ["/usr/lib64", "/usr/lib/aarch64-linux-gnu"],
+                    'arm':     ["/usr/lib/arm-linux-gnueabi"],
+                    'armv71':  ["/usr/lib/arm-linux-gnueabi"],
+                    'ppc64':   ["/usr/lib64", "/usr/lib/ppc64-linux-gnu",
+                                "/usr/lib/powerpc64-linux-gnu"],
+                    'ppc':     ["/usr/lib/ppc-linux-gnu",
+                                "/usr/lib/powerpc-linux-gnu"],
+                    's390x':   ["/usr/lib64", "/usr/lib/s390x-linux-gnu"],
+                    's390':    ["/usr/lib/s390-linux-gnu"],
+                    }
+                    
                 for platform_ in arch_tp:
-
+                    dirs = libdirs.get(platform_, None)
                     if not platform_:
                         continue
-
-                    if platform_ in ["x86_64", "64bit"]:
-                        _add_directory(library_dirs, "/lib64")
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/x86_64-linux-gnu")
-                        break
-                    elif platform_ in ["i386", "i686", "32bit"]:
-                        _add_directory(library_dirs, "/usr/lib/i386-linux-gnu")
-                        break
-                    elif platform_ in ["aarch64"]:
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/aarch64-linux-gnu")
-                        break
-                    elif platform_ in ["arm", "armv7l"]:
-                        _add_directory(library_dirs,
-                                       "/usr/lib/arm-linux-gnueabi")
-                        break
-                    elif platform_ in ["ppc64"]:
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/ppc64-linux-gnu")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/powerpc64-linux-gnu")
-                        break
-                    elif platform_ in ["ppc"]:
-                        _add_directory(library_dirs, "/usr/lib/ppc-linux-gnu")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/powerpc-linux-gnu")
-                        break
-                    elif platform_ in ["s390x"]:
-                        _add_directory(library_dirs, "/usr/lib64")
-                        _add_directory(library_dirs,
-                                       "/usr/lib/s390x-linux-gnu")
-                        break
-                    elif platform_ in ["s390"]:
-                        _add_directory(library_dirs, "/usr/lib/s390-linux-gnu")
-                        break
+                    for path in dirs:
+                        _add_directory(library_dirs, path)
+                    break
+                
                 else:
                     raise ValueError(
                         "Unable to identify Linux platform: `%s`" % platform_)
 
-                # XXX Kludge. Above /\ we brute force support multiarch. Here we
-                # try Barry's more general approach. Afterward, something should
-                # work ;-)
-                self.add_multiarch_paths()
-
                 # termux support for android.
                 # system libraries (zlib) are installed in /system/lib
                 # headers are at $PREFIX/include


Test patch: