opencv-python
/
setup.py
543 строки · 20.4 Кб
1import io2import os3import os.path4import sys5import runpy6import subprocess7import re8import sysconfig9import platform10from skbuild import cmaker, setup11
12
13def main():14os.chdir(os.path.dirname(os.path.abspath(__file__)))15
16CI_BUILD = os.environ.get("CI_BUILD", "False")17is_CI_build = True if CI_BUILD == "1" else False18cmake_source_dir = "opencv"19minimum_supported_numpy = "1.13.3"20build_contrib = get_build_env_var_by_name("contrib")21build_headless = get_build_env_var_by_name("headless")22build_java = "ON" if get_build_env_var_by_name("java") else "OFF"23build_rolling = get_build_env_var_by_name("rolling")24
25install_requires = [26'numpy>=1.13.3; python_version<"3.7"',27'numpy>=1.17.0; python_version>="3.7"', # https://github.com/numpy/numpy/pull/1372528'numpy>=1.17.3; python_version>="3.8"',29'numpy>=1.19.3; python_version>="3.9"',30'numpy>=1.21.2; python_version>="3.10"',31'numpy>=1.19.3; python_version>="3.6" and platform_system=="Linux" and platform_machine=="aarch64"',32'numpy>=1.21.0; python_version<="3.9" and platform_system=="Darwin" and platform_machine=="arm64"',33'numpy>=1.21.4; python_version>="3.10" and platform_system=="Darwin"',34"numpy>=1.23.5; python_version>='3.11'",35"numpy>=1.26.0; python_version>='3.12'"36]37
38python_version = cmaker.CMaker.get_python_version()39python_lib_path = cmaker.CMaker.get_python_library(python_version) or ""40# HACK: For Scikit-build 0.17.3 and newer that returns None or empty sptring for PYTHON_LIBRARY in manylinux201441# A small release related to PYTHON_LIBRARY handling changes in 0.17.2; scikit-build 0.17.3 returns an empty string from get_python_library if no Python library is present (like on manylinux), where 0.17.2 returned None, and previous versions returned a non-existent path. Note that adding REQUIRED to find_package(PythonLibs will fail, but it is incorrect (you must not link to libPython.so) and was really just injecting a non-existent path before.42# TODO: Remove the hack when the issue is handled correctly in main OpenCV CMake.43if python_lib_path == "":44python_lib_path = "libpython%sm.a" % python_version45python_lib_path = python_lib_path.replace("\\", "/")46
47python_include_dir = cmaker.CMaker.get_python_include_dir(python_version).replace(48"\\", "/"49)50
51if os.path.exists(".git"):52import pip._internal.vcs.git as git53
54g = git.Git() # NOTE: pip API's are internal, this has to be refactored55
56g.run_command(["submodule", "sync"])57
58if build_rolling:59g.run_command(60["submodule", "update", "--init", "--recursive", "--remote", cmake_source_dir]61)62
63if build_contrib:64g.run_command(65["submodule", "update", "--init", "--recursive", "--remote", "opencv_contrib"]66)67else:68g.run_command(69["submodule", "update", "--init", "--recursive", cmake_source_dir]70)71
72if build_contrib:73g.run_command(74["submodule", "update", "--init", "--recursive", "opencv_contrib"]75)76
77package_version, build_contrib, build_headless, build_rolling = get_and_set_info(78build_contrib, build_headless, build_rolling, is_CI_build79)80
81# https://stackoverflow.com/questions/1405913/python-32bit-or-64bit-mode82is64 = sys.maxsize > 2 ** 3283
84package_name = "opencv-python"85
86if build_contrib and not build_headless:87package_name = "opencv-contrib-python"88
89if build_contrib and build_headless:90package_name = "opencv-contrib-python-headless"91
92if build_headless and not build_contrib:93package_name = "opencv-python-headless"94
95if build_rolling:96package_name += "-rolling"97
98long_description = io.open("README.md", encoding="utf-8").read()99
100packages = ["cv2", "cv2.data"]101
102package_data = {103"cv2": ["*%s" % sysconfig.get_config_vars().get("SO"), "version.py"]104+ (["*.dll"] if os.name == "nt" else [])105+ ["LICENSE.txt", "LICENSE-3RD-PARTY.txt"],106"cv2.data": ["*.xml"],107}108
109# Files from CMake output to copy to package.110# Path regexes with forward slashes relative to CMake install dir.111rearrange_cmake_output_data = {112"cv2": (113[r"bin/opencv_videoio_ffmpeg\d{4}%s\.dll" % ("_64" if is64 else "")]114if os.name == "nt"115else []116)117+118(119[r"lib/libOrbbecSDK.dylib", r"lib/libOrbbecSDK.\d.\d.dylib", r"lib/libOrbbecSDK.\d.\d.\d.dylib"]120if platform.system() == "Darwin" and platform.machine() == "arm64"121else []122)123+124# In Windows, in python/X.Y/<arch>/; in Linux, in just python/X.Y/.125# Naming conventions vary so widely between versions and OSes126# had to give up on checking them.127[128r"python/cv2/python-%s/cv2.*"129% (sys.version_info[0])130]131+132[133r"python/cv2/__init__.py"134]135+136[137r"python/cv2/.*config.*.py"138]139+140[ r"python/cv2/py.typed" ] if sys.version_info >= (3, 6) else []141,142"cv2.data": [ # OPENCV_OTHER_INSTALL_PATH143("etc" if os.name == "nt" else "share/opencv4") + r"/haarcascades/.*\.xml"144],145"cv2.gapi": [146"python/cv2" + r"/gapi/.*\.py"147],148"cv2.mat_wrapper": [149"python/cv2" + r"/mat_wrapper/.*\.py"150],151"cv2.misc": [152"python/cv2" + r"/misc/.*\.py"153],154"cv2.utils": [155"python/cv2" + r"/utils/.*\.py"156],157}158
159if sys.version_info >= (3, 6):160rearrange_cmake_output_data["cv2.typing"] = ["python/cv2" + r"/typing/.*\.py"]161
162# Files in sourcetree outside package dir that should be copied to package.163# Raw paths relative to sourcetree root.164files_outside_package_dir = {"cv2": ["LICENSE.txt", "LICENSE-3RD-PARTY.txt"]}165
166ci_cmake_generator = (167["-G", "Visual Studio 14" + (" Win64" if is64 else "")]168if os.name == "nt"169else ["-G", "Unix Makefiles"]170)171
172cmake_args = (173(ci_cmake_generator if is_CI_build else [])174+ [175# skbuild inserts PYTHON_* vars. That doesn't satisfy opencv build scripts in case of Py3176"-DPYTHON3_EXECUTABLE=%s" % sys.executable,177"-DPYTHON_DEFAULT_EXECUTABLE=%s" % sys.executable,178"-DPYTHON3_INCLUDE_DIR=%s" % python_include_dir,179"-DPYTHON3_LIBRARY=%s" % python_lib_path,180"-DBUILD_opencv_python3=ON",181"-DBUILD_opencv_python2=OFF",182# Disable the Java build by default as it is not needed183"-DBUILD_opencv_java=%s" % build_java,184# Relative dir to install the built module to in the build tree.185# The default is generated from sysconfig, we'd rather have a constant for simplicity186"-DOPENCV_PYTHON3_INSTALL_PATH=python",187# Otherwise, opencv scripts would want to install `.pyd' right into site-packages,188# and skbuild bails out on seeing that189"-DINSTALL_CREATE_DISTRIB=ON",190# See opencv/CMakeLists.txt for options and defaults191"-DBUILD_opencv_apps=OFF",192"-DBUILD_opencv_freetype=OFF",193"-DBUILD_SHARED_LIBS=OFF",194"-DBUILD_TESTS=OFF",195"-DBUILD_PERF_TESTS=OFF",196"-DBUILD_DOCS=OFF",197"-DPYTHON3_LIMITED_API=ON",198"-DBUILD_OPENEXR=ON",199]200+ (201# CMake flags for windows/arm64 build202["-DCMAKE_GENERATOR_PLATFORM=ARM64",203# Emulated cmake requires following flags to correctly detect204# target architecture for windows/arm64 build205"-DOPENCV_WORKAROUND_CMAKE_20989=ON",206"-DCMAKE_SYSTEM_PROCESSOR=ARM64"]207if platform.machine() == "ARM64" and sys.platform == "win32"208# If it is not defined 'linker flags: /machine:X86' on Windows x64209else ["-DCMAKE_GENERATOR_PLATFORM=x64"] if is64 and sys.platform == "win32"210else []211)212+ (213["-DOPENCV_EXTRA_MODULES_PATH=" + os.path.abspath("opencv_contrib/modules")]214if build_contrib215else []216)217)218
219if build_headless:220# it seems that cocoa cannot be disabled so on macOS the package is not truly headless221cmake_args.append("-DWITH_WIN32UI=OFF")222cmake_args.append("-DWITH_QT=OFF")223cmake_args.append("-DWITH_GTK=OFF")224if is_CI_build:225cmake_args.append(226"-DWITH_MSMF=OFF"227) # see: https://github.com/skvark/opencv-python/issues/263228
229if sys.platform.startswith("linux") and not is64 and "bdist_wheel" in sys.argv:230subprocess.check_call("patch -p0 < patches/patchOpenEXR", shell=True)231
232# OS-specific components during CI builds233if is_CI_build:234
235if (236not build_headless237and "bdist_wheel" in sys.argv238and sys.platform.startswith("linux")239):240cmake_args.append("-DWITH_QT=5")241subprocess.check_call("patch -p1 < patches/patchQtPlugins", shell=True)242
243if sys.platform.startswith("linux"):244rearrange_cmake_output_data["cv2.qt.plugins.platforms"] = [245(r"lib/qt/plugins/platforms/libqxcb\.so")246]247
248# add fonts for Qt5249fonts = []250for file in os.listdir("/usr/share/fonts/dejavu"):251if file.endswith(".ttf"):252fonts.append(253(r"lib/qt/fonts/dejavu/%s\.ttf" % file.split(".")[0])254)255
256rearrange_cmake_output_data["cv2.qt.fonts"] = fonts257
258if sys.platform == "darwin":259rearrange_cmake_output_data["cv2.qt.plugins.platforms"] = [260(r"lib/qt/plugins/platforms/libqcocoa\.dylib")261]262
263if sys.platform.startswith("linux"):264cmake_args.append("-DWITH_V4L=ON")265cmake_args.append("-DWITH_LAPACK=ON")266cmake_args.append("-DENABLE_PRECOMPILED_HEADERS=OFF")267
268# works via side effect269RearrangeCMakeOutput(270rearrange_cmake_output_data, files_outside_package_dir, package_data.keys()271)272
273setup(274name=package_name,275version=package_version,276url="https://github.com/opencv/opencv-python",277license="Apache 2.0",278description="Wrapper package for OpenCV python bindings.",279long_description=long_description,280long_description_content_type="text/markdown",281packages=packages,282package_data=package_data,283maintainer="OpenCV Team",284ext_modules=EmptyListWithLength(),285install_requires=install_requires,286python_requires=">=3.6",287classifiers=[288"Development Status :: 5 - Production/Stable",289"Environment :: Console",290"Intended Audience :: Developers",291"Intended Audience :: Education",292"Intended Audience :: Information Technology",293"Intended Audience :: Science/Research",294"License :: OSI Approved :: Apache Software License",295"Operating System :: MacOS",296"Operating System :: Microsoft :: Windows",297"Operating System :: POSIX",298"Operating System :: Unix",299"Programming Language :: Python",300"Programming Language :: Python :: 3",301"Programming Language :: Python :: 3 :: Only",302"Programming Language :: Python :: 3.6",303"Programming Language :: Python :: 3.7",304"Programming Language :: Python :: 3.8",305"Programming Language :: Python :: 3.9",306"Programming Language :: Python :: 3.10",307"Programming Language :: Python :: 3.11",308"Programming Language :: Python :: 3.12",309"Programming Language :: C++",310"Programming Language :: Python :: Implementation :: CPython",311"Topic :: Scientific/Engineering",312"Topic :: Scientific/Engineering :: Image Recognition",313"Topic :: Software Development",314],315cmake_args=cmake_args,316cmake_source_dir=cmake_source_dir,317)318
319print("OpenCV is raising funds to keep the library free for everyone, and we need the support of the entire community to do it. Donate to OpenCV on GitHub:\nhttps://github.com/sponsors/opencv\n")320
321class RearrangeCMakeOutput:322"""323Patch SKBuild logic to only take files related to the Python package
324and construct a file hierarchy that SKBuild expects (see below)
325"""
326
327_setuptools_wrap = None328
329# Have to wrap a function reference, or it's converted330# into an instance method on attr assignment331import argparse332
333wraps = argparse.Namespace(_classify_installed_files=None)334del argparse335
336package_paths_re = None337packages = None338files_outside_package = None339
340def __init__(self, package_paths_re, files_outside_package, packages):341cls = self.__class__342assert not cls.wraps._classify_installed_files, "Singleton object"343import skbuild.setuptools_wrap344
345cls._setuptools_wrap = skbuild.setuptools_wrap346cls.wraps._classify_installed_files = (347cls._setuptools_wrap._classify_installed_files348)349cls._setuptools_wrap._classify_installed_files = (350self._classify_installed_files_override351)352
353cls.package_paths_re = package_paths_re354cls.files_outside_package = files_outside_package355cls.packages = packages356
357def __del__(self):358cls = self.__class__359cls._setuptools_wrap._classify_installed_files = (360cls.wraps._classify_installed_files361)362cls.wraps._classify_installed_files = None363cls._setuptools_wrap = None364
365def _classify_installed_files_override(366self,367install_paths,368package_data,369package_prefixes,370py_modules,371new_py_modules,372scripts,373new_scripts,374data_files,375cmake_source_dir,376cmake_install_reldir,377):378"""379From all CMake output, we're only interested in a few files
380and must place them into CMake install dir according
381to Python conventions for SKBuild to find them:
382package\
383file
384subpackage\
385etc.
386"""
387
388cls = self.__class__389
390# 'relpath'/'reldir' = relative to CMAKE_INSTALL_DIR/cmake_install_dir391# 'path'/'dir' = relative to sourcetree root392cmake_install_dir = os.path.join(393cls._setuptools_wrap.CMAKE_INSTALL_DIR(), cmake_install_reldir394)395install_relpaths = [396os.path.relpath(p, cmake_install_dir) for p in install_paths397]398fslash_install_relpaths = [399p.replace(os.path.sep, "/") for p in install_relpaths400]401relpaths_zip = list(zip(fslash_install_relpaths, install_relpaths))402
403final_install_relpaths = []404
405print("Copying files from CMake output")406
407# add lines from the old __init__.py file to the config file408with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts', '__init__.py'), 'r') as custom_init:409custom_init_data = custom_init.read()410
411# OpenCV generates config with different name for case with PYTHON3_LIMITED_API=ON412config_py = os.path.join(cmake_install_dir, 'python', 'cv2', 'config-%s.%s.py'413% (sys.version_info[0], sys.version_info[1]))414if not os.path.exists(config_py):415config_py = os.path.join(cmake_install_dir, 'python', 'cv2', 'config-%s.py' % sys.version_info[0])416
417with open(config_py, 'w') as opencv_init_config:418opencv_init_config.write(custom_init_data)419
420if sys.version_info >= (3, 6):421for p in install_relpaths:422if p.endswith(".pyi"):423target_rel_path = os.path.relpath(p, "python/cv2")424cls._setuptools_wrap._copy_file(425os.path.join(cmake_install_dir, p),426os.path.join(cmake_install_dir, "cv2", target_rel_path),427hide_listing=False,428)429final_install_relpaths.append(os.path.join("cv2", target_rel_path))430
431del install_relpaths, fslash_install_relpaths432
433for package_name, relpaths_re in cls.package_paths_re.items():434package_dest_reldir = package_name.replace(".", os.path.sep)435for relpath_re in relpaths_re:436found = False437r = re.compile(relpath_re + "$")438for fslash_relpath, relpath in relpaths_zip:439m = r.match(fslash_relpath)440if not m:441continue442found = True443new_install_relpath = os.path.join(444package_dest_reldir, os.path.basename(relpath)445)446cls._setuptools_wrap._copy_file(447os.path.join(cmake_install_dir, relpath),448os.path.join(cmake_install_dir, new_install_relpath),449hide_listing=False,450)451final_install_relpaths.append(new_install_relpath)452del m, fslash_relpath, new_install_relpath453else:454# gapi can be missed if ADE was not downloaded (network issue)455if not found and "gapi" not in relpath_re:456raise Exception("Not found: '%s'" % relpath_re)457del r, found458
459del relpaths_zip460
461print("Copying files from non-default sourcetree locations")462
463for package_name, paths in cls.files_outside_package.items():464package_dest_reldir = package_name.replace(".", os.path.sep)465for path in paths:466new_install_relpath = os.path.join(467package_dest_reldir,468# Don't yet have a need to copy469# to subdirectories of package dir470os.path.basename(path),471)472cls._setuptools_wrap._copy_file(473path,474os.path.join(cmake_install_dir, new_install_relpath),475hide_listing=False,476)477final_install_relpaths.append(new_install_relpath)478
479final_install_paths = [480os.path.join(cmake_install_dir, p) for p in final_install_relpaths481]482
483return (cls.wraps._classify_installed_files)(484final_install_paths,485package_data,486package_prefixes,487py_modules,488new_py_modules,489scripts,490new_scripts,491data_files,492# To get around a check that prepends source dir to paths and breaks package detection code.493cmake_source_dir="",494_cmake_install_dir=cmake_install_reldir,495)496
497
498def get_and_set_info(contrib, headless, rolling, ci_build):499# cv2/version.py should be generated by running find_version.py500version = {}501here = os.path.abspath(os.path.dirname(__file__))502version_file = os.path.join(here, "cv2", "version.py")503
504# generate a fresh version.py always when Git repository exists505# (in sdists the version.py file already exists)506if os.path.exists(".git"):507old_args = sys.argv.copy()508sys.argv = ["", str(contrib), str(headless), str(rolling), str(ci_build)]509runpy.run_path("find_version.py", run_name="__main__")510sys.argv = old_args511
512with open(version_file) as fp:513exec(fp.read(), version)514
515return version["opencv_version"], version["contrib"], version["headless"], version["rolling"]516
517
518def get_build_env_var_by_name(flag_name):519flag_set = False520
521try:522flag_set = bool(int(os.getenv("ENABLE_" + flag_name.upper(), None)))523except Exception:524pass525
526if not flag_set:527try:528flag_set = bool(int(open(flag_name + ".enabled").read(1)))529except Exception:530pass531
532return flag_set533
534
535# This creates a list which is empty but returns a length of 1.
536# Should make the wheel a binary distribution and platlib compliant.
537class EmptyListWithLength(list):538def __len__(self):539return 1540
541
542if __name__ == "__main__":543main()544