blob: d8e9a502f0c49a751426bfd97cdda27f671f4eaf [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import os
import platform
import shutil
import sys
import numpy as np
from pathlib import Path
from Cython.Build import cythonize
from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
ROOT = Path(__file__).parent.resolve()
PKG = ROOT / "tsfile"
version = "2.3.2.dev"
def _find_cpp_build():
"""Locate the C++ build output directory (one containing include/).
The Maven-driven build emits ``cpp/target/build``; a direct CMake build
emits ``cpp/build/<generator>``. Both layouts are supported, and an
explicit override may be given via the ``TSFILE_CPP_BUILD`` env var.
"""
candidates = []
override = os.environ.get("TSFILE_CPP_BUILD")
if override:
candidates.append(Path(override))
candidates += [
ROOT / ".." / "cpp" / "target" / "build",
ROOT / ".." / "cpp" / "build" / "msvc",
ROOT / ".." / "cpp" / "build" / "Release",
ROOT / ".." / "cpp" / "build" / "RelWithDebInfo",
ROOT / ".." / "cpp" / "build" / "Debug",
]
for cand in candidates:
cand = cand.resolve()
if (cand / "include").is_dir():
return cand
raise FileNotFoundError(
"Could not locate the C++ build output (a directory containing "
"include/). Build the C++ module first, or point TSFILE_CPP_BUILD "
"at the build directory."
)
def _find_lib(root, patterns):
"""Return the shortest-named file under ``root`` matching any pattern."""
for pattern in patterns:
hits = sorted(root.rglob(pattern), key=lambda p: len(p.name))
if hits:
return hits[0]
return None
CPP_OUT = _find_cpp_build()
CPP_LIB = CPP_OUT / "lib"
CPP_INC = CPP_OUT / "include"
if not CPP_INC.exists():
raise FileNotFoundError(f"missing C++ headers: {CPP_INC}")
if (PKG / "include").exists():
shutil.rmtree(PKG / "include")
shutil.copytree(CPP_INC, PKG / "include")
# Windows toolchain: "msvc" links against an MSVC import library (tsfile.lib),
# "mingw" links against a MinGW import library (libtsfile.dll.a).
win_toolchain = None
if sys.platform.startswith("linux"):
candidates = sorted(
CPP_LIB.rglob("libtsfile.so*"), key=lambda p: len(p.name), reverse=True
)
if not candidates:
raise FileNotFoundError("missing libtsfile.so* in build output")
src = candidates[0]
dst = PKG / src.name
shutil.copy2(src, dst)
link_name = PKG / "libtsfile.so"
shutil.copy2(src, link_name)
elif sys.platform == "darwin":
candidates = sorted(CPP_LIB.rglob("libtsfile.*.dylib")) or list(
CPP_LIB.rglob("libtsfile.dylib")
)
if not candidates:
raise FileNotFoundError("missing libtsfile*.dylib in build output")
src = candidates[0]
dst = PKG / src.name
shutil.copy2(src, dst)
link_name = PKG / "libtsfile.dylib"
shutil.copy2(src, link_name)
elif sys.platform == "win32":
# The shared library is named tsfile.dll (MSVC) or libtsfile.dll (MinGW).
dll_src = _find_lib(CPP_LIB, ["tsfile*.dll", "libtsfile*.dll"])
if dll_src is None:
raise FileNotFoundError(f"missing tsfile DLL in build output: {CPP_LIB}")
# Pick the import library and infer the toolchain from its kind.
mingw_imp = _find_lib(CPP_LIB, ["libtsfile*.dll.a"])
msvc_imp = _find_lib(CPP_LIB, ["tsfile*.lib", "libtsfile*.lib"])
if mingw_imp is not None and msvc_imp is None:
win_toolchain = "mingw"
imp_src = mingw_imp
elif msvc_imp is not None:
win_toolchain = "msvc"
imp_src = msvc_imp
else:
raise FileNotFoundError(
f"missing tsfile import library (*.lib or *.dll.a) in {CPP_LIB}"
)
# For MSVC the DLL is already named tsfile.dll. For MinGW, keep the original
# name (libtsfile.dll): the import library (libtsfile.dll.a) has that name
# baked in, so Cython .pyd files record "libtsfile.dll" in their PE import
# table and Windows must find it by that exact name at runtime.
dll_dst = PKG / dll_src.name
shutil.copy2(dll_src, dll_dst)
# Copy import library with a name matching the DLL.
if win_toolchain == "mingw":
imp_dst = PKG / "tsfile.dll.a"
else:
imp_dst = PKG / "tsfile.lib"
shutil.copy2(imp_src, imp_dst)
print(f"setup.py: Windows toolchain = {win_toolchain}")
print(
f"setup.py: copied {dll_src.name} -> {dll_dst.name} and {imp_src.name} -> {imp_dst.name}"
)
if win_toolchain == "mingw":
# Copy MinGW runtime DLLs next to tsfile.dll so Python can find them.
# Python 3.8+ does not search PATH for DLLs; they must sit in the
# same directory as the .pyd extensions (os.add_dll_directory).
# Prefer runtime DLLs captured from the same MSYS2 environment that
# built libtsfile.dll. Mixing MinGW runtime DLLs from a different
# install can load successfully but fail later with WinError 127 when
# libtsfile.dll imports a newer runtime symbol.
_mingw_search_dirs = [CPP_LIB]
for _dir in os.environ.get("PATH", "").split(os.pathsep):
if _dir:
_mingw_search_dirs.append(Path(_dir))
_msys_prefix = os.environ.get("MSYSTEM_PREFIX")
if _msys_prefix:
_mingw_search_dirs.append(Path(_msys_prefix) / "bin")
for _extra in (
Path("C:/msys64/mingw64/bin"),
Path("C:/ProgramData/mingw64/mingw64/bin"),
):
if _extra.is_dir():
_mingw_search_dirs.append(_extra)
for _mingw_dll in (
"libstdc++-6.dll",
"libgcc_s_seh-1.dll",
"libwinpthread-1.dll",
):
for _dir in _mingw_search_dirs:
_src = _dir / _mingw_dll
if _src.is_file():
shutil.copy2(_src, PKG / _mingw_dll)
print(f"setup.py: copied {_mingw_dll} from {_src}")
break
else:
raise FileNotFoundError(
f"setup.py: MinGW runtime DLL {_mingw_dll} not found; "
"ensure mingw-w64-x86_64-gcc is on PATH or MSYSTEM_PREFIX is set"
)
else:
raise RuntimeError(f"Unsupported platform: {sys.platform}")
class BuildExt(build_ext):
def run(self):
super().run()
def finalize_options(self):
# MinGW must be requested explicitly; MSVC is the default Windows
# compiler distutils selects, which matches an MSVC-built libtsfile.
if sys.platform == "win32" and win_toolchain == "mingw":
self.compiler = "mingw32"
super().finalize_options()
extra_compile_args = []
extra_link_args = []
runtime_library_dirs = []
libraries = []
library_dirs = [str(PKG)]
include_dirs = [str(PKG), np.get_include(), str(PKG / "include")]
if sys.platform.startswith("linux"):
libraries = ["tsfile"]
extra_compile_args += ["-O3", "-std=c++11", "-fvisibility=hidden", "-fPIC"]
runtime_library_dirs = ["$ORIGIN"]
extra_link_args += ["-Wl,-rpath,$ORIGIN"]
elif sys.platform == "darwin":
libraries = ["tsfile"]
extra_compile_args += ["-O3", "-std=c++11", "-fvisibility=hidden", "-fPIC"]
extra_link_args += ["-Wl,-rpath,@loader_path", "-stdlib=libc++"]
elif sys.platform == "win32":
if win_toolchain == "mingw":
# Resolve EH/runtime symbols from MinGW runtimes before libtsfile.dll.a.
# Otherwise _Unwind_Resume is recorded against libtsfile.dll and loading
# the .pyd fails with WinError 127 on Windows.
libraries = ["gcc_s", "stdc++", "tsfile"]
extra_compile_args += [
"-O2",
"-std=c++11",
"-DSIZEOF_VOID_P=8",
"-D__USE_MINGW_ANSI_STDIO=1",
"-DMS_WIN64",
"-D_WIN64",
]
else: # msvc
libraries = ["tsfile"]
# cl.exe rejects the GCC-style flags above. Mirror the options the
# C++ module itself is built with (see cpp/CMakeLists.txt). C++17 is
# required because Cython 3 emits inline variables (error C7525); the
# C++11 headers compile cleanly under the newer standard.
extra_compile_args += [
"/O2",
"/std:c++17",
"/EHsc",
"/bigobj",
"/utf-8",
"/Zc:__cplusplus",
"/DNOMINMAX",
"/D_CRT_SECURE_NO_WARNINGS",
"/D_CRT_NONSTDC_NO_WARNINGS",
"/D_SCL_SECURE_NO_WARNINGS",
"/D_WINSOCK_DEPRECATED_NO_WARNINGS",
]
else:
raise RuntimeError(f"Unsupported platform: {sys.platform}")
common = dict(
language="c++",
include_dirs=include_dirs,
library_dirs=library_dirs,
libraries=libraries,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args,
runtime_library_dirs=runtime_library_dirs,
)
exts = [
Extension("tsfile.tsfile_py_cpp", ["tsfile/tsfile_py_cpp.pyx"], **common),
Extension("tsfile.tsfile_reader", ["tsfile/tsfile_reader.pyx"], **common),
Extension("tsfile.tsfile_writer", ["tsfile/tsfile_writer.pyx"], **common),
]
setup(
name="tsfile",
version=version,
packages=["tsfile", "tsfile.dataset"],
package_dir={"": "."},
include_package_data=True,
ext_modules=cythonize(exts, compiler_directives={"language_level": 3}),
cmdclass={"build_ext": BuildExt},
)