diff --git a/cf_units/__init__.py b/cf_units/__init__.py index 33366729..da56e291 100644 --- a/cf_units/__init__.py +++ b/cf_units/__init__.py @@ -32,7 +32,6 @@ ) from . import config -from ._version import version as __version__ # noqa: F401 from .util import _OrderedHashable __all__ = [ diff --git a/cf_units/_udunits2_parser/meson.build b/cf_units/_udunits2_parser/meson.build new file mode 100644 index 00000000..40baeee6 --- /dev/null +++ b/cf_units/_udunits2_parser/meson.build @@ -0,0 +1,13 @@ +python_sources = [ + '__init__.py', + 'compile.py', + 'graph.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/_udunits2_parser' +) + +subdir('parser') diff --git a/cf_units/_udunits2_parser/parser/meson.build b/cf_units/_udunits2_parser/parser/meson.build new file mode 100644 index 00000000..84202f45 --- /dev/null +++ b/cf_units/_udunits2_parser/parser/meson.build @@ -0,0 +1,12 @@ +python_sources = [ + '__init__.py', + 'udunits2Lexer.py', + 'udunits2Parser.py', + 'udunits2ParserVisitor.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/_udunits2_parser/parser' +) diff --git a/cf_units/meson.build b/cf_units/meson.build new file mode 100644 index 00000000..0cdac030 --- /dev/null +++ b/cf_units/meson.build @@ -0,0 +1,25 @@ +python_sources = [ + '__init__.py', + 'config.py', + 'tex.py', + 'util.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units' +) + +udunits2 = dependency('udunits') + +py.extension_module( + '_udunits2', + ['_udunits2.pyx'], + dependencies: [udunits2], + install: true, + subdir: 'cf_units' +) + +subdir('tests') +subdir('_udunits2_parser') diff --git a/cf_units/tests/integration/meson.build b/cf_units/tests/integration/meson.build new file mode 100644 index 00000000..41196399 --- /dev/null +++ b/cf_units/tests/integration/meson.build @@ -0,0 +1,16 @@ +python_sources = [ + '__init__.py', + 'test_date2num.py', + 'test_num2date.py', + 'test_num2pydate.py', + 'test__Unit_date2num.py', + 'test__Unit_num2date.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/tests/integration' +) + +subdir('parse') diff --git a/cf_units/tests/integration/parse/meson.build b/cf_units/tests/integration/parse/meson.build new file mode 100644 index 00000000..bc4aa016 --- /dev/null +++ b/cf_units/tests/integration/parse/meson.build @@ -0,0 +1,10 @@ +python_sources = [ + 'test_graph.py', + 'test_parse.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/tests/integration/parse' +) diff --git a/cf_units/tests/meson.build b/cf_units/tests/meson.build new file mode 100644 index 00000000..79e96588 --- /dev/null +++ b/cf_units/tests/meson.build @@ -0,0 +1,15 @@ +python_sources = [ + '__init__.py', + 'test_coding_standards.py', + 'test_tex.py', + 'test_unit.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/tests' +) + +subdir('integration') +subdir('unit') diff --git a/cf_units/tests/unit/meson.build b/cf_units/tests/unit/meson.build new file mode 100644 index 00000000..b0d86cec --- /dev/null +++ b/cf_units/tests/unit/meson.build @@ -0,0 +1,12 @@ +python_sources = [ + '__init__.py', + 'test__udunits2.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/tests/unit' +) + +subdir('unit') diff --git a/cf_units/tests/unit/unit/meson.build b/cf_units/tests/unit/unit/meson.build new file mode 100644 index 00000000..ac07c789 --- /dev/null +++ b/cf_units/tests/unit/unit/meson.build @@ -0,0 +1,10 @@ +python_sources = [ + 'test_as_unit.py', + 'test_Unit.py', +] + +py.install_sources( + python_sources, + pure: false, + subdir: 'cf_units/tests/unit/unit' +) diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..ebc66067 --- /dev/null +++ b/meson.build @@ -0,0 +1,6 @@ +project('cf-units', 'cython', version: '1.3.0') + +py_mod = import('python') +py = py_mod.find_installation(pure: true) + +subdir('cf_units') diff --git a/pyproject.toml b/pyproject.toml index 3fd0732b..21c3c3b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,28 @@ -[build-system] -# Defined by PEP 518 -requires = [ - "setuptools>=45", - "setuptools_scm[toml]>=7.0", - "wheel", - "oldest-supported-numpy", - "Cython" +[project] +name = "cf-units" +dynamic = ["version"] +description = "Units of measure as required by the Climate and Forecast (CF) Metadata Conventions" +requires-python = ">=3.10" +readme = "README.md" +dependencies = [ + 'python>=3.8', + 'cftime>=1.6', + 'Jinja2>=3', + 'numpy>=1.18', + 'antlr4-python3-runtime==4.11.1', +] + +[project.optional-dependencies] +build = [ + 'Cython>=3', + 'meson-python>=0.15.0', + 'pytest>=7', + 'spin>=0.8', ] -# Defined by PEP 517 -build-backend = "setuptools.build_meta" + +[build-system] +requires = ['meson-python', 'cython'] +build-backend = 'mesonpy' [tool.coverage.run] branch = true @@ -19,8 +33,6 @@ include = [ "cf_units/*" ] omit = [ - "setup.py", - "cf_units/_version.py", "cf_units/etc/*", "cf_units/tests/*", "cf_units/_udunits2_parser/parser/*", @@ -35,12 +47,6 @@ exclude_lines = [ "if __name__ == .__main__.:" ] -[tool.pytest.ini_options] -addopts = "-ra -v --doctest-modules" -doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS NUMBER" -minversion = "6.0" -testpaths = "cf_units" - [tool.setuptools_scm] write_to = "cf_units/_version.py" local_scheme = "dirty-tag" @@ -73,3 +79,22 @@ line_length = 79 profile = "black" skip_gitignore = "True" verbose = "False" + +[tool.spin] +package = 'cf_units' + +[tool.spin.commands] +Build = [ + 'spin.cmds.meson.build', + 'spin.cmds.meson.test', + 'spin.cmds.build.sdist', +] +Environments = [ + 'spin.cmds.meson.run', + 'spin.cmds.meson.python', +] + +[tool.pytest.ini_options] +addopts = "-ra -v --doctest-modules" +doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS NUMBER" +minversion = "6.0" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 2f50d221..00000000 --- a/setup.cfg +++ /dev/null @@ -1,98 +0,0 @@ -[metadata] -author = SciTools Developers -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Science/Research - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Topic :: Scientific/Engineering -description = Units of measure as required by the Climate and Forecast (CF) metadata conventions -download_url = https://github.com/SciTools/cf-units -keywords = - units - cf - netcdf - science - oceanography - meteorology - climate -license = BSD -license_files = LICENSE -long_description = file: README.md -long_description_content_type = text/markdown -name = cf-units -project_urls = - Code = https://github.com/SciTools/cf-units - Discussions = https://github.com/SciTools/cf-units/discussions - Issues = https://github.com/SciTools/cf-units/issues -url = https://github.com/SciTools/cf-units -version = attr: cf_units.__version__ - -[options] -include_package_data = True -install_requires = - antlr4-python3-runtime ==4.11.1 # To update this, see cf_units/_udunits2_parser/README.md - cftime >=1.2 - jinja2 - numpy - # udunits2 cannot be installed with pip, and it is expected to be - # installed separately. -packages = find_namespace: -python_requires = - >=3.9 -zip_safe = False - -[options.extras_require] -docs = - sphinx -test = - codecov - cython - jinja2 - pip - pytest - pytest-cov -all = - pre-commit - %(docs)s - %(test)s - -[flake8] -# References: -# https://flake8.readthedocs.io/en/latest/user/configuration.html -# https://flake8.readthedocs.io/en/latest/user/error-codes.html -# https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes - -select = C,E,F,W,B,B950 -ignore = - # E203: whitespace before ':' - E203, - # E226: missing whitespace around arithmetic operator - E226, - # E231: missing whitespace after ',', ';', or ':' - E231, - # E402: module level imports on one line - E402, - # E501: line too long - E501, - # E731: do not assign a lambda expression, use a def - E731, - # W503: line break before binary operator - W503, - # W504: line break after binary operator - W504 -exclude = - .eggs, - .git, - build, - cf_units/_version.py, - doc, - etc, - cf_units/_udunits2_parser/parser/* - -[aliases] -test = pytest diff --git a/setup.py b/setup.py deleted file mode 100644 index 34fb29e4..00000000 --- a/setup.py +++ /dev/null @@ -1,156 +0,0 @@ -import sys -from distutils.sysconfig import get_config_var -from os import environ -from pathlib import Path -from shutil import copy - -from setuptools import Command, Extension, setup - -# Default to using cython, but use the .c files if it doesn't exist -try: - from Cython.Build import cythonize -except ImportError: - cythonize = False - -COMPILER_DIRECTIVES = {} -DEFINE_MACROS = None -FLAG_COVERAGE = "--cython-coverage" # custom flag enabling Cython line tracing -BASEDIR = Path(__file__).resolve().parent -PACKAGE = "cf_units" -CFUNITS_DIR = BASEDIR / PACKAGE - - -class CleanCython(Command): - description = "Purge artifacts built by Cython" - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - for path in CFUNITS_DIR.rglob("*"): - if path.suffix in (".pyc", ".pyo", ".c", ".so"): - msg = f"clean: removing file {path}" - print(msg) - path.unlink() - - -def get_include_dirs(): - include_dirs = [] - include_dir = environ.get("UDUNITS2_INCDIR") - if include_dir is None: - include_dir = get_config_var("INCLUDEDIR") - if include_dir is not None: - include_dirs.append(include_dir) - return include_dirs - - -def get_library_dirs(): - library_dirs = [] - library_dir = environ.get("UDUNITS2_LIBDIR") - if library_dir is None: - library_dir = get_config_var("LIBDIR") - if library_dir is not None: - library_dirs.append(library_dir) - return library_dirs - - -def get_package_data(): - package_data = {} - # Determine whether we're building a wheel. - if "bdist_wheel" in sys.argv: - # The protocol is that the UDUNITS2_XML_PATH environment variable - # identifies the root UDUNITS2 XML file and parent directory containing - # all the XML resource files that require to be bundled within this - # wheel. Note that, this should match the UDUNITS2 distribution for the - # UDUNITS2 library cf-units is linking against. - xml_env = "UDUNITS2_XML_PATH" - xml_database = environ.get(xml_env) - if xml_database is None: - emsg = f"Require to set {xml_env} for a cf-units wheel build." - raise ValueError(emsg) - xml_database = Path(xml_database) - if not xml_database.is_file(): - emsg = ( - f"Can't open {xml_env} file {xml_database} " - "during cf-units wheel build." - ) - raise ValueError(emsg) - # We have a valid XML file, so copy the distro bundle into the - # cf_units/etc/share directory. - xml_dir = xml_database.expanduser().resolve().parent - share_base = Path("etc") / "share" - share_dir = CFUNITS_DIR / share_base - if not share_dir.is_dir(): - share_dir.mkdir(parents=True) - else: - # Purge any existing XML share files. - [fname.unlink() for fname in share_dir.glob("*.xml")] - # Bundle the UDUNITS2 XML file/s for the wheel. - [copy(fname, share_dir) for fname in xml_dir.glob("*.xml")] - # Register our additional wheel content. - package_data = {PACKAGE: [str(share_base / "*.xml")]} - return package_data - - -def numpy_build_ext(pars): - from setuptools.command.build_ext import build_ext as _build_ext - - class build_ext(_build_ext): - def finalize_options(self): - # See https://github.com/SciTools/cf-units/issues/151 - def _set_builtin(name, value): - if isinstance(__builtins__, dict): - __builtins__[name] = value - else: - setattr(__builtins__, name, value) - - _build_ext.finalize_options(self) - _set_builtin("__NUMPY_SETUP__", False) - import numpy - - self.include_dirs.append(numpy.get_include()) - - return build_ext(pars) - - -if FLAG_COVERAGE in sys.argv or environ.get("CYTHON_COVERAGE", None): - COMPILER_DIRECTIVES = {"linetrace": True} - DEFINE_MACROS = [("CYTHON_TRACE", "1"), ("CYTHON_TRACE_NOGIL", "1")] - if FLAG_COVERAGE in sys.argv: - sys.argv.remove(FLAG_COVERAGE) - print('enable: "linetrace" Cython compiler directive') - -library_dirs = get_library_dirs() - -udunits_ext = Extension( - f"{PACKAGE}._udunits2", - [str(Path(f"{PACKAGE}") / f"_udunits2.{'pyx' if cythonize else 'c'}")], - include_dirs=get_include_dirs(), - library_dirs=library_dirs, - libraries=["udunits2"], - define_macros=DEFINE_MACROS, - runtime_library_dirs=None - if sys.platform.startswith("win") - else library_dirs, -) - -if cythonize: - [udunits_ext] = cythonize( - udunits_ext, - compiler_directives=COMPILER_DIRECTIVES, - language_level="3str", - ) - -cmdclass = {"clean_cython": CleanCython, "build_ext": numpy_build_ext} - -kwargs = dict( - cmdclass=cmdclass, - ext_modules=[udunits_ext], - package_data=get_package_data(), -) - -setup(**kwargs)