From f373371204adcd76bde3b74fad1208fe668c3344 Mon Sep 17 00:00:00 2001
From: Martin Larralde <martin.larralde@embl.de>
Date: Sat, 12 Jun 2021 04:38:38 +0200
Subject: [PATCH] Add setuptools configuration files with Cython support

---
 setup.cfg |  84 ++++++++++++++++++++++++++
 setup.py  | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 256 insertions(+)
 create mode 100644 setup.cfg
 create mode 100644 setup.py

diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..b41a238
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,84 @@
+# https://gist.github.com/althonos/6914b896789d3f2078d1e6237642c35c
+
+# --- Setuptools metadata ---------------------------------------------------
+
+[metadata]
+name = pyFastANI
+version = attr: pyfastani.__version__
+author = Martin Larralde
+author_email = martin.larralde@embl.de
+url = https://github.com/althonos/pyFastANI
+description = Cython bindings and Python interface to FastANI.
+long_description = file: README.md
+long_description_content_type = text/markdown
+license = MIT
+platform = posix
+keywords = bioinformatics, genomics, average, nucleotide, identity
+classifier =
+    Development Status :: 3 - Alpha
+    Intended Audience :: Developers
+    Intended Audience :: Science/Research
+    License :: OSI Approved :: MIT License
+    Operating System :: OS Independent
+    Programming Language :: C++
+    Programming Language :: Cython
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: Implementation :: CPython
+    Programming Language :: Python :: Implementation :: PyPy
+    Topic :: Scientific/Engineering :: Bio-Informatics
+    Topic :: Scientific/Engineering :: Medical Science Apps.
+    Typing :: Typed
+project_urls =
+    Documentation = https://pyfastani.readthedocs.io/en/stable/
+    Bug Tracker = https://github.com/althonos/pyFastANI/issues
+    Changelog = https://github.com/althonos/pyFastANI/blob/master/CHANGELOG.md
+    Coverage = https://codecov.io/gh/althonos/pyFastANI/
+    Builds = https://github.com/althonos/pyFastANI/actions/
+    Zenodo = https://doi.org/10.5281/zenodo.4270012
+    PyPI = https://pypi.org/project/pyFastANI
+
+[options]
+zip_safe = false
+packages = pyfastani
+python_requires = >=3.6
+test_suite = tests
+include_package_data = true
+setup_requires =
+    setuptools >=46.4
+    cython ~=0.29.16
+
+[options.package_data]
+pyfastani = py.typed, *.pyi
+
+
+# --- Python tools configuration --------------------------------------------
+
+[coverage:run]
+plugins = Cython.Coverage
+
+[coverage:report]
+include = pyfastani/*.pyx
+show_missing = true
+exclude_lines =
+    pragma: no cover
+    if typing.TYPE_CHECKING:
+    @abc.abstractmethod
+    @abc.abstractproperty
+    raise NotImplementedError
+    return NotImplemented
+    raise UnexpectedError
+    raise AllocationError
+
+[mypy]
+disallow_any_decorated = true
+disallow_any_generics = true
+disallow_any_unimported = false
+disallow_subclassing_any = false
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+ignore_missing_imports = true
+warn_unused_ignores = true
+warn_return_any = true
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..c8eccbb
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+import collections
+import configparser
+import glob
+import itertools
+import os
+import platform
+import re
+import sys
+import subprocess
+from unittest import mock
+
+import setuptools
+from distutils import log
+from distutils.command.clean import clean as _clean
+from distutils.errors import CompileError
+from setuptools.command.build_ext import build_ext as _build_ext
+from setuptools.command.sdist import sdist as _sdist
+from setuptools.extension import Extension, Library
+
+try:
+    from Cython.Build import cythonize
+except ImportError as err:
+    cythonize = err
+
+# --- Constants --------------------------------------------------------------
+
+PLATFORM_MACHINE   = platform.machine()
+SYS_IMPLEMENTATION = sys.implementation.name
+
+
+# --- Utils ------------------------------------------------------------------
+
+def _split_multiline(value):
+    value = value.strip()
+    sep = max('\n,;', key=value.count)
+    return list(filter(None, map(lambda x: x.strip(), value.split(sep))))
+
+
+# --- `setup.py` commands ----------------------------------------------------
+
+class sdist(_sdist):
+    """A `sdist` that generates a `pyproject.toml` on the fly.
+    """
+
+    def run(self):
+        # build `pyproject.toml` from `setup.cfg`
+        c = configparser.ConfigParser()
+        c.add_section("build-system")
+        c.set("build-system", "requires", str(self.distribution.setup_requires))
+        c.set("build-system", 'build-backend', '"setuptools.build_meta"')
+        with open("pyproject.toml", "w") as pyproject:
+            c.write(pyproject)
+        # run the rest of the packaging
+        _sdist.run(self)
+
+
+class build_ext(_build_ext):
+    """A `build_ext` that disables optimizations if compiled in debug mode.
+    """
+
+    def finalize_options(self):
+        _build_ext.finalize_options(self)
+        self._clib_cmd = self.get_finalized_command("build_clib")
+        self._clib_cmd.force = self.force
+        self._clib_cmd.debug = self.debug
+
+    def run(self):
+        # check `cythonize` is available
+        if isinstance(cythonize, ImportError):
+            raise RuntimeError("Cython is required to run `build_ext` command") from cythonize
+
+        # use debug directives with Cython if building in debug mode
+        cython_args = {"include_path": ["include", "pyfastani"], "compiler_directives": {}}
+        cython_args["compile_time_env"] = {"SYS_IMPLEMENTATION": SYS_IMPLEMENTATION}
+        if self.force:
+            cython_args["force"] = True
+        if self.debug:
+            cython_args["annotate"] = True
+            cython_args["compiler_directives"]["warn.undeclared"] = True
+            cython_args["compiler_directives"]["warn.unreachable"] = True
+            cython_args["compiler_directives"]["warn.maybe_uninitialized"] = True
+            cython_args["compiler_directives"]["warn.unused"] = True
+            cython_args["compiler_directives"]["warn.unused_arg"] = True
+            cython_args["compiler_directives"]["warn.unused_result"] = True
+            cython_args["compiler_directives"]["warn.multiple_declarators"] = True
+            cython_args["compiler_directives"]["profile"] = True
+        else:
+            cython_args["compiler_directives"]["boundscheck"] = False
+            cython_args["compiler_directives"]["wraparound"] = False
+            cython_args["compiler_directives"]["cdivision"] = True
+
+        # cythonize and patch the extensions
+        self.extensions = cythonize(self.extensions, **cython_args)
+        for ext in self.extensions:
+            ext._needs_stub = False
+
+        # # update the compiler include and link dirs to use the
+        # # temporary build folder so that the platform-specific headers
+        # # and static libs can be found
+
+        # check the libraries have been built already
+        if not self.distribution.have_run["build_clib"]:
+            self._clib_cmd.run()
+
+        # build the extensions as normal
+        _build_ext.run(self)
+
+    def build_extension(self, ext):
+        # update compile flags if compiling in debug mode
+        if self.debug:
+            if self.compiler.compiler_type in {"unix", "cygwin", "mingw32"}:
+                ext.extra_compile_args.append("-Og")
+                ext.extra_compile_args.append("--coverage")
+                ext.extra_link_args.append("--coverage")
+            elif self.compiler.compiler_type == "msvc":
+                ext.extra_compile_args.append("/Od")
+            if sys.implementation.name == "cpython":
+                ext.define_macros.append(("CYTHON_TRACE_NOGIL", 1))
+        else:
+            ext.define_macros.append(("CYTHON_WITHOUT_ASSERTIONS", 1))
+
+        # build the rest of the extension as normal
+        _build_ext.build_extension(self, ext)
+
+
+class clean(_clean):
+
+    def run(self):
+
+        source_dir_abs = os.path.join(os.path.dirname(__file__), "pyfastani")
+        source_dir = os.path.relpath(source_dir_abs)
+
+        patterns = ["*.html"]
+        if self.all:
+            patterns.extend(["*.so", "*.c"])
+
+        for pattern in patterns:
+            for file in glob.glob(os.path.join(source_dir, pattern)):
+                log.info("removing {!r}".format(file))
+                os.remove(file)
+
+        _clean.run(self)
+
+
+# --- Cython extensions ------------------------------------------------------
+
+extensions = [
+    Extension(
+        "pyfastani._fastani",
+        [os.path.join("pyfastani", "_utils.cpp"), os.path.join("pyfastani", "_fastani.pyx")],
+        language="c++",
+        libraries=["gsl", "gslcblas", "stdc++", "z", "m"],
+        include_dirs=["include", "pyfastani", os.path.join("vendor", "FastANI", "src")],
+        extra_compile_args=["-std=c++11"],
+        extra_link_args=["-std=c++11"],
+    )
+]
+
+# --- Setup ------------------------------------------------------------------
+
+setuptools.setup(
+    ext_modules=extensions,
+    # libraries=libraries,
+    cmdclass=dict(
+        build_ext=build_ext,
+        clean=clean,
+        sdist=sdist,
+    ),
+)
-- 
GitLab