opencv-python

Форк
0
/
setup.py 
543 строки · 20.4 Кб
1
import io
2
import os
3
import os.path
4
import sys
5
import runpy
6
import subprocess
7
import re
8
import sysconfig
9
import platform
10
from skbuild import cmaker, setup
11

12

13
def main():
14
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
15

16
    CI_BUILD = os.environ.get("CI_BUILD", "False")
17
    is_CI_build = True if CI_BUILD == "1" else False
18
    cmake_source_dir = "opencv"
19
    minimum_supported_numpy = "1.13.3"
20
    build_contrib = get_build_env_var_by_name("contrib")
21
    build_headless = get_build_env_var_by_name("headless")
22
    build_java = "ON" if get_build_env_var_by_name("java") else "OFF"
23
    build_rolling = get_build_env_var_by_name("rolling")
24

25
    install_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/13725
28
        '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

38
    python_version = cmaker.CMaker.get_python_version()
39
    python_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 manylinux2014
41
    # 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.
43
    if python_lib_path == "":
44
        python_lib_path = "libpython%sm.a" % python_version
45
    python_lib_path = python_lib_path.replace("\\", "/")
46

47
    python_include_dir = cmaker.CMaker.get_python_include_dir(python_version).replace(
48
        "\\", "/"
49
    )
50

51
    if os.path.exists(".git"):
52
        import pip._internal.vcs.git as git
53

54
        g = git.Git()  # NOTE: pip API's are internal, this has to be refactored
55

56
        g.run_command(["submodule", "sync"])
57

58
        if build_rolling:
59
            g.run_command(
60
                ["submodule", "update", "--init", "--recursive", "--remote", cmake_source_dir]
61
            )
62

63
            if build_contrib:
64
                g.run_command(
65
                    ["submodule", "update", "--init", "--recursive", "--remote", "opencv_contrib"]
66
                )
67
        else:
68
            g.run_command(
69
                ["submodule", "update", "--init", "--recursive", cmake_source_dir]
70
            )
71

72
            if build_contrib:
73
                g.run_command(
74
                    ["submodule", "update", "--init", "--recursive", "opencv_contrib"]
75
                )
76

77
    package_version, build_contrib, build_headless, build_rolling = get_and_set_info(
78
        build_contrib, build_headless, build_rolling, is_CI_build
79
    )
80

81
    # https://stackoverflow.com/questions/1405913/python-32bit-or-64bit-mode
82
    is64 = sys.maxsize > 2 ** 32
83

84
    package_name = "opencv-python"
85

86
    if build_contrib and not build_headless:
87
        package_name = "opencv-contrib-python"
88

89
    if build_contrib and build_headless:
90
        package_name = "opencv-contrib-python-headless"
91

92
    if build_headless and not build_contrib:
93
        package_name = "opencv-python-headless"
94

95
    if build_rolling:
96
        package_name += "-rolling"
97

98
    long_description = io.open("README.md", encoding="utf-8").read()
99

100
    packages = ["cv2", "cv2.data"]
101

102
    package_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.
111
    rearrange_cmake_output_data = {
112
        "cv2": (
113
            [r"bin/opencv_videoio_ffmpeg\d{4}%s\.dll" % ("_64" if is64 else "")]
114
            if os.name == "nt"
115
            else []
116
        )
117
        +
118
        (
119
            [r"lib/libOrbbecSDK.dylib", r"lib/libOrbbecSDK.\d.\d.dylib", r"lib/libOrbbecSDK.\d.\d.\d.dylib"]
120
            if platform.system() == "Darwin" and platform.machine() == "arm64"
121
            else []
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 OSes
126
        # had to give up on checking them.
127
        [
128
            r"python/cv2/python-%s/cv2.*"
129
            % (sys.version_info[0])
130
        ]
131
        +
132
        [
133
            r"python/cv2/__init__.py"
134
        ]
135
        +
136
        [
137
            r"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_PATH
143
            ("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

159
    if sys.version_info >= (3, 6):
160
        rearrange_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.
164
    files_outside_package_dir = {"cv2": ["LICENSE.txt", "LICENSE-3RD-PARTY.txt"]}
165

166
    ci_cmake_generator = (
167
        ["-G", "Visual Studio 14" + (" Win64" if is64 else "")]
168
        if os.name == "nt"
169
        else ["-G", "Unix Makefiles"]
170
    )
171

172
    cmake_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 Py3
176
            "-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 needed
183
            "-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 simplicity
186
            "-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 that
189
            "-DINSTALL_CREATE_DISTRIB=ON",
190
            # See opencv/CMakeLists.txt for options and defaults
191
            "-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 build
202
            ["-DCMAKE_GENERATOR_PLATFORM=ARM64",
203
             # Emulated cmake requires following flags to correctly detect
204
             # target architecture for windows/arm64 build
205
             "-DOPENCV_WORKAROUND_CMAKE_20989=ON",
206
             "-DCMAKE_SYSTEM_PROCESSOR=ARM64"]
207
            if platform.machine() == "ARM64" and sys.platform == "win32"
208
            # If it is not defined 'linker flags: /machine:X86' on Windows x64
209
            else ["-DCMAKE_GENERATOR_PLATFORM=x64"] if is64 and sys.platform == "win32"
210
            else []
211
          )
212
        + (
213
            ["-DOPENCV_EXTRA_MODULES_PATH=" + os.path.abspath("opencv_contrib/modules")]
214
            if build_contrib
215
            else []
216
        )
217
    )
218

219
    if build_headless:
220
        # it seems that cocoa cannot be disabled so on macOS the package is not truly headless
221
        cmake_args.append("-DWITH_WIN32UI=OFF")
222
        cmake_args.append("-DWITH_QT=OFF")
223
        cmake_args.append("-DWITH_GTK=OFF")
224
        if is_CI_build:
225
            cmake_args.append(
226
                "-DWITH_MSMF=OFF"
227
            )  # see: https://github.com/skvark/opencv-python/issues/263
228

229
    if sys.platform.startswith("linux") and not is64 and "bdist_wheel" in sys.argv:
230
        subprocess.check_call("patch -p0 < patches/patchOpenEXR", shell=True)
231

232
    # OS-specific components during CI builds
233
    if is_CI_build:
234

235
        if (
236
            not build_headless
237
            and "bdist_wheel" in sys.argv
238
            and sys.platform.startswith("linux")
239
        ):
240
            cmake_args.append("-DWITH_QT=5")
241
            subprocess.check_call("patch -p1 < patches/patchQtPlugins", shell=True)
242

243
            if sys.platform.startswith("linux"):
244
                rearrange_cmake_output_data["cv2.qt.plugins.platforms"] = [
245
                    (r"lib/qt/plugins/platforms/libqxcb\.so")
246
                ]
247

248
                # add fonts for Qt5
249
                fonts = []
250
                for file in os.listdir("/usr/share/fonts/dejavu"):
251
                    if file.endswith(".ttf"):
252
                        fonts.append(
253
                            (r"lib/qt/fonts/dejavu/%s\.ttf" % file.split(".")[0])
254
                        )
255

256
                rearrange_cmake_output_data["cv2.qt.fonts"] = fonts
257

258
            if sys.platform == "darwin":
259
                rearrange_cmake_output_data["cv2.qt.plugins.platforms"] = [
260
                    (r"lib/qt/plugins/platforms/libqcocoa\.dylib")
261
                ]
262

263
        if sys.platform.startswith("linux"):
264
            cmake_args.append("-DWITH_V4L=ON")
265
            cmake_args.append("-DWITH_LAPACK=ON")
266
            cmake_args.append("-DENABLE_PRECOMPILED_HEADERS=OFF")
267

268
    # works via side effect
269
    RearrangeCMakeOutput(
270
        rearrange_cmake_output_data, files_outside_package_dir, package_data.keys()
271
    )
272

273
    setup(
274
        name=package_name,
275
        version=package_version,
276
        url="https://github.com/opencv/opencv-python",
277
        license="Apache 2.0",
278
        description="Wrapper package for OpenCV python bindings.",
279
        long_description=long_description,
280
        long_description_content_type="text/markdown",
281
        packages=packages,
282
        package_data=package_data,
283
        maintainer="OpenCV Team",
284
        ext_modules=EmptyListWithLength(),
285
        install_requires=install_requires,
286
        python_requires=">=3.6",
287
        classifiers=[
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
        ],
315
        cmake_args=cmake_args,
316
        cmake_source_dir=cmake_source_dir,
317
    )
318

319
    print("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

321
class RearrangeCMakeOutput:
322
    """
323
        Patch SKBuild logic to only take files related to the Python package
324
        and construct a file hierarchy that SKBuild expects (see below)
325
    """
326

327
    _setuptools_wrap = None
328

329
    # Have to wrap a function reference, or it's converted
330
    # into an instance method on attr assignment
331
    import argparse
332

333
    wraps = argparse.Namespace(_classify_installed_files=None)
334
    del argparse
335

336
    package_paths_re = None
337
    packages = None
338
    files_outside_package = None
339

340
    def __init__(self, package_paths_re, files_outside_package, packages):
341
        cls = self.__class__
342
        assert not cls.wraps._classify_installed_files, "Singleton object"
343
        import skbuild.setuptools_wrap
344

345
        cls._setuptools_wrap = skbuild.setuptools_wrap
346
        cls.wraps._classify_installed_files = (
347
            cls._setuptools_wrap._classify_installed_files
348
        )
349
        cls._setuptools_wrap._classify_installed_files = (
350
            self._classify_installed_files_override
351
        )
352

353
        cls.package_paths_re = package_paths_re
354
        cls.files_outside_package = files_outside_package
355
        cls.packages = packages
356

357
    def __del__(self):
358
        cls = self.__class__
359
        cls._setuptools_wrap._classify_installed_files = (
360
            cls.wraps._classify_installed_files
361
        )
362
        cls.wraps._classify_installed_files = None
363
        cls._setuptools_wrap = None
364

365
    def _classify_installed_files_override(
366
        self,
367
        install_paths,
368
        package_data,
369
        package_prefixes,
370
        py_modules,
371
        new_py_modules,
372
        scripts,
373
        new_scripts,
374
        data_files,
375
        cmake_source_dir,
376
        cmake_install_reldir,
377
    ):
378
        """
379
            From all CMake output, we're only interested in a few files
380
            and must place them into CMake install dir according
381
            to Python conventions for SKBuild to find them:
382
                package\
383
                    file
384
                    subpackage\
385
                        etc.
386
        """
387

388
        cls = self.__class__
389

390
        # 'relpath'/'reldir' = relative to CMAKE_INSTALL_DIR/cmake_install_dir
391
        # 'path'/'dir' = relative to sourcetree root
392
        cmake_install_dir = os.path.join(
393
            cls._setuptools_wrap.CMAKE_INSTALL_DIR(), cmake_install_reldir
394
        )
395
        install_relpaths = [
396
            os.path.relpath(p, cmake_install_dir) for p in install_paths
397
        ]
398
        fslash_install_relpaths = [
399
            p.replace(os.path.sep, "/") for p in install_relpaths
400
        ]
401
        relpaths_zip = list(zip(fslash_install_relpaths, install_relpaths))
402

403
        final_install_relpaths = []
404

405
        print("Copying files from CMake output")
406

407
        # add lines from the old __init__.py file to the config file
408
        with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'scripts', '__init__.py'), 'r') as custom_init:
409
            custom_init_data = custom_init.read()
410

411
        # OpenCV generates config with different name for case with PYTHON3_LIMITED_API=ON
412
        config_py = os.path.join(cmake_install_dir, 'python', 'cv2', 'config-%s.%s.py'
413
                                 % (sys.version_info[0], sys.version_info[1]))
414
        if not os.path.exists(config_py):
415
            config_py = os.path.join(cmake_install_dir, 'python', 'cv2', 'config-%s.py' % sys.version_info[0])
416

417
        with open(config_py, 'w') as opencv_init_config:
418
            opencv_init_config.write(custom_init_data)
419

420
        if sys.version_info >= (3, 6):
421
            for p in install_relpaths:
422
                if p.endswith(".pyi"):
423
                    target_rel_path = os.path.relpath(p, "python/cv2")
424
                    cls._setuptools_wrap._copy_file(
425
                        os.path.join(cmake_install_dir, p),
426
                        os.path.join(cmake_install_dir, "cv2", target_rel_path),
427
                        hide_listing=False,
428
                    )
429
                    final_install_relpaths.append(os.path.join("cv2", target_rel_path))
430

431
        del install_relpaths, fslash_install_relpaths
432

433
        for package_name, relpaths_re in cls.package_paths_re.items():
434
            package_dest_reldir = package_name.replace(".", os.path.sep)
435
            for relpath_re in relpaths_re:
436
                found = False
437
                r = re.compile(relpath_re + "$")
438
                for fslash_relpath, relpath in relpaths_zip:
439
                    m = r.match(fslash_relpath)
440
                    if not m:
441
                        continue
442
                    found = True
443
                    new_install_relpath = os.path.join(
444
                        package_dest_reldir, os.path.basename(relpath)
445
                    )
446
                    cls._setuptools_wrap._copy_file(
447
                        os.path.join(cmake_install_dir, relpath),
448
                        os.path.join(cmake_install_dir, new_install_relpath),
449
                        hide_listing=False,
450
                    )
451
                    final_install_relpaths.append(new_install_relpath)
452
                    del m, fslash_relpath, new_install_relpath
453
                else:
454
                    # gapi can be missed if ADE was not downloaded (network issue)
455
                    if not found and "gapi" not in relpath_re:
456
                        raise Exception("Not found: '%s'" % relpath_re)
457
                del r, found
458

459
        del relpaths_zip
460

461
        print("Copying files from non-default sourcetree locations")
462

463
        for package_name, paths in cls.files_outside_package.items():
464
            package_dest_reldir = package_name.replace(".", os.path.sep)
465
            for path in paths:
466
                new_install_relpath = os.path.join(
467
                    package_dest_reldir,
468
                    # Don't yet have a need to copy
469
                    # to subdirectories of package dir
470
                    os.path.basename(path),
471
                )
472
                cls._setuptools_wrap._copy_file(
473
                    path,
474
                    os.path.join(cmake_install_dir, new_install_relpath),
475
                    hide_listing=False,
476
                )
477
                final_install_relpaths.append(new_install_relpath)
478

479
        final_install_paths = [
480
            os.path.join(cmake_install_dir, p) for p in final_install_relpaths
481
        ]
482

483
        return (cls.wraps._classify_installed_files)(
484
            final_install_paths,
485
            package_data,
486
            package_prefixes,
487
            py_modules,
488
            new_py_modules,
489
            scripts,
490
            new_scripts,
491
            data_files,
492
            # To get around a check that prepends source dir to paths and breaks package detection code.
493
            cmake_source_dir="",
494
            _cmake_install_dir=cmake_install_reldir,
495
        )
496

497

498
def get_and_set_info(contrib, headless, rolling, ci_build):
499
    # cv2/version.py should be generated by running find_version.py
500
    version = {}
501
    here = os.path.abspath(os.path.dirname(__file__))
502
    version_file = os.path.join(here, "cv2", "version.py")
503

504
    # generate a fresh version.py always when Git repository exists
505
    # (in sdists the version.py file already exists)
506
    if os.path.exists(".git"):
507
        old_args = sys.argv.copy()
508
        sys.argv = ["", str(contrib), str(headless), str(rolling), str(ci_build)]
509
        runpy.run_path("find_version.py", run_name="__main__")
510
        sys.argv = old_args
511

512
    with open(version_file) as fp:
513
        exec(fp.read(), version)
514

515
    return version["opencv_version"], version["contrib"], version["headless"], version["rolling"]
516

517

518
def get_build_env_var_by_name(flag_name):
519
    flag_set = False
520

521
    try:
522
        flag_set = bool(int(os.getenv("ENABLE_" + flag_name.upper(), None)))
523
    except Exception:
524
        pass
525

526
    if not flag_set:
527
        try:
528
            flag_set = bool(int(open(flag_name + ".enabled").read(1)))
529
        except Exception:
530
            pass
531

532
    return flag_set
533

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.
537
class EmptyListWithLength(list):
538
    def __len__(self):
539
        return 1
540

541

542
if __name__ == "__main__":
543
    main()
544

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.