From 15be350aece67c897ffb6fa6d7073ddd2404a47e Mon Sep 17 00:00:00 2001 From: Gage Larsen Date: Wed, 9 Mar 2022 10:16:18 -0700 Subject: [PATCH] Initial repository --- .github/workflows/xmsconan-ci.yaml | 33 +++++ .gitignore | 56 ++++++++ setup.py | 21 +++ xmsconan/__init__.py | 3 + xmsconan/build_helpers.py | 103 +++++++++++++++ xmsconan/xms_conan_file.py | 204 +++++++++++++++++++++++++++++ 6 files changed, 420 insertions(+) create mode 100644 .github/workflows/xmsconan-ci.yaml create mode 100644 .gitignore create mode 100644 setup.py create mode 100644 xmsconan/__init__.py create mode 100644 xmsconan/build_helpers.py create mode 100644 xmsconan/xms_conan_file.py diff --git a/.github/workflows/xmsconan-ci.yaml b/.github/workflows/xmsconan-ci.yaml new file mode 100644 index 0000000..395b196 --- /dev/null +++ b/.github/workflows/xmsconan-ci.yaml @@ -0,0 +1,33 @@ +name: xmsconan + +on: [push, pull_request] + +jobs: + flake: + name: Flake Project + runs-on: ${{ matrix.platform }} + + strategy: + fail-fast: false + matrix: + platform: [ubuntu-18.04] + python-version: ["3.10"] + + steps: + # Checkout Sources + - name: Checkout Source + uses: actions/checkout@v2 + # Setup Python + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + # Install Python Dependencies + - name: Install Python Dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 flake8-docstrings flake8-bugbear flake8-import-order pep8-naming + # Flake Code + - name: Run Flake + run: | + flake8 --exclude .tox,.git,__pycache__,_package/tests/files/*,pydocs/source/conf.py,build,dist,tests/fixtures/*,*.pyc,*.egg-info,.cache,.eggs --ignore=D200,D212 --max-line-length=120 --docstring-convention google --isolated --import-order-style=appnexus --application-import-names=xmsconan --application-package-names=xms --count --statistics . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efa0371 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Misc +.*.swp +*.DS_STORE +*~ +*.pyc +.idea/ +build/ +cmake-build-debug/ +cmake-build-release/ +test_package/build/ +*.egg-info/ + +# Doxygen +Doxygen/doxy_warn.log +Doxygen/sphinx_warnings.log +Doxygen/xmscore.tag +Doxygen/html/* + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll +*.pyd + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# build directories +build/* +pybuild/* +build_py/* diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bd0cc1b --- /dev/null +++ b/setup.py @@ -0,0 +1,21 @@ +""" +Setup.py file for the xms.core python package. +""" +from setuptools import setup + +requires = [] + + +version = '0.0.0' + +setup( + python_requires='>=3.10', + name='xmsconan', + version=version, + packages=['xmsconan'], + include_package_data=True, + license='BSD 2-Clause License', + description='', + author='Gage Larsen', + install_requires=requires, +) diff --git a/xmsconan/__init__.py b/xmsconan/__init__.py new file mode 100644 index 0000000..952205d --- /dev/null +++ b/xmsconan/__init__.py @@ -0,0 +1,3 @@ +""" +Methods and Modules used to aid in xmsconan projects. +""" diff --git a/xmsconan/build_helpers.py b/xmsconan/build_helpers.py new file mode 100644 index 0000000..1cf24ae --- /dev/null +++ b/xmsconan/build_helpers.py @@ -0,0 +1,103 @@ +""" +A group of functions to aid in conan builds. +""" +import os + +from cpt.packager import ConanMultiPackager + + +def get_builder(library_name): + """ + A function to build a matrix of conan BuildConfig objects. + + Args: + library_name (str): The name of the library the conan file is for. + + Returns: + List: A list of BuildConfig objects for the conan build. + """ + builder = ConanMultiPackager() + builder.add_common_builds() + + # Add environment variables to build definitions + xms_version = os.getenv('XMS_VERSION', None) + python_target_version = os.getenv('PYTHON_TARGET_VERSION', "3.10") + release_python = os.getenv('RELEASE_PYTHON', 'False') + aquapi_username = os.getenv('AQUAPI_USERNAME', None) + aquapi_password = os.getenv('AQUAPI_PASSWORD', None) + aquapi_url = os.getenv('AQUAPI_URL', None) + + for settings, options, env_vars, _, _ in builder.items: + # General Options + env_vars.update({ + 'XMS_VERSION': xms_version, + 'PYTHON_TARGET_VERSION': python_target_version, + 'RELEASE_PYTHON': release_python, + 'AQUAPI_USERNAME': aquapi_username, + 'AQUAPI_PASSWORD': aquapi_password, + 'AQUAPI_URL': aquapi_url, + }) + + # Require C++ standard compatibility + if settings['compiler'] == 'gcc': + settings.update({ + 'compiler.libcxx': 'libstdc++11' + }) + compiler_version = int(settings['compiler.version']) + if compiler_version in [5, 6]: + settings.update({'cppstd': '14'}) + elif compiler_version == 7: + settings.update({'cppstd': '17'}) + elif settings['compiler'] == 'apple-clang': + settings.update({'cppstd': 'gnu17'}) + elif settings['compiler'] == 'Visual Studio': + settings.update({'cppstd': '17'}) + + # These options are mutually exclusive. wchar_t:builtin == True + options.update({ + f'{library_name}:wchar_t': 'builtin', + f'{library_name}:pybind': False, + f'{library_name}:testing': False, + }) + + # wchar_t builders + wchar_t_update_builds = [] + for settings, options, env_vars, build_requires, _ in builder.items: + # wchar_t builds are only built for Visual Studio builds. + if settings['compiler'] == 'Visual Studio': + # Set wchar_t options and add a build configuration + wchar_t_options = dict(options) + wchar_t_options.update({ + f'{library_name}:wchar_t': 'typedef', + }) + wchar_t_update_builds.append([settings, wchar_t_options, env_vars, build_requires]) + + # pybind builders + pybind_updated_builds = [] + for settings, options, env_vars, build_requires, _ in builder.items: + # Pybind builds are built for 64-bit, non-debug MD(d) builds. + if settings['arch'] == 'x86_64' and settings['build_type'] != 'Debug' and \ + (settings['compiler'] != 'Visual Studio' or settings['compiler.runtime'] in ['MD', 'MDd']): + # Pybind is only built for visual studio versions greater than 12. + if settings['compiler'] == 'Visual Studio' and int(settings['compiler.version']) <= 12: + continue + # Update conan options and add a build configuration + pybind_options = dict(options) + pybind_options.update({ + f'{library_name}:pybind': True, + }) + pybind_updated_builds.append([settings, pybind_options, env_vars, build_requires]) + + # testing_builders + testing_update_builds = [] + for settings, options, env_vars, build_requires, _ in builder.items: + # Testing builds are built for each base configuration + testing_options = dict(options) + testing_options.update({ + f'{library_name}:testing': True, + }) + testing_update_builds.append([settings, testing_options, env_vars, build_requires]) + + builder.builds = builder.items + wchar_t_update_builds + pybind_updated_builds + testing_update_builds + + return builder diff --git a/xmsconan/xms_conan_file.py b/xmsconan/xms_conan_file.py new file mode 100644 index 0000000..0840560 --- /dev/null +++ b/xmsconan/xms_conan_file.py @@ -0,0 +1,204 @@ +""" +Conanfile base for the xmscore projects. +""" +import os + +from conans import CMake, ConanFile, tools +from conans.errors import ConanException + + +class XmsConanFile(ConanFile): + """ + XmsConan class used for defining the conan info. + """ + license = "FreeBSD Software License" + settings = "os", "compiler", "build_type", "arch" + options = { + "wchar_t": ['typedef', 'builtin'], + "pybind": [True, False], + "testing": [True, False], + } + generators = "cmake", "txt" + build_requires = "cxxtest/4.4@aquaveo/stable" + + default_options = { + 'wchar_t': 'builtin', + 'pybind': False, + 'testing': False, + } + + def requirements(self): + """Requirements.""" + self.requires("boost/1.74.0.3@aquaveo/stable") + # Pybind if not clang + if not self.settings.compiler == "clang" and self.options.pybind: + self.requires("pybind11/2.9.1@aquaveo/stable") + + for dependency in self.xms_dependencies: + self.requires(dependency) + + def configure_options(self): + """ + Configure the options for the conan class. + """ + # TODO: Do we want to delete the options? + if self.settings.build_type != "Release": + del self.options.pybind + + if self.settings.compiler != 'Visual Studio': + del self.options.wchar_t + + def configure(self): + """ + The configure method. + """ + self.version = self.env.get('XMS_VERSION', '99.99.99') + + # Raise ConanExceptions for Unsupported Versions + s_os = self.settings.os + s_compiler = self.settings.compiler + s_compiler_version = self.settings.compiler.version + + if s_compiler == "apple-clang" and s_os == 'Linux': + raise ConanException("Clang on Linux is not supported.") + + if s_compiler == "gcc" and float(s_compiler_version.value) < 5.0: + raise ConanException("GCC < 5.0 is not supported.") + + if s_compiler == "apple-clang" and s_os == 'Macos' \ + and float(s_compiler_version.value) < 9.0: + raise ConanException("Clang > 9.0 is required for Mac.") + + if (self.options.wchar_t == 'typedef' and self.settings.compiler != 'Visual Studio'): + raise ConanException('wchar_t==typedef is only supported for' + 'Visual Studio') + + self.options['boost'].wchar_t = self.options.wchar_t + + def build(self): + """ + The build method for the conan class. + """ + cmake = CMake(self) + + # CxxTest doesn't play nice with PyBind. Also, it would be nice to not + # have tests in release code. Thus, if we want to run tests, we will + # build a test version (without python), run the tests, and then (on + # success) rebuild the library without tests. + cmake.definitions["IS_PYTHON_BUILD"] = self.options.pybind + cmake.definitions["BUILD_TESTING"] = self.options.testing + cmake.definitions["XMS_TEST_PATH"] = "test_files" + cmake.definitions['USE_TYPEDEF_WCHAR_T'] = ( + self.options.wchar_t == 'typedef') + + # Version Info + cmake.definitions["XMS_VERSION"] = '{}'.format(self.version) + cmake.definitions["PYTHON_TARGET_VERSION"] = self.env.get( + "PYTHON_TARGET_VERSION", "3.10") + + cmake.configure(source_folder=".") + cmake.build() + cmake.install() + + # Run the tests if it is testing configuration. + if self.options.testing: + self.run_cxx_tests(cmake) + + # If this build is python run the python tests. + elif self.options.pybind: + self.run_python_tests_and_upload() + + def package(self): + """ + The package method of the conan class. + """ + self.copy("license", dst="licenses", ignore_case=True, keep_path=False) + + def package_info(self): + """ + The package_info method of the conan class. + """ + self.env_info.PYTHONPATH.append( + os.path.join(self.package_folder, "_package")) + if self.settings.build_type == 'Debug': + self.cpp_info.libs = [f'{self.name}lib_d'] + else: + self.cpp_info.libs = [f'{self.name}lib'] + + def run_cxx_tests(self, cmake): + """ + A function to run the cxx_tests. + """ + try: + cmake.test() + except ConanException: + raise + finally: + if os.path.isfile("TEST-cxxtest.xml"): + with open("TEST-cxxtest.xml", "r") as f: + for line in f.readlines(): + no_newline = line.strip('\n') + self.output.info(no_newline) + + def run_python_tests_and_upload(self): + """A method to run the python tests, and upload if this is a release.""" + with tools.pythonpath(self): + + # Install required packages for python testing. + packages_to_install = ['numpy', 'wheel'] + if not self.settings.os == "Macos": + self.run(f'pip install --user {" ".join(packages_to_install)}') + else: + self.run(f'pip install {" ".join(packages_to_install)}') + + # Run python tests. + path_to_python_tests = os.path.join( + self.build_folder, '_package', 'tests') + self.run(f'python -m unittest discover -v -p *_pyt.py -s {path_to_python_tests}', + cwd=os.path.join(self.package_folder, "_package")) + + # Create and upload wheel to aquapi if release and windows + # We are uploading to aquapi here instead of pypi because pypi doesn't accept + # the type of package 'linux_x86_64 that we want to upload. They only accept + # manylinux1 as the plat-tag + is_release = self.env.get("RELEASE_PYTHON", 'False') == 'True' + is_mac_os = self.settings.os == 'Macos' + is_gcc_6 = self.settings.os == "Linux" and float( + self.settings.compiler.version.value) == 6.0 + is_windows_md = (self.settings.os == "Windows" and str( + self.settings.compiler.runtime) == "MD") + if is_release and (is_mac_os or is_gcc_6 or is_windows_md): + self.upload_python_package() + + def upload_python_package(self): + """ + Upload the python package to AQUAPI_URL. + """ + devpi_url = self.env.get("AQUAPI_URL", 'NO_URL') + devpi_username = self.env.get("AQUAPI_USERNAME", 'NO_USERNAME') + devpi_password = self.env.get("AQUAPI_PASSWORD", 'NO_PASSWORD') + self.run('devpi use {}'.format(devpi_url)) + self.run( + 'devpi login {} --password {}'.format(devpi_username, devpi_password)) + plat_names = {'Windows': 'win_amd64', + 'Linux': 'linux_x86_64', "Macos": 'macosx-10.6-intel'} + self.run('python setup.py bdist_wheel --plat-name={} --dist-dir {}'.format( + plat_names[str(self.settings.os)], + os.path.join(self.build_folder, "dist")), cwd=os.path.join(self.package_folder, "_package")) + self.run( + 'devpi upload --from-dir {}'.format(os.path.join(self.build_folder, "dist")), cwd=".") + + def export_sources(self): + """ + Specify sources to be exported. + """ + self.output.info('----- RUNNING EXPORT_SOURCES()') + self.copy('*', src=f'{self.name}', dst=f'{self.name}') + self.copy('*', src='_package', dst='_package') + + def export(self): + """ + Specify files to be exported. + """ + self.copy('CMakeLists.txt') + self.copy('LICENSE')