diff --git a/.github/conda-env/build-env.yml b/.github/conda-env/build-env.yml index 64ef8932..a2611b09 100644 --- a/.github/conda-env/build-env.yml +++ b/.github/conda-env/build-env.yml @@ -1,4 +1,6 @@ name: build-env dependencies: - - boa - - numpy!=1.23.0 + - conda-build + - conda-verify + # - boa # re-enable when boa is compatible with mamba 1.0 and Python 3.11 + - numpy !=1.23.0 diff --git a/.github/workflows/slycot-build-and-test.yml b/.github/workflows/slycot-build-and-test.yml index b8f84ade..1af15d3f 100644 --- a/.github/workflows/slycot-build-and-test.yml +++ b/.github/workflows/slycot-build-and-test.yml @@ -23,9 +23,9 @@ jobs: fetch-depth: 0 submodules: 'recursive' - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: '3.11' - name: Setup Ubuntu run: | sudo apt-get -y install gfortran cmake --fix-missing @@ -58,23 +58,23 @@ jobs: - 'macos' python: - '3.7' - - '3.10' + - '3.11' bla_vendor: [ 'unset' ] include: - os: 'ubuntu' - python: '3.10' + python: '3.11' bla_vendor: 'Generic' - os: 'ubuntu' - python: '3.10' + python: '3.11' bla_vendor: 'OpenBLAS' - os: 'macos' - python: '3.10' + python: '3.11' bla_vendor: 'Apple' - os: 'macos' - python: '3.10' + python: '3.11' bla_vendor: 'Generic' - os: 'macos' - python: '3.10' + python: '3.11' bla_vendor: 'OpenBLAS' steps: @@ -84,7 +84,7 @@ jobs: fetch-depth: 0 submodules: 'recursive' - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Setup Ubuntu @@ -113,7 +113,7 @@ jobs: echo "bla_vendor option ${{ matrix.bla_vendor }} not supported" exit 1 ;; esac - echo "FC=gfortran-10" >> $GITHUB_ENV + echo "FC=gfortran-11" >> $GITHUB_ENV - name: Build wheel env: BLA_VENDOR: ${{ matrix.bla_vendor }} @@ -144,7 +144,7 @@ jobs: - 'windows' python: - '3.9' - - '3.10' + - '3.11' steps: - name: Checkout Slycot @@ -168,7 +168,7 @@ jobs: run: | set -e numpyversion=$(python -c 'import numpy; print(numpy.version.version)') - conda mambabuild --python "${{ matrix.python }}" --numpy $numpyversion conda-recipe + conda build --python "${{ matrix.python }}" --numpy $numpyversion conda-recipe # preserve directory structure for custom conda channel find "${CONDA_PREFIX}/conda-bld" -maxdepth 2 -name 'slycot*.tar.bz2' | while read -r conda_pkg; do conda_platform=$(basename $(dirname "${conda_pkg}")) @@ -243,7 +243,7 @@ jobs: repository: 'python-control/python-control' path: python-control - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Setup Ubuntu diff --git a/CMakeLists.txt b/CMakeLists.txt index b018ff83..d7b015c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,27 +1,25 @@ # CMake file for use in conjunction with scikit-build -cmake_minimum_required(VERSION 3.11.0) +cmake_minimum_required(VERSION 3.14.0) -if (CMAKE_VERSION VERSION_GREATER "3.11.99") - cmake_policy(SET CMP0074 NEW) -endif() +cmake_policy(SET CMP0074 NEW) -project(slycot VERSION ${SLYCOT_VERSION} LANGUAGES NONE) +project(slycot LANGUAGES NONE) enable_language(C) enable_language(Fortran) -find_package(PythonLibs REQUIRED) +find_package(Python COMPONENTS Interpreter Development NumPy REQUIRED) find_package(PythonExtensions REQUIRED) find_package(NumPy REQUIRED) find_package(F2PY REQUIRED) find_package(BLAS REQUIRED) find_package(LAPACK REQUIRED) -message(STATUS "NumPy included from: ${NumPy_INCLUDE_DIR}") -message(STATUS "F2PY included from: ${F2PY_INCLUDE_DIR}") +message(STATUS "Python headers included from: ${Python_INCLUDE_DIRS}") +message(STATUS "NumPy headers included from: ${Python_NumPy_INCLUDE_DIRS}") +message(STATUS "F2PY headers included from: ${F2PY_INCLUDE_DIRS}") message(STATUS "LAPACK: ${LAPACK_LIBRARIES}") message(STATUS "BLAS: ${BLAS_LIBRARIES}") -message(STATUS "Slycot version: ${SLYCOT_VERSION}") add_subdirectory(slycot) diff --git a/MANIFEST.in b/MANIFEST.in index 8fecc477..b8c04e7c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,7 +9,6 @@ include conda-recipe/* include slycot/CMakeLists.txt include slycot/tests/CMakeLists.txt include slycot/*.py -include slycot/version.py.in include slycot/src/*.f include slycot/tests/*.py graft slycot/src/SLICOT-Reference diff --git a/README.rst b/README.rst index 1dcde8bc..df1dbc22 100644 --- a/README.rst +++ b/README.rst @@ -19,18 +19,18 @@ Riccati, Lyapunov, and Sylvester equations. Dependencies ------------ -Slycot supports Python versions 3.6 or later. +Slycot supports Python versions 3.7 or later. To run the compiled Slycot package, the following must be installed as dependencies: -- Python 3.6+ +- Python 3.7+ - NumPy If you are compiling and installing Slycot from source, you will need the following dependencies: -- 3.6+ +- Python 3.7+ - NumPy - scikit-build - CMake diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index b3e846db..7b416356 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -13,7 +13,7 @@ requirements: build: - {{ compiler('fortran') }} # [not win] - {{ compiler('c') }} - - cmake + - cmake >=3.14 - make # [linux] - flang >=11 # [win] @@ -27,9 +27,11 @@ requirements: - libcblas * *netlib - liblapack * *netlib - python - - numpy!=1.23.0 + - numpy !=1.23.0 - pip - - scikit-build >=0.14.1 + - scikit-build >=0.15 + - setuptools >=45 + - setuptools_scm >=6.3 run: - python {{ PY_VER }} diff --git a/pyproject.toml b/pyproject.toml index e55372af..1c6f74cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,55 @@ [build-system] requires = [ - "setuptools", + "setuptools>=45", + "setuptools_scm>=6.3", "wheel", - "scikit-build>=0.14.1", - "cmake", + "scikit-build>=0.15", + "cmake>=3.14", "numpy!=1.23.0"] build-backend = "setuptools.build_meta" + +[project] +name = "slycot" +description = "A wrapper for the SLICOT control and systems library" +readme = "README.rst" +authors = [{ name = "Enrico Avventi et al." }] +maintainers = [{ name = "Slycot developers", email = "python-control-discuss@lists.sourceforge.net"}] +license = {text = "GPL-2.0 AND BSD-3-Clause"} +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved", + "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", + "License :: OSI Approved :: BSD License", + "Programming Language :: C", + "Programming Language :: Fortran", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", +] +requires-python = ">=3.7" +dependencies = [ + "numpy", +] +dynamic = ["version"] + +[project.urls] +homepage = "https://github.com/python-control/Slycot" + + +[tool.setuptools_scm] +write_to = "slycot/version.py" + +[tool.pytest.ini_options] +# run the tests with compiled and installed package +addopts = "--pyargs slycot" diff --git a/setup.cfg.in b/setup.cfg.in deleted file mode 100644 index 6473f3df..00000000 --- a/setup.cfg.in +++ /dev/null @@ -1,10 +0,0 @@ -[metadata] - -name = slycot -version = @version@ -gitrevision = @gitrevision@ -release = @release@ - -[tool:pytest] -# run the tests with compiled and installed package -addopts = --pyargs slycot diff --git a/setup.py b/setup.py index 1f7f6707..5ee3cb4f 100644 --- a/setup.py +++ b/setup.py @@ -8,14 +8,7 @@ import builtins import os -import sys import subprocess -import re -import platform -try: - import configparser -except ImportError: - import ConfigParser as configparser try: from skbuild import setup @@ -23,96 +16,10 @@ except ImportError: raise ImportError('scikit-build must be installed before running setup.py') -DOCLINES = __doc__.split("\n") - -CLASSIFIERS = """\ -Development Status :: 4 - Beta -Intended Audience :: Science/Research -Intended Audience :: Developers -License :: OSI Approved -License :: OSI Approved :: GNU General Public License v2 (GPLv2) -Programming Language :: C -Programming Language :: Fortran -Programming Language :: Python -Programming Language :: Python :: 3.7 -Programming Language :: Python :: 3.8 -Programming Language :: Python :: 3.9 -Programming Language :: Python :: 3.10 -Topic :: Software Development -Topic :: Scientific/Engineering -Operating System :: Microsoft :: Windows -Operating System :: POSIX -Operating System :: Unix -Operating System :: MacOS -""" - -# defaults -ISRELEASED = True -# assume a version set by conda, next update with git, -# otherwise count on default -VERSION = 'Unknown' - - -class GitError(RuntimeError): - """Exception for git errors occuring in in git_version""" - pass - - -def git_version(srcdir=None): - """Return the git version, revision and cycle - - Uses rev-parse to get the revision tag to get the version number from the - latest tag and detects (approximate) revision cycles - - """ - def _minimal_ext_cmd(cmd, srcdir): - # construct minimal environment - env = {} - for k in ['SYSTEMROOT', 'PATH']: - v = os.environ.get(k) - if v is not None: - env[k] = v - # LANGUAGE is used on win32 - env['LANGUAGE'] = 'C' - env['LANG'] = 'C' - env['LC_ALL'] = 'C' - proc = subprocess.Popen( - cmd, - cwd=srcdir, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env) - out, err = proc.communicate() - if proc.returncode: - errmsg = err.decode('ascii', errors='ignore').strip() - raise GitError("git err; return code %d, error message:\n '%s'" - % (proc.returncode, errmsg)) - return out - - try: - GIT_VERSION = VERSION - GIT_REVISION = 'Unknown' - GIT_CYCLE = 0 - out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'], srcdir) - GIT_REVISION = out.strip().decode('ascii') - out = _minimal_ext_cmd(['git', 'tag'], srcdir) - GIT_VERSION = out.strip().decode('ascii').split('\n')[-1][1:] - out = _minimal_ext_cmd(['git', 'describe', '--tags', - '--long', '--always'], srcdir) - try: - # don't get a good description with shallow clones - GIT_CYCLE = out.strip().decode('ascii').split('-')[1] - except IndexError: - pass - except OSError: - pass - - return GIT_VERSION, GIT_REVISION, GIT_CYCLE - -# BEFORE importing distutils, remove MANIFEST. distutils doesn't properly -# update it when the contents of directories change. -if os.path.exists('MANIFEST'): - os.remove('MANIFEST') +try: + from setuptools_scm import get_version +except ImportError: + raise ImportError('setuptools_scm must be installed before running setup.py') # This is a bit hackish: we are setting a global variable so that the main # slycot __init__ can detect if it is being loaded by the setup routine, to @@ -121,69 +28,6 @@ def _minimal_ext_cmd(cmd, srcdir): builtins.__SLYCOT_SETUP__ = True -def rewrite_setup_cfg(version, gitrevision, release): - toreplace = dict(locals()) - data = ''.join(open('setup.cfg.in', 'r').readlines()).split('@') - for k, v in toreplace.items(): - idx = data.index(k) - data[idx] = v - cfg = open('setup.cfg', 'w') - cfg.write(''.join(data)) - cfg.close() - - -def get_version_info(srcdir=None): - global ISRELEASED - GIT_CYCLE = 0 - - # Adding the git rev number needs to be done inside write_version_py(), - # otherwise the import of slycot.version messes up - # the build under Python 3. - if os.environ.get('CONDA_BUILD', False): - FULLVERSION = os.environ.get('PKG_VERSION', '???') - GIT_REVISION = os.environ.get('GIT_DESCRIBE_HASH', '') - ISRELEASED = True - rewrite_setup_cfg(FULLVERSION, GIT_REVISION, 'yes') - elif os.path.exists('.git'): - FULLVERSION, GIT_REVISION, GIT_CYCLE = git_version(srcdir) - ISRELEASED = (GIT_CYCLE == 0) - rewrite_setup_cfg(FULLVERSION, GIT_REVISION, - (ISRELEASED and 'yes') or 'no') - elif os.path.exists('setup.cfg'): - # valid distribution - setupcfg = configparser.ConfigParser(allow_no_value=True) - setupcfg.read('setup.cfg') - - FULLVERSION = setupcfg.get(section='metadata', option='version') - - if FULLVERSION is None: - FULLVERSION = "Unknown" - - GIT_REVISION = setupcfg.get(section='metadata', option='gitrevision') - - if GIT_REVISION is None: - GIT_REVISION = "" - - return FULLVERSION, GIT_REVISION - else: - - # try to find a version number from the dir name - dname = os.getcwd().split(os.sep)[-1] - - m = re.search(r'[0-9.]+', dname) - if m: - FULLVERSION = m.group() - GIT_REVISION = '' - - else: - FULLVERSION = VERSION - GIT_REVISION = "Unknown" - - if not ISRELEASED: - FULLVERSION += '.' + str(GIT_CYCLE) - - return FULLVERSION, GIT_REVISION - def check_submodules(): """ verify that the submodules are checked out and clean use `git submodule update --init`; on failure @@ -212,43 +56,11 @@ def run(self): check_submodules() sdist.run(self) -def setup_package(): - src_path = os.path.dirname(os.path.abspath(__file__)) - sys.path.insert(0, src_path) - - # Rewrite the version file everytime - VERSION, gitrevision = get_version_info(src_path) - - metadata = dict( - name='slycot', - packages=['slycot', 'slycot.tests'], - cmake_languages=('C', 'Fortran'), - version=VERSION, - maintainer="Slycot developers", - maintainer_email="python-control-discuss@lists.sourceforge.net", - description=DOCLINES[0], - long_description=open('README.rst').read(), - url='https://github.com/python-control/Slycot', - author='Enrico Avventi et al.', - license='GPL-2.0', - classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f], - platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"], - cmdclass={"sdist": sdist_checked}, - cmake_args=['-DSLYCOT_VERSION:STRING=' + VERSION, - '-DGIT_REVISION:STRING=' + gitrevision, - '-DISRELEASE:STRING=' + str(ISRELEASED), - '-DFULL_VERSION=' + VERSION + '.git' + gitrevision[:7]], - zip_safe=False, - install_requires=['numpy'], - python_requires=">=3.7" - ) - - try: - setup(**metadata) - finally: - del sys.path[0] - return - - -if __name__ == '__main__': - setup_package() +# These need to stay in setup.py +# https://scikit-build.readthedocs.io/en/latest/usage.html#setuptools-options +setup( + packages=['slycot', 'slycot.tests'], + cmdclass={'sdist': sdist_checked}, + cmake_languages=('C', 'Fortran'), + use_scm_version = True, +) diff --git a/slycot/CMakeLists.txt b/slycot/CMakeLists.txt index f656c326..46980725 100644 --- a/slycot/CMakeLists.txt +++ b/slycot/CMakeLists.txt @@ -630,13 +630,11 @@ set(F2PYSOURCE_DEPS src/transform.pyf src/synthesis.pyf src/_helper.pyf) -configure_file(version.py.in version.py @ONLY) - set(PYSOURCE __init__.py examples.py exceptions.py analysis.py math.py synthesis.py transform.py - ${CMAKE_CURRENT_BINARY_DIR}/version.py) +) set(SLYCOT_MODULE "_wrapper") @@ -666,8 +664,9 @@ target_link_libraries(${SLYCOT_MODULE} target_include_directories( ${SLYCOT_MODULE} PUBLIC + ${Python_INCLUDE_DIRS} + ${Python_NumPy_INCLUDE_DIRS} ${F2PY_INCLUDE_DIRS} - ${PYTHON_INCLUDE_DIRS} ) if (UNIX) diff --git a/slycot/__init__.py b/slycot/__init__.py index 836c8a51..6bd59014 100644 --- a/slycot/__init__.py +++ b/slycot/__init__.py @@ -45,8 +45,7 @@ from .transform import tf01md, tf01rd from .transform import td04ad, tb01pd - # Version information - from .version import version as __version__ + from .version import __version__ def test(): diff --git a/slycot/version.py.in b/slycot/version.py.in deleted file mode 100644 index 0843bf15..00000000 --- a/slycot/version.py.in +++ /dev/null @@ -1,10 +0,0 @@ - -# THIS FILE IS GENERATED FROM SLYCOT SETUP.PY -short_version = '@SLYCOT_VERSION@' -version = '@SLYCOT_VERSION@' -full_version = '@FULL_VERSION@' -git_revision = '@GIT_REVISION@' -release = @ISRELEASE@ - -if not release: - version = full_version