Repository: scikit-image/scikit-image

Issue:
Reduce the wheel size relative to 0.19.0 for upcoming releases
## Description

Continuing the discussion from https://github.com/scikit-image/scikit-image/issues/6086#issuecomment-986063471

We have seen a recent increase in wheel size by nearly a factor of two for 0.19.0 vs. 0.18.3. In order to be a good citizen, preserving PyPI and CI resources we should try to reduce this if possible.

So far, one difference I noticed was that the 0.19.0 wheels have various `.c`, `.cpp` and `.pyx` files that should not need to be in the wheel. 




Discussion:
I checked for one of the linux wheels that the wheel size without the extra source files goes from 59.7 MB -> 52.4MB so ~7.3 MB improvement. The difference is bigger once the wheel is unzipped: 234 MB -> 144 MB.

@hmaarrfk, do you happen to know why these source files are suddenly being included in the wheels? We were already using `cibuildwheel` for 0.18.3 too, just without the automated deployment. I just tried `pip wheel . -vv` locally and the resulting wheel does not include these extra source files.

wow! no i really don't tbh. the difference in size is quite staggering. I didn't expect that.
I don't know about any specific recent changes in wheel building for scikit-image or other packages, but for whatever reason including the cythonized `.c` files in the pypi wheels seems to be the norm.  

And the cythonized .c files are _enormous_.  Something like x20 the number of lines of the associated .pyx files.
> including the cythonized .c files in the pypi wheels seems to be the norm.

Maybe, but I looked at a wheel from the most recent SciPy and PyWavelets releases and see only `.py` and `.so` files. SciPy is using a different build toolchain, but PyWavelets is using a `cibuildwheel` script that I copied from the one here, so I don't see why it should be different!
Interesting!  Then I think we want to get to the bottom of this and then file issues wherever we see extra c files creating extra large packages (e.g. pytables wheels have the c files as well).
To avoid use of deprecated distutils, we recently changed some imports to use setuptools instead. For some reason the following 1 line change to setup.py gets us back to smaller wheels (no source files included) when using `python setup.py bdist_wheel`.

```Python
from setuptools.command.build_py import build_py
```
vs. the import we had in 0.18.3
```Python
from distutils.command.build_py import build_py
```

When the import is from `distutils` the wheel size is ~40MB vs. when the import is from `setuptools` it ends up ~63 MB.

Another option that can result in smaller wheels is to strip debug symbols by adding `-Wl,--strip-debug` to the LDFLAGS environment variable. For example, NumPy does this here: https://github.com/MacPython/numpy-wheels/blob/df5cb747e055ec5c60298c936e9c68709f05bcd6/env_vars.sh#L7

Using that flag further shrinks the wheel from ~40MB to ~14MB and even the uncompressed size is only around 32MB!

The same applies when `pip wheel` is used instead of `python setup.py bdist_wheel`
Good catch on the `setuptools` vs `distutils`!  And I _think_ that with `setuptools` the issue is that `include_package_data=True` which will package up all the files, while if you explicitly list what extra files you want with the `package_data` kwarg then I think it won't include the extra pyx and c files.

Then if you strip the debug symbols you get the additional amazing space savings!

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -116,6 +116,8 @@ def build_extensions(self):
         else:
             compile_flags += ['-fopenmp']
             link_flags += ['-fopenmp']
+        if 'SKIMAGE_LINK_FLAGS' in os.environ:
+            link_flags += [os.environ['SKIMAGE_LINK_FLAGS']]
 
         if self.can_compile_link(compile_flags, link_flags):
             for ext in self.extensions:
@@ -244,7 +246,7 @@ def configuration(parent_package='', top_path=None):
         extras_require=extras_require,
         python_requires='>=3.8',
         packages=setuptools.find_packages(exclude=['doc', 'benchmarks']),
-        include_package_data=True,
+        include_package_data=False,
         zip_safe=False,  # the package can run out of an .egg file
         entry_points={
             'console_scripts': ['skivi = skimage.scripts.skivi:main'],
diff --git a/skimage/_build.py b/skimage/_build.py
--- a/skimage/_build.py
+++ b/skimage/_build.py
@@ -60,10 +60,13 @@ def cython(pyx_files, working_path=''):
                 process_tempita_pyx(pyxfile)
                 pyx_files[i] = pyxfile.replace('.pyx.in', '.pyx')
 
-        # Cython doesn't automatically choose a number of threads > 1
-        # https://github.com/cython/cython/blob/a0bbb940c847dfe92cac446c8784c34c28c92836/Cython/Build/Dependencies.py#L923-L925
-        cythonize(pyx_files, nthreads=cpu_count(),
-                  compiler_directives={'language_level': 3})
+        # skip cythonize when creating an sdist
+        # (we do not want the large cython-generated sources to be included)
+        if 'sdist' not in sys.argv:
+            # Cython doesn't automatically choose a number of threads > 1
+            # https://github.com/cython/cython/blob/a0bbb940c847dfe92cac446c8784c34c28c92836/Cython/Build/Dependencies.py#L923-L925
+            cythonize(pyx_files, nthreads=cpu_count(),
+                      compiler_directives={'language_level': 3})
 
 
 def process_tempita_pyx(fromfile):


Test patch:
diff --git a/.github/workflows/wheel_tests_and_release.yml b/.github/workflows/wheel_tests_and_release.yml
--- a/.github/workflows/wheel_tests_and_release.yml
+++ b/.github/workflows/wheel_tests_and_release.yml
@@ -48,6 +48,8 @@ jobs:
           CIBW_SKIP: "*-musllinux_*"
           CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.cibw_manylinux }}
           CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.cibw_manylinux }}
+          CIBW_ENVIRONMENT_PASS_LINUX: SKIMAGE_LINK_FLAGS
+          SKIMAGE_LINK_FLAGS: "-Wl,--strip-debug"
       - uses: actions/upload-artifact@v2
         with:
           name: wheels
@@ -88,6 +90,8 @@ jobs:
           CIBW_ARCHS_LINUX: aarch64
           CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.cibw_manylinux }}
           CIBW_MANYLINUX_I686_IMAGE: ${{ matrix.cibw_manylinux }}
+          CIBW_ENVIRONMENT_PASS_LINUX: SKIMAGE_LINK_FLAGS
+          SKIMAGE_LINK_FLAGS: "-Wl,--strip-debug"
       - uses: actions/upload-artifact@v2
         with:
           name: wheels
@@ -151,7 +155,7 @@ jobs:
           CPPFLAGS: "-Xpreprocessor -fopenmp"
           CFLAGS: "-Wno-implicit-function-declaration -I/opt/local/include/libomp"
           CXXFLAGS: "-I/opt/local/include/libomp"
-          LDFLAGS: "-Wl,-rpath,/opt/local/lib/libomp -L/opt/local/lib/libomp -lomp"
+          LDFLAGS: "-Wl,-S -Wl,-rpath,/opt/local/lib/libomp -L/opt/local/lib/libomp -lomp"
 
       - uses: actions/upload-artifact@v2
         with:
@@ -209,6 +213,8 @@ jobs:
         env:
           CIBW_BUILD: "cp310-*"
           CIBW_ARCHS_WINDOWS: ${{ matrix.cibw_arch }}
+          # -Wl,-S equivalent to gcc's -Wl,--strip-debug
+          LDFLAGS: "-Wl,-S"
 
       - name: Build Windows wheels for CPython
         run: |
@@ -217,6 +223,8 @@ jobs:
           CIBW_BUILD: "cp3?-*"
           CIBW_SKIP: "cp36-* cp37-*"
           CIBW_ARCHS_WINDOWS: ${{ matrix.cibw_arch }}
+          # -Wl,-S equivalent to gcc's -Wl,--strip-debug
+          LDFLAGS: "-Wl,-S"
 
       - uses: actions/upload-artifact@v2
         with:
@@ -236,13 +244,13 @@ jobs:
         name: Install Python
         with:
           python-version: '3.9'
-      
+
       - name: Install Twine
         run: |
           python -m pip install --upgrade pip
           pip install -r requirements/build.txt
           pip install twine
-      
+
       - uses: actions/download-artifact@v2
         id: download
         with: