mirror of https://github.com/kendryte/nncase.git
307 lines
11 KiB
Python
307 lines
11 KiB
Python
from distutils.command.install_data import install_data
|
|
import imp
|
|
from posixpath import dirname
|
|
from setuptools import find_packages, setup, Extension
|
|
from setuptools.command.build_ext import build_ext
|
|
from setuptools.command.install_lib import install_lib
|
|
from setuptools.command.install_scripts import install_scripts
|
|
import shutil
|
|
import os
|
|
import platform
|
|
import sys
|
|
import io
|
|
import re
|
|
import time
|
|
import subprocess
|
|
from git.repo import Repo
|
|
# See ref: https://stackoverflow.com/a/51575996
|
|
|
|
|
|
class CMakeExtension(Extension):
|
|
"""
|
|
An extension to run the cmake build
|
|
|
|
This simply overrides the base extension class so that setuptools
|
|
doesn't try to build your sources for you
|
|
"""
|
|
|
|
def __init__(self, name, sourcedir=''):
|
|
super().__init__(name, sources=[])
|
|
self.sourcedir = os.path.abspath(sourcedir)
|
|
|
|
|
|
class InstallCMakeLibsData(install_data):
|
|
"""
|
|
Just a wrapper to get the install data into the egg-info
|
|
|
|
Listing the installed files in the egg-info guarantees that
|
|
all of the package files will be uninstalled when the user
|
|
uninstalls your package through pip
|
|
"""
|
|
|
|
def run(self):
|
|
"""
|
|
Outfiles are the libraries that were built using cmake
|
|
"""
|
|
|
|
# There seems to be no other way to do this; I tried listing the
|
|
# libraries during the execution of the InstallCMakeLibs.run() but
|
|
# setuptools never tracked them, seems like setuptools wants to
|
|
# track the libraries through package data more than anything...
|
|
# help would be appriciated
|
|
|
|
self.outfiles = self.distribution.data_files
|
|
|
|
|
|
class InstallCMakeLibs(install_lib):
|
|
"""
|
|
Get the libraries from the parent distribution, use those as the outfiles
|
|
|
|
Skip building anything; everything is already built, forward libraries to
|
|
the installation step
|
|
"""
|
|
|
|
def run(self):
|
|
"""
|
|
Copy libraries from the bin directory and place them as appropriate
|
|
"""
|
|
|
|
self.announce("Moving library files", level=3)
|
|
|
|
# We have already built the libraries in the previous build_ext step
|
|
|
|
self.skip_build = True
|
|
|
|
bin_dir = self.distribution.bin_dir
|
|
|
|
# Depending on the files that are generated from your cmake
|
|
# build chain, you may need to change the below code, such that
|
|
# your files are moved to the appropriate location when the installation
|
|
# is run
|
|
|
|
sharp_libs = [os.path.join(root, _lib) for root, _, files in
|
|
os.walk(os.path.join(bin_dir, 'sharplibs')) for _lib in files if
|
|
os.path.isfile(os.path.join(root, _lib)) and
|
|
(os.path.splitext(_lib)[-1] in [".dll", ".so", ".dylib", ".json"] or
|
|
_lib.startswith("lib"))]
|
|
|
|
for lib in sharp_libs:
|
|
shutil.move(lib, os.path.join(self.build_dir,
|
|
'nncase',
|
|
os.path.basename(lib)))
|
|
|
|
libs = [os.path.join(root, _lib) for root, _, files in
|
|
os.walk(bin_dir) for _lib in files if
|
|
'sharplibs' not in root and
|
|
os.path.isfile(os.path.join(root, _lib)) and
|
|
(os.path.splitext(_lib)[-1] in [".dll", ".so", ".dylib", ".json"] or
|
|
_lib.startswith("lib"))
|
|
and not (_lib.startswith("python") or _lib.startswith("_nncase"))]
|
|
|
|
for lib in libs:
|
|
shutil.move(lib, os.path.join(self.build_dir,
|
|
os.path.basename(lib)))
|
|
|
|
# Mark the libs for installation, adding them to
|
|
# distribution.data_files seems to ensure that setuptools' record
|
|
# writer appends them to installed-files.txt in the package's egg-info
|
|
#
|
|
# Also tried adding the libraries to the distribution.libraries list,
|
|
# but that never seemed to add them to the installed-files.txt in the
|
|
# egg-info, and the online recommendation seems to be adding libraries
|
|
# into eager_resources in the call to setup(), which I think puts them
|
|
# in data_files anyways.
|
|
#
|
|
# What is the best way?
|
|
|
|
# These are the additional installation files that should be
|
|
# included in the package, but are resultant of the cmake build
|
|
# step; depending on the files that are generated from your cmake
|
|
# build chain, you may need to modify the below code
|
|
|
|
data_files = [os.path.join(self.install_dir,
|
|
os.path.basename(lib))
|
|
for lib in libs]
|
|
data_files += [os.path.join(self.install_dir,
|
|
'nncase',
|
|
os.path.basename(lib))
|
|
for lib in sharp_libs]
|
|
self.distribution.data_files = data_files
|
|
|
|
# Must be forced to run after adding the libs to data_files
|
|
|
|
self.distribution.run_command("install_data")
|
|
|
|
super().run()
|
|
|
|
|
|
class InstallCMakeScripts(install_scripts):
|
|
"""
|
|
Install the scripts in the build dir
|
|
"""
|
|
|
|
def run(self):
|
|
"""
|
|
Copy the required directory to the build directory and super().run()
|
|
"""
|
|
|
|
self.announce("Moving scripts files", level=3)
|
|
|
|
# Scripts were already built in a previous step
|
|
|
|
self.skip_build = True
|
|
|
|
bin_dir = self.distribution.bin_dir
|
|
|
|
scripts_dirs = [os.path.join(bin_dir, _dir) for _dir in
|
|
os.listdir(bin_dir) if
|
|
os.path.isdir(os.path.join(bin_dir, _dir))]
|
|
|
|
for scripts_dir in scripts_dirs:
|
|
|
|
shutil.move(scripts_dir,
|
|
os.path.join(self.build_dir,
|
|
os.path.basename(scripts_dir)))
|
|
|
|
# Mark the scripts for installation, adding them to
|
|
# distribution.scripts seems to ensure that the setuptools' record
|
|
# writer appends them to installed-files.txt in the package's egg-info
|
|
|
|
self.distribution.scripts = scripts_dirs
|
|
|
|
super().run()
|
|
|
|
|
|
class BuildCMakeExt(build_ext):
|
|
"""
|
|
Builds using cmake instead of the python setuptools implicit build
|
|
"""
|
|
|
|
def run(self):
|
|
"""
|
|
Perform build_cmake before doing the 'normal' stuff
|
|
"""
|
|
|
|
for ext in self.extensions:
|
|
if isinstance(ext, CMakeExtension):
|
|
self.build_cmake(ext)
|
|
super().run()
|
|
|
|
def build_cmake(self, ext: Extension):
|
|
"""
|
|
The steps required to build the extension
|
|
"""
|
|
|
|
self.announce("Preparing the build environment", level=3)
|
|
|
|
extpath = os.path.abspath(self.get_ext_fullpath(ext.name))
|
|
extdir = os.path.abspath(os.path.dirname(
|
|
self.get_ext_fullpath(ext.name)))
|
|
|
|
# required for auto-detection of auxiliary "native" libs
|
|
if not extdir.endswith(os.path.sep):
|
|
extdir += os.path.sep
|
|
|
|
bin_dir = os.path.abspath(os.path.join(self.build_temp, 'install'))
|
|
cmake_args = ['-G', 'Ninja', '-DDOTNET_INIT_FOR_CONFIG=OFF']
|
|
if platform.system() == 'Windows':
|
|
cmake_args += ['-DCMAKE_C_COMPILER=clang-cl']
|
|
cmake_args += ['-DCMAKE_CXX_COMPILER=clang-cl']
|
|
cmake_args += ['-DPython3_ROOT_DIR=' + os.path.dirname(sys.executable)]
|
|
|
|
cfg = 'Debug' if self.debug else 'Release'
|
|
build_args = ['--config', cfg]
|
|
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
|
|
install_args = ['--prefix', bin_dir]
|
|
|
|
if not os.path.exists(self.build_temp):
|
|
os.makedirs(self.build_temp)
|
|
|
|
# Now that the necessary directories are created, build
|
|
|
|
self.announce("Configuring cmake project", level=3)
|
|
|
|
# Change your cmake arguments below as necessary
|
|
# Below is just an example set of arguments for building Blender as a Python module
|
|
|
|
self.spawn(['cmake', '-S' + ext.sourcedir, '-B' + self.build_temp] +
|
|
cmake_args)
|
|
|
|
self.announce("Building binaries", level=3)
|
|
|
|
self.spawn(["cmake", "--build", self.build_temp] + build_args)
|
|
self.spawn(["cmake", "--install", self.build_temp] + install_args)
|
|
|
|
# Build finished, now copy the files into the copy directory
|
|
# The copy directory is the parent directory of the extension (.pyd)
|
|
|
|
self.announce("Moving built python module", level=3)
|
|
|
|
self.distribution.bin_dir = bin_dir
|
|
|
|
pyd_path = [os.path.join(root, _pyd) for root, _, files in
|
|
os.walk(bin_dir) for _pyd in files if
|
|
os.path.isfile(os.path.join(root, _pyd)) and
|
|
os.path.splitext(_pyd)[0].startswith('_nncase') and
|
|
os.path.splitext(_pyd)[-1] in [".pyd", ".so"]][0]
|
|
|
|
shutil.move(pyd_path, extpath)
|
|
|
|
# copy nncase publish
|
|
nncase_libs = [os.path.join(root, _lib) for root, _, files in
|
|
os.walk(os.path.join(ext.sourcedir, 'install')) for _lib in files if
|
|
os.path.isfile(os.path.join(root, _lib)) and
|
|
(os.path.splitext(_lib)[-1] in [".dll", ".so", ".dylib", ".json"] or
|
|
_lib.startswith("lib"))]
|
|
|
|
sharp_libs_dir = os.path.join(bin_dir, 'sharplibs')
|
|
os.makedirs(sharp_libs_dir)
|
|
for lib in nncase_libs:
|
|
shutil.copy(lib, os.path.join(sharp_libs_dir, os.path.basename(lib)))
|
|
|
|
# After build_ext is run, the following commands will run:
|
|
#
|
|
# install_lib
|
|
# install_scripts
|
|
#
|
|
# These commands are subclassed above to avoid pitfalls that
|
|
# setuptools tries to impose when installing these, as it usually
|
|
# wants to build those libs and scripts as well or move them to a
|
|
# different place. See comments above for additional information
|
|
|
|
|
|
def find_version():
|
|
with io.open("CMakeLists.txt", encoding="utf8") as f:
|
|
version_file = f.read()
|
|
|
|
version_prefix = re.findall(r"NNCASE_VERSION \"(.+)\"", version_file)
|
|
|
|
if version_prefix:
|
|
repo_path = os.getcwd()
|
|
repo = Repo(repo_path)
|
|
if repo.tags:
|
|
latest_commit = subprocess.check_output(
|
|
['git', 'rev-parse', 'HEAD']).decode('utf-8').strip()
|
|
tagged_commit = subprocess.check_output(
|
|
['git', 'rev-list', '-n', '1', repo.tags[-1].name]).decode('utf-8').strip()
|
|
if latest_commit == tagged_commit:
|
|
return version_prefix[0]
|
|
|
|
version_suffix = time.strftime("%Y%m%d", time.localtime())
|
|
return version_prefix[0] + "." + version_suffix
|
|
|
|
raise RuntimeError("Unable to find version string.")
|
|
|
|
|
|
setup(name='nncase',
|
|
version=find_version(),
|
|
packages=['nncase'],
|
|
package_dir={'': 'python'},
|
|
ext_modules=[CMakeExtension(name="_nncase", sourcedir='.')],
|
|
cmdclass={
|
|
'build_ext': BuildCMakeExt,
|
|
'install_data': InstallCMakeLibsData,
|
|
'install_lib': InstallCMakeLibs
|
|
}
|
|
)
|