1#!/usr/bin/python3
2import os
3import re
4import sys
5import platform
6import subprocess
7
8from setuptools import setup, setuptools, Extension
9from setuptools.command.build_ext import build_ext
10from distutils.version import LooseVersion
11
12
13class CMakeExtension(Extension):
14    def __init__(self, name, sourcedir=""):
15        Extension.__init__(self, name, sources=["./"])
16        self.sourcedir = os.path.abspath(sourcedir)
17
18
19class CMakeBuild(build_ext):
20    def run(self):
21        try:
22            out = subprocess.check_output(["cmake", "--version"])
23        except OSError:
24            raise RuntimeError(
25                "CMake must be installed to build"
26                + " the following extensions: "
27                + ", ".join(e.name for e in self.extensions)
28            )
29
30        if platform.system() == "Windows":
31            cmake_version = LooseVersion(
32                re.search(r"version\s*([\d.]+)", out.decode()).group(1)
33            )
34            if cmake_version < "3.1.0":
35                raise RuntimeError("CMake >= 3.1.0 is required on Windows")
36
37        for ext in self.extensions:
38            self.build_extension(ext)
39
40    def build_extension(self, ext):
41        extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
42        cmake_args = [
43            "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + str(extdir),
44            "-DPYTHON_EXECUTABLE=" + sys.executable,
45        ]
46
47        cfg = "Debug" if self.debug else "Release"
48        build_args = ["--config", cfg]
49
50        if platform.system() == "Windows":
51            cmake_args += [
52                "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir)
53            ]
54            if sys.maxsize > 2 ** 32:
55                cmake_args += ["-A", "x64"]
56            build_args += ["--", "/m"]
57        else:
58            cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg]
59            build_args += ["--", "-j", "6"]
60
61        env = os.environ.copy()
62        env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format(
63            env.get("CXXFLAGS", ""), self.distribution.get_version()
64        )
65        if not os.path.exists(self.build_temp):
66            os.makedirs(self.build_temp)
67        subprocess.check_call(
68            ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env
69        )
70        subprocess.check_call(
71            ["cmake", "--build", "."] + build_args, cwd=self.build_temp
72        )
73
74
75class get_pybind_include(object):
76    """Helper class to determine the pybind11 include path
77
78    The purpose of this class is to postpone importing pybind11
79    until it is actually installed, so that the ``get_include()``
80    method can be invoked."""
81
82    def __init__(self, user=False):
83        self.user = user
84
85    def __str__(self):
86        import pybind11
87
88        return pybind11.get_include(self.user)
89
90
91ext_modules = [
92    Extension(
93        "chiapos",
94        [
95            "lib/FiniteStateEntropy/lib/entropy_common.c",
96            "lib/FiniteStateEntropy/lib/fse_compress.c",
97            "lib/FiniteStateEntropy/lib/fse_decompress.c",
98            "lib/FiniteStateEntropy/lib/hist.c",
99            "python-bindings/chiapos.cpp",
100            "uint128_t/uint128_t.cpp",
101            "src/b3/blake3.c",
102            "src/b3/blake3_portable.c",
103            "src/b3/blake3_dispatch.c",
104            "src/b3/blake3_avx2.c",
105            "src/b3/blake3_avx512.c",
106            "src/b3/blake3_sse41.c",
107            "src/chacha8.c",
108        ],
109        include_dirs=[
110            "src",
111            "uint128_t",
112            ".",
113        ],
114    ),
115]
116
117
118# As of Python 3.6, CCompiler has a `has_flag` method.
119# cf http://bugs.python.org/issue26689
120def has_flag(compiler, flagname):
121    """Return a boolean indicating whether a flag name is supported on
122    the specified compiler.
123    """
124    import tempfile
125
126    with tempfile.NamedTemporaryFile("w", suffix=".cpp") as f:
127        f.write("int main (int argc, char **argv) { return 0; }")
128        try:
129            compiler.compile([f.name], extra_postargs=[flagname])
130        except setuptools.distutils.errors.CompileError:
131            return False
132    return True
133
134
135def cpp_flag(compiler):
136    """Return the -std=c++[11/14/17] compiler flag.
137
138    The newer version is prefered over c++11 (when it is available).
139    """
140    flags = ["-std=c++17", "-std=c++14", "-std=c++11"]
141
142    for flag in flags:
143        if has_flag(compiler, flag):
144            return flag
145
146    raise RuntimeError("Unsupported compiler -- at least C++11 support " "is needed!")
147
148
149class BuildExt(build_ext):
150    """A custom build extension for adding compiler-specific options."""
151
152    c_opts = {
153        "msvc": ["/EHsc", "/std:c++17", "/O2"],
154        "unix": [""],
155    }
156    l_opts = {
157        "msvc": [],
158        "unix": [""],
159    }
160
161    if sys.platform == "darwin":
162        darwin_opts = ["-stdlib=libc++", "-mmacosx-version-min=10.14"]
163        c_opts["unix"] += darwin_opts
164        l_opts["unix"] += darwin_opts  # type: ignore
165
166    def build_extensions(self):
167        ct = self.compiler.compiler_type
168        opts = self.c_opts.get(ct, [])
169        link_opts = self.l_opts.get(ct, [])
170        if ct == "unix":
171            opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version())
172            opts.append(cpp_flag(self.compiler))
173            if has_flag(self.compiler, "-fvisibility=hidden"):
174                opts.append("-fvisibility=hidden")
175        elif ct == "msvc":
176            opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version())
177        for ext in self.extensions:
178            ext.extra_compile_args = opts
179            ext.extra_link_args = link_opts
180        build_ext.build_extensions(self)
181
182
183if platform.system() == "Windows":
184    setup(
185        name="chiapos",
186        author="Mariano Sorgente",
187        author_email="mariano@chia.net",
188        description="Chia proof of space plotting, proving, and verifying (wraps C++)",
189        license="Apache License",
190        python_requires=">=3.7",
191        long_description=open("README.md").read(),
192        long_description_content_type="text/markdown",
193        url="https://github.com/Chia-Network/chiapos",
194        setup_requires=["pybind11"],
195        ext_modules=ext_modules,
196        cmdclass={"build_ext": BuildExt},
197        zip_safe=False,
198    )
199else:
200    setup(
201        name="chiapos",
202        version="1.0.3",
203        author="Mariano Sorgente",
204        author_email="mariano@chia.net",
205        description="Chia proof of space plotting, proving, and verifying (wraps C++)",
206        license="Apache License",
207        python_requires=">=3.7",
208        long_description=open("README.md").read(),
209        long_description_content_type="text/markdown",
210        url="https://github.com/Chia-Network/chiapos",
211        cmdclass=dict(build_ext=CMakeBuild),
212        zip_safe=False,
213    )
214