diff --git a/CMakeLists.txt b/CMakeLists.txt index ed4fe302b38d0730ed6aa213743b4f9c0d7967a0..93bc6fb666075b3591cc99300630b8d75f5adee4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,8 +8,22 @@ if(NOT DEFINED BGS_PYTHON_SUPPORT) elseif() # add_definitions(-DBGS_PYTHON_SUPPORT) endif() +# cmake -D BGS_PYTHON_ONLY=ON .. +if(NOT DEFINED BGS_PYTHON_ONLY) + set(BGS_PYTHON_ONLY OFF) +elseif() + # add_definitions(-DBGS_PYTHON_ONLY) +endif() +# cmake -D BGS_CORE_STATIC=ON .. +if(NOT DEFINED BGS_CORE_STATIC) + set(BGS_CORE_STATIC OFF) +elseif() + # add_definitions(-DBGS_CORE_STATIC) +endif() message(STATUS "") -message(STATUS "BGSLIBRARY WITH PYTHON SUPPORT: ${BGS_PYTHON_SUPPORT}") +message(STATUS "BGS_PYTHON_SUPPORT: ${BGS_PYTHON_SUPPORT}") +message(STATUS "BGS_PYTHON_ONLY: ${BGS_PYTHON_ONLY}") +message(STATUS "BGS_CORE_STATIC: ${BGS_CORE_STATIC}") # cmake -D BGS_PYTHON_SUPPORT=ON -D BGS_PYTHON_VERSION=3 .. if(NOT DEFINED BGS_PYTHON_VERSION) @@ -146,8 +160,10 @@ if(BGS_PYTHON_SUPPORT) message(STATUS "NUMPY_INCLUDE_DIR: ${NUMPY_INCLUDE_DIR}\n") endif() -file(GLOB main_src src/*.cpp src/*.c) -file(GLOB main_inc src/*.h src/*.hpp) +if(NOT BGS_PYTHON_ONLY) + file(GLOB main_src src/*.cpp src/*.c) + file(GLOB main_inc src/*.h src/*.hpp) +endif() file(GLOB_RECURSE utils_src src/utils/*.cpp src/utils/*.c) file(GLOB_RECURSE utils_inc src/utils/*.h src/utils/*.hpp) @@ -177,16 +193,20 @@ if(${OpenCV_VERSION} VERSION_LESS 2.4.3) list(REMOVE_ITEM bgs_src ${gmg}) endif() -add_library(bgslibrary_core SHARED ${bgs_src} ${tools_src} ${utils_src} ${bgs_inc} ${tools_inc} ${utils_inc}) -# generates the export header bgslibrary_core_EXPORTS.h automatically -include(GenerateExportHeader) -GENERATE_EXPORT_HEADER(bgslibrary_core - BASE_NAME bgslibrary_core - EXPORT_MACRO_NAME bgslibrary_core_EXPORTS - EXPORT_FILE_NAME bgslibrary_core_EXPORTS.h - STATIC_DEFINE BGSLIBRARY_CORE_EXPORTS_BUILT_AS_STATIC) -target_link_libraries(bgslibrary_core ${OpenCV_LIBS}) -set_property(TARGET bgslibrary_core PROPERTY PUBLIC_HEADER ${bgs_inc} ${tools_inc} ${utils_inc}) +if(BGS_CORE_STATIC) + add_library(bgslibrary_core STATIC ${bgs_src} ${tools_src} ${utils_src} ${bgs_inc} ${tools_inc} ${utils_inc}) +elseif() + add_library(bgslibrary_core SHARED ${bgs_src} ${tools_src} ${utils_src} ${bgs_inc} ${tools_inc} ${utils_inc}) + # generates the export header bgslibrary_core_EXPORTS.h automatically + include(GenerateExportHeader) + GENERATE_EXPORT_HEADER(bgslibrary_core + BASE_NAME bgslibrary_core + EXPORT_MACRO_NAME bgslibrary_core_EXPORTS + EXPORT_FILE_NAME bgslibrary_core_EXPORTS.h + STATIC_DEFINE BGSLIBRARY_CORE_EXPORTS_BUILT_AS_STATIC) + target_link_libraries(bgslibrary_core ${OpenCV_LIBS}) + set_property(TARGET bgslibrary_core PROPERTY PUBLIC_HEADER ${bgs_inc} ${tools_inc} ${utils_inc}) +endif() if(BGS_PYTHON_SUPPORT) #add_library(bgs_python SHARED ${bgs_src} ${tools_src}) @@ -223,9 +243,11 @@ endif() # endif() #endif() -add_executable(bgslibrary ${main_src} ${main_inc}) -target_link_libraries(bgslibrary ${OpenCV_LIBS} bgslibrary_core) -# set_target_properties(bgslibrary PROPERTIES OUTPUT_NAME bgs) +if(NOT BGS_PYTHON_ONLY) + add_executable(bgslibrary ${main_src} ${main_inc}) + target_link_libraries(bgslibrary ${OpenCV_LIBS} bgslibrary_core) + # set_target_properties(bgslibrary PROPERTIES OUTPUT_NAME bgs) +endif() if(UNIX AND BGS_PYTHON_SUPPORT) execute_process( @@ -244,24 +266,26 @@ if(UNIX AND BGS_PYTHON_SUPPORT) #install(TARGETS bgs_python DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}) endif() -install(TARGETS bgslibrary_core - bgslibrary - RUNTIME DESTINATION bin COMPONENT app - LIBRARY DESTINATION lib COMPONENT runtime - ARCHIVE DESTINATION lib COMPONENT runtime - #PUBLIC_HEADER DESTINATION include/bgslibrary COMPONENT dev - FRAMEWORK DESTINATION "/Library/Frameworks" -) +if(NOT BGS_PYTHON_ONLY) + install(TARGETS bgslibrary_core + bgslibrary + RUNTIME DESTINATION bin COMPONENT app + LIBRARY DESTINATION lib COMPONENT runtime + ARCHIVE DESTINATION lib COMPONENT runtime + #PUBLIC_HEADER DESTINATION include/bgslibrary COMPONENT dev + FRAMEWORK DESTINATION "/Library/Frameworks" + ) -if(UNIX) - # to avoid: error while loading shared libraries: libbgslibrary_core.so - message(STATUS "You might need to run:") - message(STATUS "$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib") - message(STATUS "$ export LD_LIBRARY_PATH") - message(STATUS "after 'make install' to avoid error while loading libbgslibrary_core\n") -endif() + if(UNIX) + # to avoid: error while loading shared libraries: libbgslibrary_core.so + message(STATUS "You might need to run:") + message(STATUS "$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib") + message(STATUS "$ export LD_LIBRARY_PATH") + message(STATUS "after 'make install' to avoid error while loading libbgslibrary_core\n") + endif() -if(WIN32) - message(STATUS "You might need to add ${CMAKE_CURRENT_BINARY_DIR} to your PATH to be able to run your applications.") - message(STATUS "> set PATH=%PATH%;${CMAKE_CURRENT_BINARY_DIR}\n") + if(WIN32) + message(STATUS "You might need to add ${CMAKE_CURRENT_BINARY_DIR} to your PATH to be able to run your applications.") + message(STATUS "> set PATH=%PATH%;${CMAKE_CURRENT_BINARY_DIR}\n") + endif() endif() diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..edea572f1cfbb9767e74a1568bab922d741c3bbd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,22 @@ +include *.txt +prune src +recursive-include src/algorithms *.* +recursive-include src/tools *.* +recursive-include src/utils *.* +prune wrapper +recursive-include wrapper/python *.* +recursive-include modules *.* +recursive-include pybgs *.* +prune build +prune dist +prune tools +prune test +prune gui +prune examples +prune docs +prune dataset +prune config +prune cmake-modules +prune _dist +prune _wiki +prune _opencv_cmake diff --git a/setup.py b/setup.py index ed07e0098bf47d99979ce334f027af39b7ae61ac..9dc2bfecd099f6359425dd680a1c5b5eee2587f2 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,10 @@ To build the bgslibrary: python setup.py build To build and install: python setup.py install +To install using pip: + pip install . +To install using PyPI: + pip install pybgs To package the wheel (after pip installing twine and wheel): python setup.py bdist_wheel To upload the binary wheel to PyPi @@ -11,88 +15,184 @@ To upload the source distribution to PyPi python setup.py sdist twine upload dist/pybgs-*.tar.gz """ -import os -import re -import sys -import platform -import subprocess +import os, re, sys, shutil, pathlib, platform, subprocess from setuptools import setup, find_packages, 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 +from distutils.command.install_data import install_data from distutils.version import LooseVersion -#import datetime -#now = datetime.datetime.now() -# -#pkg_properties={} -#with open('.properties') as fp: -# for line in fp: -# if '=' in line: -# name, value = line.replace('\n','').split('=', 1) -# if "SNAPSHOT" in value: -# dev_version = "." + now.strftime("%y%m%d%H%M") + ".dev" -# value = value.replace("-SNAPSHOT", dev_version) -# pkg_properties[name] = value - +PACKAGE_NAME = "pybgs" class CMakeExtension(Extension): def __init__(self, name, sourcedir=''): Extension.__init__(self, name, sources=[]) self.sourcedir = os.path.abspath(sourcedir) +class InstallCMakeLibsData(install_data): + """ + Just a wrapper to get the install data into the egg-info -class CMakeBuild(build_ext): + 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): - try: - out = subprocess.check_output(['cmake', '--version']) - except OSError: - raise RuntimeError("CMake must be installed to build the following extensions: " + - ", ".join(e.name for e in self.extensions)) + """ + 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 - if platform.system() == "Windows": - cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) - if cmake_version < '3.10.0': - raise RuntimeError("CMake >= 3.10.0 is required on Windows") + 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 + if hasattr(self.distribution, 'bin_dir'): + bin_dir = self.distribution.bin_dir + else: + bin_dir = os.path.join(self.build_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 + libs = [os.path.join(bin_dir, _lib) for _lib in + os.listdir(bin_dir) if + os.path.isfile(os.path.join(bin_dir, _lib)) and + os.path.splitext(_lib)[1] in [".dll", ".so"] + and not (_lib.startswith("python") or _lib.startswith(PACKAGE_NAME))] + 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 + self.distribution.data_files = [os.path.join(self.install_dir, + os.path.basename(lib)) + for lib in libs] + # Must be forced to run after adding the libs to data_files + self.distribution.run_command("install_data") + super().run() - for ext in self.extensions: - self.build_extension(ext) +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 extension in self.extensions: + self.build_cmake(extension) + super().run() - def build_extension(self, ext): + def build_cmake(self, extension: Extension): + """ + The steps required to build the extension + """ + self.announce("Preparing the build environment", level=3) + build_dir = pathlib.Path(self.build_temp) + extension_path = pathlib.Path(self.get_ext_fullpath(extension.name)) + os.makedirs(build_dir, exist_ok=True) + os.makedirs(extension_path.parent.absolute(), exist_ok=True) python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1]) - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable, + + # Now that the necessary directories are created, build + self.announce("Configuring cmake project", level=3) + cmake_args = ['-DPYTHON_EXECUTABLE=' + sys.executable, + '-DBGS_CORE_STATIC=ON', '-DBGS_PYTHON_SUPPORT=ON', + '-DBGS_PYTHON_ONLY=ON', '-DBGS_PYTHON_VERSION=' + python_version] + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + self.spawn(['cmake', '-H'+extension.sourcedir, '-B'+self.build_temp]+ cmake_args) - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg] + self.announce("Building binaries", level=3) + self.spawn(["cmake", "--build", self.build_temp, + "--config", "Release", '--', '-j8']) - if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - build_args += ['--', '/m'] - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - build_args += ['--', '-j8'] + # 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) + bin_dir = os.path.join(build_dir) + self.distribution.bin_dir = bin_dir + pyd_path = [os.path.join(bin_dir, _pyd) for _pyd in + os.listdir(bin_dir) if + os.path.isfile(os.path.join(bin_dir, _pyd)) and + os.path.splitext(_pyd)[0].startswith(PACKAGE_NAME) and + os.path.splitext(_pyd)[1] in [".pyd", ".so"]][0] + shutil.move(pyd_path, extension_path) - env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), - self.distribution.get_version()) - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) - print() + # 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 with open("README.md", "r") as fh: long_description = fh.read() setup( name='pybgs', - version='3.0.0', - #version=pkg_properties["version"], + version='3.0.0.post0', author='Andrews Sobral', author_email='andrewssobral@gmail.com', url='https://github.com/andrewssobral/bgslibrary', @@ -100,10 +200,14 @@ setup( description='Python wrapper for bgslibrary using pybind11 and CMake', long_description=long_description, long_description_content_type="text/markdown", - ext_modules=[CMakeExtension('src')], - cmdclass=dict(build_ext=CMakeBuild), + ext_modules=[CMakeExtension(name='pybgs', sourcedir='.')], + cmdclass={ + 'build_ext': BuildCMakeExt, + 'install_data': InstallCMakeLibsData, + 'install_lib': InstallCMakeLibs, + #'install_scripts': InstallCMakeScripts + }, zip_safe=False, - #packages=find_packages('pybgs'), - #package_dir={'':'pybgs'}, + packages=find_packages(), keywords=['BGSLibrary', 'Background Subtraction', 'Computer Vision', 'Machine Learning'], )