CIRCT 23.0.0git
Loading...
Searching...
No Matches
setup.py
Go to the documentation of this file.
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5# Build/install the ESI runtime python package.
6#
7# To install:
8# pip install .
9# To build a wheel:
10# pip wheel .
11#
12# It is recommended to build with Ninja and ccache. To do so, set environment
13# variables by prefixing to above invocations:
14# CC=clang CXX=clang++
15#
16# On CIs, it is often advantageous to re-use/control the CMake build directory.
17# This can be set with the PYCDE_CMAKE_BUILD_DIR env var.
18
19import os
20import platform
21import shutil
22import subprocess
23import sys
24import sysconfig
25
26from setuptools.command.build import build as _build
27from setuptools import find_namespace_packages, setup, Extension
28from setuptools.command.build_ext import build_ext
29from setuptools.command.build_py import build_py
30
31_thisdir = os.path.abspath(os.path.dirname(__file__))
32
33
34# Build phase discovery is unreliable. Just tell it what phases to run.
36
37 def run(self):
38 self.run_command("build_py")
39 self.run_command("build_ext")
40 self.run_command("build_scripts")
41
42
43class CMakeExtension(Extension):
44
45 def __init__(self, name, sourcedir=""):
46 Extension.__init__(self, name, sources=[])
47 self.sourcedir = os.path.abspath(sourcedir)
48
49
50class CMakeBuild(build_py):
51
52 def run(self):
53 # Set up build dirs.
54 target_dir = os.path.abspath(self.build_lib)
55 cmake_build_dir = os.getenv("CMAKE_BUILD_DIR")
56 if not cmake_build_dir:
57 cmake_build_dir = os.path.abspath(
58 os.path.join(target_dir, "..", "cmake_build"))
59 src_dir = _thisdir
60
61 # On Windows, we build both Release and Debug configurations
62 # to support applications in both modes.
63 configs_to_build = ["Release"]
64 if platform.system() == "Windows":
65 configs_to_build.append("Debug")
66
67 for cfg in configs_to_build:
68 # Use separate build directories for each configuration
69 current_build_dir = cmake_build_dir
70 if len(configs_to_build) > 1:
71 current_build_dir = os.path.join(cmake_build_dir, cfg)
72
73 os.makedirs(current_build_dir, exist_ok=True)
74 cmake_cache_file = os.path.join(current_build_dir, "CMakeCache.txt")
75 if os.path.exists(cmake_cache_file):
76 os.remove(cmake_cache_file)
77
78 # Configure the build.
79 cmake_args = [
80 "-GNinja", # This build only works with Ninja on Windows.
81 "-DCMAKE_BUILD_TYPE={}".format(cfg), # not used on MSVC, but no harm
82 "-DPython3_EXECUTABLE={}".format(sys.executable.replace("\\", "/")),
83 "-DPython_EXECUTABLE={}".format(sys.executable.replace("\\", "/")),
84 "-DWHEEL_BUILD=ON",
85 ]
86
87 # Get the nanobind cmake directory from the isolated build environment.
88 # This is necessary because CMake's execute_process may not properly find
89 # nanobind installed in the isolated build environment.
90 try:
91 import nanobind
92
93 nanobind_dir = nanobind.cmake_dir()
94 cmake_args.append("-Dnanobind_DIR={}".format(
95 nanobind_dir.replace("\\", "/")))
96 except ImportError:
97 print("Skipping nanobind directory detection, nanobind not found.")
98
99 cxx = os.getenv("CXX")
100 if cxx is not None:
101 cmake_args.append("-DCMAKE_CXX_COMPILER={}".format(cxx))
102 cxxflags = os.getenv("CXXFLAGS")
103 if cxxflags is not None:
104 cmake_args.append("-DCMAKE_CXX_FLAGS={}".format(cxxflags))
105
106 cc = os.getenv("CC")
107 if cc is not None:
108 cmake_args.append("-DCMAKE_C_COMPILER={}".format(cc))
109 cflags = os.getenv("CFLAGS")
110 if cflags is not None:
111 cmake_args.append("-DCMAKE_C_FLAGS={}".format(cflags))
112
113 if "VCPKG_INSTALLATION_ROOT" in os.environ:
114 cmake_args.append(
115 f"-DCMAKE_TOOLCHAIN_FILE={os.environ['VCPKG_INSTALLATION_ROOT']}/scripts/buildsystems/vcpkg.cmake"
116 )
117
118 if "CIRCT_EXTRA_CMAKE_ARGS" in os.environ:
119 cmake_args += os.environ["CIRCT_EXTRA_CMAKE_ARGS"].split(" ")
120
121 # HACK: CMake fails to auto-detect static linked Python installations, which
122 # happens to be what exists on manylinux. We detect this and give it a dummy
123 # library file to reference (which is checks exists but never gets
124 # used).
125 if platform.system() == "Linux":
126 python_libdir = sysconfig.get_config_var('LIBDIR')
127 python_library = sysconfig.get_config_var('LIBRARY')
128 if python_libdir and not os.path.isabs(python_library):
129 python_library = os.path.join(python_libdir, python_library)
130 if python_library and not os.path.exists(python_library):
131 print("Detected static linked python. Faking a library for cmake.")
132 fake_libdir = os.path.join(current_build_dir, "fake_python", "lib")
133 os.makedirs(fake_libdir, exist_ok=True)
134 fake_library = os.path.join(fake_libdir,
135 sysconfig.get_config_var('LIBRARY'))
136 subprocess.check_call(["ar", "q", fake_library])
137 cmake_args.append("-DPython3_LIBRARY:PATH={}".format(fake_library))
138
139 # Finally run the cmake configure.
140 print(f"Configuring {cfg} build...")
141 subprocess.check_call(["cmake", src_dir] + cmake_args,
142 cwd=current_build_dir)
143 print(" ".join(["cmake", src_dir] + cmake_args))
144
145 # Run the build.
146 # For Debug builds on Windows, only build the C++ runtime and tools (not
147 # the Python extension). The debug Python extension requires a debug
148 # Python interpreter and nanobind's auto-linking conflicts with the
149 # debug build. The purpose of the Debug build is to include the debug
150 # DLLs, EXEs, and PDBs in the wheel, not the Python extension.
151
152 print(f"Building {cfg} configuration...")
153 subprocess.check_call(
154 [
155 "cmake",
156 "--build",
157 ".",
158 "--parallel",
159 "--target",
160 "ESIRuntime",
161 ],
162 cwd=current_build_dir,
163 )
164
165 # Install the runtime directly into the target directory.
166 # For the first config (Release), clear the target directory.
167 # For subsequent configs (Debug on Windows), keep existing files.
168 if cfg == "Release" and os.path.exists(target_dir):
169 shutil.rmtree(target_dir)
170
171 print(f"Installing {cfg} configuration...")
172 if cfg == "Debug":
173 # For Debug builds, we only built the C++ runtime (not the Python
174 # extension), so we can't use cmake --install with the ESIRuntime
175 # component (it would fail trying to install the unbuilt Python
176 # extension). Instead, manually copy the debug DLLs, PDBs, import
177 # libraries, and executables for the specific ESIRuntime binaries.
178 import glob
179 install_dir = os.path.join(target_dir, "esiaccel")
180 os.makedirs(install_dir, exist_ok=True)
181 debug_patterns = [
182 "ESICppRuntime*.dll",
183 "ESICppRuntime*.lib",
184 "CosimRpc*.dll",
185 "CosimRpc*.lib",
186 "CosimBackend*.dll",
187 "CosimBackend*.lib",
188 "esiquery*.exe",
189 ]
190 for pattern in debug_patterns:
191 for f in glob.glob(os.path.join(current_build_dir, pattern)):
192 print(f" Installing {os.path.basename(f)}")
193 shutil.copy2(f, install_dir)
194 else:
195 subprocess.check_call(
196 [
197 "cmake",
198 "--install",
199 ".",
200 "--prefix",
201 os.path.join(target_dir, "esiaccel"),
202 "--component",
203 "ESIRuntime",
204 ],
205 cwd=current_build_dir,
206 )
207
208
209class NoopBuildExtension(build_ext):
210
211 def build_extension(self, ext):
212 if not self.editable_mode:
213 return
214 # For editable installs, trigger the CMake build and copy native
215 # artifacts into the source tree so the extension is importable.
216 self.run_command("build_py")
217 build_py_cmd = self.get_finalized_command("build_py")
218 esiaccel_build = os.path.join(os.path.abspath(build_py_cmd.build_lib),
219 "esiaccel")
220 esiaccel_src = os.path.join(_thisdir, "python", "esiaccel")
221
222 import glob
223 # Copy the native extension module and type stubs.
224 for f in glob.glob(os.path.join(esiaccel_build, "esiCppAccel*")):
225 if os.path.isfile(f):
226 shutil.copy2(f, esiaccel_src)
227
228 # Copy shared libraries and tools needed at runtime.
229 for dirname in ("lib", "bin"):
230 src_dir = os.path.join(esiaccel_build, dirname)
231 dst_dir = os.path.join(esiaccel_src, dirname)
232 if os.path.isdir(src_dir):
233 if os.path.exists(dst_dir):
234 shutil.rmtree(dst_dir)
235 shutil.copytree(src_dir, dst_dir)
236
238 pass
239
240
241setup(
242 name="esiaccel",
243 include_package_data=True,
244 ext_modules=[
245 CMakeExtension("esiaccel.esiCppAccel"),
246 ],
247 cmdclass={
248 "build": CustomBuild,
249 "build_ext": NoopBuildExtension,
250 "build_py": CMakeBuild,
251 },
252 zip_safe=False,
253 package_dir={'': 'python'},
254 packages=find_namespace_packages(where="python",
255 include=[
256 "esiaccel",
257 "esiaccel.*",
258 ]),
259)
static void print(TypedAttr val, llvm::raw_ostream &os)
__init__(self, name, sourcedir="")
Definition setup.py:45
copy_extensions_to_source(self)
Definition setup.py:237
build_extension(self, ext)
Definition setup.py:211
Definition setup.py:1