forked from numpy/numpy
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ENH: add hook and test for PyInstaller.
Adding this special hook file tells PyInstaller what files a self contained NumPy application needs to run. A test is encluded to verify that the hook still finds all necessary files. Closes numpy#17184.
- Loading branch information
Showing
6 changed files
with
133 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# --- Copyright Disclaimer --- | ||
# | ||
# In order to support PyInstaller with numpy<1.20.0 this file will be | ||
# duplicated for a short period inside PyInstaller's repository [1]. However | ||
# this file is the intellectual property of the NumPy team and is under the | ||
# terms and conditions outlined their repository [2]. | ||
# | ||
# [1]: PyInstaller: https://github.com/pyinstaller/pyinstaller/ | ||
# [2]: NumPy's license: https://github.com/numpy/numpy/blob/master/LICENSE.txt | ||
# | ||
"""This hook should collect all binary files and any hidden modules that numpy | ||
needs. | ||
Our (some-what inadequate) docs for writing PyInstaller hooks are kept here: | ||
https://pyinstaller.readthedocs.io/en/stable/hooks.html | ||
""" | ||
from PyInstaller.compat import is_conda, is_pure_conda | ||
from PyInstaller.utils.hooks import collect_dynamic_libs, is_module_satisfies | ||
|
||
# Collect all DLLs inside numpy's installation folder, dump them into built | ||
# app's root. | ||
binaries = collect_dynamic_libs("numpy", ".") | ||
|
||
# If using Conda without any non-conda virtual environment manager: | ||
if is_pure_conda: | ||
# Assume running the NumPy from Conda-forge and collect it's DLLs from the | ||
# communal Conda bin directory. DLLs from NumPy's dependencies must also be | ||
# collected to capture MKL, OpenBlas, OpenMP, etc. | ||
from PyInstaller.utils.hooks import conda_support | ||
datas = conda_support.collect_dynamic_libs("numpy", dependencies=True) | ||
|
||
# Submodules PyInstaller cannot detect (probably because they are only imported | ||
# by extension modules, which PyInstaller cannot read). | ||
hiddenimports = ['numpy.core._dtype_ctypes'] | ||
if is_conda: | ||
hiddenimports.append("six") | ||
|
||
# Remove testing and building code and packages that are referenced throughout | ||
# NumPy but are not really dependencies. | ||
excludedimports = [ | ||
"scipy", | ||
"pytest", | ||
"nose", | ||
"f2py", | ||
"setuptools", | ||
"numpy.f2py", | ||
] | ||
|
||
# As of version 1.22, numpy.testing (imported for example by some scipy | ||
# modules) requires numpy.distutils and distutils. So exclude them only for | ||
# earlier versions. | ||
if is_module_satisfies("numpy < 1.22"): | ||
excludedimports += [ | ||
"distutils", | ||
"numpy.distutils", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
"""A crude *bit of everything* smoke test to verify PyInstaller compatibility. | ||
PyInstaller typically goes wrong by forgetting to package modules, extension | ||
modules or shared libraries. This script should aim to touch as many of those | ||
as possible in an attempt to trip a ModuleNotFoundError or a DLL load failure | ||
due to an uncollected resource. Missing resources are unlikely to lead to | ||
arithmitic errors so there's generally no need to verify any calculation's | ||
output - merely that it made it to the end OK. This script should not | ||
explicitly import any of numpy's submodules as that gives PyInstaller undue | ||
hints that those submodules exist and should be collected (accessing implicitly | ||
loaded submodules is OK). | ||
""" | ||
import numpy as np | ||
|
||
a = np.arange(1., 10.).reshape((3, 3)) | ||
np.linalg.det(a) | ||
a @ a | ||
a @ a.T | ||
np.linalg.inv(a) | ||
np.sin(np.exp(a)) | ||
np.linalg.svd(a) | ||
np.linalg.eigh(a) | ||
|
||
np.unique(np.random.randint(0, 10, 100)) | ||
np.sort(np.random.uniform(0, 10, 100)) | ||
|
||
np.fft.fft(np.exp(2j * np.pi * np.arange(8) / 8)) | ||
np.ma.masked_array(np.arange(10), np.random.rand(10) < .5).sum() | ||
np.polynomial.Legendre([7, 8, 9]).roots() | ||
|
||
print("I made it!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import subprocess | ||
from pathlib import Path | ||
|
||
import pytest | ||
|
||
|
||
# PyInstaller has been very unproactive about replacing 'imp' with 'importlib'. | ||
@pytest.mark.filterwarnings('ignore::DeprecationWarning') | ||
# It also leaks io.BytesIO()s. | ||
@pytest.mark.filterwarnings('ignore::ResourceWarning') | ||
@pytest.mark.parametrize("mode", ["--onedir", "--onefile"]) | ||
@pytest.mark.slow | ||
def test_pyinstaller(mode, tmpdir): | ||
"""Compile and run pyinstaller-smoke.py using PyInstaller.""" | ||
|
||
pyinstaller_cli = pytest.importorskip("PyInstaller.__main__").run | ||
|
||
source = Path(__file__).with_name("pyinstaller-smoke.py").resolve() | ||
args = [ | ||
# Place all generated files in ``tmp_path``. | ||
'--workpath', str(tmpdir / "build"), | ||
'--distpath', str(tmpdir / "dist"), | ||
'--specpath', str(tmpdir), | ||
mode, | ||
str(source), | ||
] | ||
pyinstaller_cli(args) | ||
|
||
if mode == "--onefile": | ||
exe = tmpdir / "dist" / source.stem | ||
else: | ||
exe = tmpdir / "dist" / source.stem / source.stem | ||
|
||
p = subprocess.run([exe], check=True, stdout=subprocess.PIPE) | ||
assert p.stdout.strip() == b"I made it!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters