microsoft/onnxruntime-extensions
Publicmirrored from https://github.com/microsoft/onnxruntime-extensionsAvailable
setup.py
273lines · modecode
| 1 | # -*- coding: utf-8 -*- |
| 2 | # Copyright (c) Microsoft Corporation. All rights reserved. |
| 3 | # Licensed under the MIT License. See License.txt in the project root for |
| 4 | # license information. |
| 5 | ########################################################################### |
| 6 | |
| 7 | from setuptools import setup, find_packages |
| 8 | from setuptools.command.build import build as _build |
| 9 | from setuptools.command.build_ext import build_ext as _build_ext |
| 10 | |
| 11 | import re |
| 12 | import os |
| 13 | import sys |
| 14 | import setuptools |
| 15 | import pathlib |
| 16 | import subprocess |
| 17 | |
| 18 | from textwrap import dedent |
| 19 | |
| 20 | TOP_DIR = os.path.dirname(__file__) or os.getcwd() |
| 21 | PACKAGE_NAME = 'onnxruntime_extensions' |
| 22 | VSINSTALLDIR_NAME = 'VSINSTALLDIR' |
| 23 | |
| 24 | |
| 25 | def load_vsdevcmd(): |
| 26 | if os.environ.get(VSINSTALLDIR_NAME) is None: |
| 27 | stdout, _ = subprocess.Popen([ |
| 28 | 'powershell', ' -noprofile', '-executionpolicy', |
| 29 | 'bypass', '-f', TOP_DIR + '/tools/get_vsdevcmd.ps1', '-outputEnv', '1'], |
| 30 | stdout=subprocess.PIPE, shell=False, universal_newlines=True).communicate() |
| 31 | for line in stdout.splitlines(): |
| 32 | kv_pair = line.split('=') |
| 33 | if len(kv_pair) == 2: |
| 34 | os.environ[kv_pair[0]] = kv_pair[1] |
| 35 | else: |
| 36 | import shutil |
| 37 | if shutil.which('cmake') is None: |
| 38 | raise SystemExit( |
| 39 | "Cannot find cmake in the executable path, " |
| 40 | "please run this script under Developer Command Prompt for VS.") |
| 41 | |
| 42 | |
| 43 | def read_git_refs(): |
| 44 | release_branch = False |
| 45 | stdout, _ = subprocess.Popen( |
| 46 | ['git'] + ['log', '-1', '--format=%H'], |
| 47 | cwd=TOP_DIR, |
| 48 | stdout=subprocess.PIPE, universal_newlines=True).communicate() |
| 49 | HEAD = dedent(stdout.splitlines()[0]).strip('\n\r') |
| 50 | stdout, _ = subprocess.Popen( |
| 51 | ['git'] + ['show-ref', '--head'], |
| 52 | cwd=TOP_DIR, |
| 53 | stdout=subprocess.PIPE, universal_newlines=True).communicate() |
| 54 | for _ln in stdout.splitlines(): |
| 55 | _ln = dedent(_ln).strip('\n\r') |
| 56 | if _ln.startswith(HEAD): |
| 57 | _, _2 = _ln.split(' ') |
| 58 | if _2.startswith('refs/remotes/origin/rel-'): |
| 59 | release_branch = True |
| 60 | return release_branch, HEAD |
| 61 | |
| 62 | |
| 63 | class BuildCMakeExt(_build_ext): |
| 64 | |
| 65 | def run(self): |
| 66 | """ |
| 67 | Perform build_cmake before doing the 'normal' stuff |
| 68 | """ |
| 69 | for extension in self.extensions: |
| 70 | if extension.name == 'onnxruntime_extensions._extensions_pydll': |
| 71 | self.build_cmake(extension) |
| 72 | |
| 73 | def build_cmake(self, extension): |
| 74 | project_dir = pathlib.Path().absolute() |
| 75 | build_temp = pathlib.Path(self.build_temp) |
| 76 | build_temp.mkdir(parents=True, exist_ok=True) |
| 77 | ext_fullpath = pathlib.Path(self.get_ext_fullpath(extension.name)) |
| 78 | |
| 79 | config = 'RelWithDebInfo' if self.debug else 'Release' |
| 80 | cmake_args = [ |
| 81 | '-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + str(ext_fullpath.parent.absolute()), |
| 82 | '-DOCOS_BUILD_PYTHON=ON', |
| 83 | '-DOCOS_EXTENTION_NAME=' + ext_fullpath.name, |
| 84 | '-DCMAKE_BUILD_TYPE=' + config |
| 85 | ] |
| 86 | |
| 87 | if os.environ.get('OCOS_NO_OPENCV') == '1': |
| 88 | # Disabling openCV can drastically reduce the build time. |
| 89 | cmake_args += [ |
| 90 | '-DOCOS_ENABLE_CTEST=OFF', |
| 91 | '-DOCOS_ENABLE_OPENCV_CODECS=OFF', |
| 92 | '-DOCOS_ENABLE_CV2=OFF', |
| 93 | '-DOCOS_ENABLE_VISION=OFF'] |
| 94 | |
| 95 | # CMake lets you override the generator - we need to check this. |
| 96 | # Can be set with Conda-Build, for example. |
| 97 | cmake_generator = os.environ.get("CMAKE_GENERATOR", "") |
| 98 | # Adding CMake arguments set as environment variable |
| 99 | # (needed e.g. to build for ARM OSx on conda-forge) |
| 100 | if "CMAKE_ARGS" in os.environ: |
| 101 | cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] |
| 102 | |
| 103 | if sys.platform != "win32": |
| 104 | # Using Ninja-build since it a) is available as a wheel and b) |
| 105 | # multithreads automatically. MSVC would require all variables be |
| 106 | # exported for Ninja to pick it up, which is a little tricky to do. |
| 107 | # Users can override the generator with CMAKE_GENERATOR in CMake |
| 108 | # 3.15+. |
| 109 | if not cmake_generator or cmake_generator == "Ninja": |
| 110 | try: |
| 111 | import ninja # noqa: F401 |
| 112 | |
| 113 | ninja_executable_path = os.path.join(ninja.BIN_DIR, "ninja") |
| 114 | cmake_args += [ |
| 115 | "-GNinja", |
| 116 | f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", |
| 117 | ] |
| 118 | except ImportError: |
| 119 | pass |
| 120 | |
| 121 | if sys.platform.startswith("darwin"): |
| 122 | # Cross-compile support for macOS - respect ARCHFLAGS if set |
| 123 | archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) |
| 124 | if archs: |
| 125 | cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] |
| 126 | |
| 127 | # overwrite the Python module info if the auto-detection doesn't work. |
| 128 | # export Python3_INCLUDE_DIRS=/opt/python/cp38-cp38 |
| 129 | # export Python3_LIBRARIES=/opt/python/cp38-cp38 |
| 130 | for env in ['Python3_INCLUDE_DIRS', 'Python3_LIBRARIES']: |
| 131 | if env in os.environ: |
| 132 | cmake_args.append("-D%s=%s" % (env, os.environ[env])) |
| 133 | |
| 134 | if self.debug: |
| 135 | cmake_args += ['-DCC_OPTIMIZE=OFF'] |
| 136 | |
| 137 | # the parallel build has to be limited on some Linux VM machine. |
| 138 | cpu_number = os.environ.get('CPU_NUMBER') |
| 139 | build_args = [ |
| 140 | '--config', config, |
| 141 | '--parallel' + ('' if cpu_number is None else ' ' + cpu_number) |
| 142 | ] |
| 143 | cmake_exe = 'cmake' |
| 144 | # unlike Linux/macOS, cmake pip package on Windows fails to build some 3rd party dependencies. |
| 145 | # so we have to use the cmake installed from Visual Studio. |
| 146 | if os.environ.get(VSINSTALLDIR_NAME): |
| 147 | cmake_exe = os.environ[VSINSTALLDIR_NAME] + \ |
| 148 | 'Common7\\IDE\\CommonExtensions\\Microsoft\\CMake\\CMake\\bin\\cmake.exe' |
| 149 | |
| 150 | self.spawn([cmake_exe, '-S', str(project_dir), '-B', str(build_temp)] + cmake_args) |
| 151 | if not self.dry_run: |
| 152 | self.spawn([cmake_exe, '--build', str(build_temp)] + build_args) |
| 153 | |
| 154 | if sys.platform == "win32": |
| 155 | config_dir = '.' |
| 156 | if not (build_temp / 'build.ninja').exists(): |
| 157 | config_dir = config |
| 158 | self.copy_file(build_temp / 'bin' / config_dir / 'extensions_pydll.dll', ext_fullpath, |
| 159 | link='hard' if self.debug else None) |
| 160 | else: |
| 161 | self.copy_file(build_temp / 'lib' / ext_fullpath.name, ext_fullpath, |
| 162 | link='sym' if self.debug else None) |
| 163 | |
| 164 | |
| 165 | class Build(_build): |
| 166 | def initialize_options(self) -> None: |
| 167 | super().initialize_options() |
| 168 | if os.environ.get('OCOS_SCB_DEBUG', None) == '1': |
| 169 | self.debug = True |
| 170 | |
| 171 | def finalize_options(self) -> None: |
| 172 | # There is a bug in setuptools that prevents the build get the right platform name from arguments. |
| 173 | # So, it cannot generate the correct wheel with the right arch in Official release pipeline. |
| 174 | # Force plat_name to be 'win-amd64' in Windows to fix that. |
| 175 | # Since extensions cmake is only available on x64 for Windows now, it is not a problem to hardcode it. |
| 176 | if sys.platform == "win32" and "arm" not in sys.version.lower(): |
| 177 | self.plat_name = "win-amd64" |
| 178 | super().finalize_options() |
| 179 | |
| 180 | |
| 181 | def read_requirements(): |
| 182 | with open(os.path.join(TOP_DIR, "requirements.txt"), "r", encoding="utf-8") as f: |
| 183 | requirements = [_ for _ in [dedent(_) for _ in f.readlines()] if _ is not None] |
| 184 | return requirements |
| 185 | |
| 186 | |
| 187 | # read version from the package file. |
| 188 | def read_version(): |
| 189 | version_str = '1.0.0' |
| 190 | with (open(os.path.join(TOP_DIR, 'version.txt'), "r")) as f: |
| 191 | version_str = f.readline().strip() |
| 192 | |
| 193 | # special handling for Onebranch building |
| 194 | if os.getenv('BUILD_SOURCEBRANCHNAME', "").startswith('rel-'): |
| 195 | return version_str |
| 196 | |
| 197 | # is it a dev build or release? |
| 198 | rel_br, cid = read_git_refs() if os.path.isdir( |
| 199 | os.path.join(TOP_DIR, '.git')) else (True, None) |
| 200 | |
| 201 | if rel_br: |
| 202 | return version_str |
| 203 | |
| 204 | build_id = os.getenv('BUILD_BUILDID', None) |
| 205 | if build_id is not None: |
| 206 | version_str += '.{}'.format(build_id) |
| 207 | else: |
| 208 | version_str += '+' + cid[:7] |
| 209 | return version_str |
| 210 | |
| 211 | |
| 212 | def write_py_version(ortx_version): |
| 213 | text = ["# Generated by setup.py, DON'T MANUALLY UPDATE IT!\n", |
| 214 | "__version__ = \"{}\"\n".format(ortx_version)] |
| 215 | with (open(os.path.join(TOP_DIR, 'onnxruntime_extensions/_version.py'), "w")) as _f: |
| 216 | _f.writelines(text) |
| 217 | |
| 218 | |
| 219 | if sys.platform == "win32": |
| 220 | load_vsdevcmd() |
| 221 | |
| 222 | ext_modules = [ |
| 223 | setuptools.extension.Extension( |
| 224 | name=str('onnxruntime_extensions._extensions_pydll'), |
| 225 | sources=[]) |
| 226 | ] |
| 227 | |
| 228 | packages = find_packages() |
| 229 | package_dir = {k: os.path.join('.', k.replace(".", "/")) for k in packages} |
| 230 | package_data = { |
| 231 | "onnxruntime_extensions": ["*.so", "*.pyd"], |
| 232 | } |
| 233 | |
| 234 | long_description = '' |
| 235 | with open(os.path.join(TOP_DIR, "README.md"), 'r', encoding="utf-8") as _f: |
| 236 | long_description += _f.read() |
| 237 | start_pos = long_description.find('# Introduction') |
| 238 | start_pos = 0 if start_pos < 0 else start_pos |
| 239 | end_pos = long_description.find('# Contributing') |
| 240 | long_description = long_description[start_pos:end_pos] |
| 241 | ortx_version = read_version() |
| 242 | write_py_version(ortx_version) |
| 243 | |
| 244 | setup( |
| 245 | name=PACKAGE_NAME, |
| 246 | version=ortx_version, |
| 247 | packages=packages, |
| 248 | package_dir=package_dir, |
| 249 | package_data=package_data, |
| 250 | description="ONNXRuntime Extensions", |
| 251 | long_description=long_description, |
| 252 | long_description_content_type='text/markdown', |
| 253 | license='MIT License', |
| 254 | author='Microsoft Corporation', |
| 255 | author_email='onnxruntime@microsoft.com', |
| 256 | url='https://github.com/microsoft/onnxruntime-extensions', |
| 257 | ext_modules=ext_modules, |
| 258 | cmdclass=dict(build_ext=BuildCMakeExt, build=Build), |
| 259 | include_package_data=True, |
| 260 | install_requires=read_requirements(), |
| 261 | classifiers=[ |
| 262 | 'Development Status :: 4 - Beta', |
| 263 | 'Environment :: Console', |
| 264 | 'Intended Audience :: Developers', |
| 265 | 'Operating System :: MacOS :: MacOS X', |
| 266 | 'Operating System :: Microsoft :: Windows', |
| 267 | 'Operating System :: POSIX :: Linux', |
| 268 | "Programming Language :: C++", |
| 269 | 'Programming Language :: Python', |
| 270 | "Programming Language :: Python :: Implementation :: CPython", |
| 271 | 'License :: OSI Approved :: MIT License' |
| 272 | ] |
| 273 | ) |