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
103 cc = os.getenv("CC")
104 if cc is not None:
105 cmake_args.append("-DCMAKE_C_COMPILER={}".format(cc))
106
107 if "VCPKG_INSTALLATION_ROOT" in os.environ:
108 cmake_args.append(
109 f"-DCMAKE_TOOLCHAIN_FILE={os.environ['VCPKG_INSTALLATION_ROOT']}/scripts/buildsystems/vcpkg.cmake"
110 )
111
112 if "CIRCT_EXTRA_CMAKE_ARGS" in os.environ:
113 cmake_args += os.environ["CIRCT_EXTRA_CMAKE_ARGS"].split(" ")
114
115 # HACK: CMake fails to auto-detect static linked Python installations, which
116 # happens to be what exists on manylinux. We detect this and give it a dummy
117 # library file to reference (which is checks exists but never gets
118 # used).
119 if platform.system() == "Linux":
120 python_libdir = sysconfig.get_config_var('LIBDIR')
121 python_library = sysconfig.get_config_var('LIBRARY')
122 if python_libdir and not os.path.isabs(python_library):
123 python_library = os.path.join(python_libdir, python_library)
124 if python_library and not os.path.exists(python_library):
125 print("Detected static linked python. Faking a library for cmake.")
126 fake_libdir = os.path.join(current_build_dir, "fake_python", "lib")
127 os.makedirs(fake_libdir, exist_ok=True)
128 fake_library = os.path.join(fake_libdir,
129 sysconfig.get_config_var('LIBRARY'))
130 subprocess.check_call(["ar", "q", fake_library])
131 cmake_args.append("-DPython3_LIBRARY:PATH={}".format(fake_library))
132
133 # Finally run the cmake configure.
134 print(f"Configuring {cfg} build...")
135 subprocess.check_call(["cmake", src_dir] + cmake_args,
136 cwd=current_build_dir)
137 print(" ".join(["cmake", src_dir] + cmake_args))
138
139 # Run the build.
140 # For Debug builds on Windows, only build the C++ runtime and tools (not
141 # the Python extension). The debug Python extension requires a debug
142 # Python interpreter and nanobind's auto-linking conflicts with the
143 # debug build. The purpose of the Debug build is to include the debug
144 # DLLs, EXEs, and PDBs in the wheel, not the Python extension.
145
146 print(f"Building {cfg} configuration...")
147 subprocess.check_call(
148 [
149 "cmake",
150 "--build",
151 ".",
152 "--parallel",
153 "--target",
154 "ESIRuntime",
155 ],
156 cwd=current_build_dir,
157 )
158
159 # Install the runtime directly into the target directory.
160 # For the first config (Release), clear the target directory.
161 # For subsequent configs (Debug on Windows), keep existing files.
162 if cfg == "Release" and os.path.exists(target_dir):
163 shutil.rmtree(target_dir)
164
165 print(f"Installing {cfg} configuration...")
166 if cfg == "Debug":
167 # For Debug builds, we only built the C++ runtime (not the Python
168 # extension), so we can't use cmake --install with the ESIRuntime
169 # component (it would fail trying to install the unbuilt Python
170 # extension). Instead, manually copy the debug DLLs, PDBs, import
171 # libraries, and executables for the specific ESIRuntime binaries.
172 import glob
173 install_dir = os.path.join(target_dir, "esiaccel")
174 os.makedirs(install_dir, exist_ok=True)
175 debug_patterns = [
176 "ESICppRuntime*.dll",
177 "ESICppRuntime*.lib",
178 "CosimRpc*.dll",
179 "CosimRpc*.lib",
180 "CosimBackend*.dll",
181 "CosimBackend*.lib",
182 "esiquery*.exe",
183 ]
184 for pattern in debug_patterns:
185 for f in glob.glob(os.path.join(current_build_dir, pattern)):
186 print(f" Installing {os.path.basename(f)}")
187 shutil.copy2(f, install_dir)
188 else:
189 subprocess.check_call(
190 [
191 "cmake",
192 "--install",
193 ".",
194 "--prefix",
195 os.path.join(target_dir, "esiaccel"),
196 "--component",
197 "ESIRuntime",
198 ],
199 cwd=current_build_dir,
200 )
201
202
203class NoopBuildExtension(build_ext):
204
205 def build_extension(self, ext):
206 if not self.editable_mode:
207 return
208 # For editable installs, trigger the CMake build and copy native
209 # artifacts into the source tree so the extension is importable.
210 self.run_command("build_py")
211 build_py_cmd = self.get_finalized_command("build_py")
212 esiaccel_build = os.path.join(os.path.abspath(build_py_cmd.build_lib),
213 "esiaccel")
214 esiaccel_src = os.path.join(_thisdir, "python", "esiaccel")
215
216 import glob
217 # Copy the native extension module and type stubs.
218 for f in glob.glob(os.path.join(esiaccel_build, "esiCppAccel*")):
219 if os.path.isfile(f):
220 shutil.copy2(f, esiaccel_src)
221
222 # Copy shared libraries and tools needed at runtime.
223 for dirname in ("lib", "bin"):
224 src_dir = os.path.join(esiaccel_build, dirname)
225 dst_dir = os.path.join(esiaccel_src, dirname)
226 if os.path.isdir(src_dir):
227 if os.path.exists(dst_dir):
228 shutil.rmtree(dst_dir)
229 shutil.copytree(src_dir, dst_dir)
230
232 pass
233
234
235setup(
236 name="esiaccel",
237 include_package_data=True,
238 ext_modules=[
239 CMakeExtension("esiaccel.esiCppAccel"),
240 ],
241 cmdclass={
242 "build": CustomBuild,
243 "build_ext": NoopBuildExtension,
244 "build_py": CMakeBuild,
245 },
246 zip_safe=False,
247 package_dir={'': 'python'},
248 packages=find_namespace_packages(where="python",
249 include=[
250 "esiaccel",
251 "esiaccel.*",
252 ]),
253)
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:231
build_extension(self, ext)
Definition setup.py:205
Definition setup.py:1