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 # We build a single Release configuration. On Windows, Debug builds for
62 # consumers are supported by shipping the C++ sources alongside the wheel
63 # and having the distributed esiaccelConfig.cmake compile them on demand
64 # (see the copy step below and cpp/cmake/esiaccelConfig.cmake.in).
65 configs_to_build = ["Release"]
66
67 for cfg in configs_to_build:
68 current_build_dir = cmake_build_dir
69
70 os.makedirs(current_build_dir, exist_ok=True)
71 cmake_cache_file = os.path.join(current_build_dir, "CMakeCache.txt")
72 if os.path.exists(cmake_cache_file):
73 os.remove(cmake_cache_file)
74
75 # Configure the build.
76 cmake_args = [
77 "-GNinja", # This build only works with Ninja on Windows.
78 "-DCMAKE_BUILD_TYPE={}".format(cfg), # not used on MSVC, but no harm
79 "-DPython3_EXECUTABLE={}".format(sys.executable.replace("\\", "/")),
80 "-DPython_EXECUTABLE={}".format(sys.executable.replace("\\", "/")),
81 "-DWHEEL_BUILD=ON",
82 ]
83
84 # Get the nanobind cmake directory from the isolated build environment.
85 # This is necessary because CMake's execute_process may not properly find
86 # nanobind installed in the isolated build environment.
87 try:
88 import nanobind
89
90 nanobind_dir = nanobind.cmake_dir()
91 cmake_args.append("-Dnanobind_DIR={}".format(
92 nanobind_dir.replace("\\", "/")))
93 except ImportError:
94 print("Skipping nanobind directory detection, nanobind not found.")
95
96 cxx = os.getenv("CXX")
97 if cxx is not None:
98 cmake_args.append("-DCMAKE_CXX_COMPILER={}".format(cxx))
99 cxxflags = os.getenv("CXXFLAGS")
100 if cxxflags is not None:
101 cmake_args.append("-DCMAKE_CXX_FLAGS={}".format(cxxflags))
102
103 cc = os.getenv("CC")
104 if cc is not None:
105 cmake_args.append("-DCMAKE_C_COMPILER={}".format(cc))
106 cflags = os.getenv("CFLAGS")
107 if cflags is not None:
108 cmake_args.append("-DCMAKE_C_FLAGS={}".format(cflags))
109
110 if "VCPKG_INSTALLATION_ROOT" in os.environ:
111 cmake_args.append(
112 f"-DCMAKE_TOOLCHAIN_FILE={os.environ['VCPKG_INSTALLATION_ROOT']}/scripts/buildsystems/vcpkg.cmake"
113 )
114
115 if "CIRCT_EXTRA_CMAKE_ARGS" in os.environ:
116 cmake_args += os.environ["CIRCT_EXTRA_CMAKE_ARGS"].split(" ")
117
118 # HACK: CMake fails to auto-detect static linked Python installations, which
119 # happens to be what exists on manylinux. We detect this and give it a dummy
120 # library file to reference (which is checks exists but never gets
121 # used).
122 if platform.system() == "Linux":
123 python_libdir = sysconfig.get_config_var('LIBDIR')
124 python_library = sysconfig.get_config_var('LIBRARY')
125 if python_libdir and not os.path.isabs(python_library):
126 python_library = os.path.join(python_libdir, python_library)
127 if python_library and not os.path.exists(python_library):
128 print("Detected static linked python. Faking a library for cmake.")
129 fake_libdir = os.path.join(current_build_dir, "fake_python", "lib")
130 os.makedirs(fake_libdir, exist_ok=True)
131 fake_library = os.path.join(fake_libdir,
132 sysconfig.get_config_var('LIBRARY'))
133 subprocess.check_call(["ar", "q", fake_library])
134 cmake_args.append("-DPython3_LIBRARY:PATH={}".format(fake_library))
135
136 # Finally run the cmake configure.
137 print(f"Configuring {cfg} build...")
138 subprocess.check_call(["cmake", src_dir] + cmake_args,
139 cwd=current_build_dir)
140 print(" ".join(["cmake", src_dir] + cmake_args))
141
142 # Run the build.
143 # For Debug builds on Windows, only build the C++ runtime and tools (not
144 # the Python extension). The debug Python extension requires a debug
145 # Python interpreter and nanobind's auto-linking conflicts with the
146 # debug build. The purpose of the Debug build is to include the debug
147 # DLLs, EXEs, and PDBs in the wheel, not the Python extension.
148
149 print(f"Building {cfg} configuration...")
150 subprocess.check_call(
151 [
152 "cmake",
153 "--build",
154 ".",
155 "--parallel",
156 "--target",
157 "ESIRuntime",
158 ],
159 cwd=current_build_dir,
160 )
161
162 # Install the runtime directly into the target directory.
163 if cfg == "Release" and os.path.exists(target_dir):
164 shutil.rmtree(target_dir)
165
166 print(f"Installing {cfg} configuration...")
167 subprocess.check_call(
168 [
169 "cmake",
170 "--install",
171 ".",
172 "--prefix",
173 os.path.join(target_dir, "esiaccel"),
174 "--component",
175 "ESIRuntime",
176 ],
177 cwd=current_build_dir,
178 )
179
180 # Ship the C++ sources alongside the wheel so that consumers on Windows
181 # can build a Debug variant on demand. The distributed
182 # esiaccelConfig.cmake adds this source tree as a subdirectory when the
183 # consumer is configuring a Debug build on Windows.
184 source_dst = os.path.join(target_dir, "esiaccel", "source")
185 if os.path.exists(source_dst):
186 shutil.rmtree(source_dst)
187 os.makedirs(source_dst, exist_ok=True)
188 print(f"Copying C++ sources into wheel at {source_dst}")
189 shutil.copy2(os.path.join(src_dir, "CMakeLists.txt"), source_dst)
190 cosim_proto_doc = os.path.join(src_dir, "cosim-protocol.md")
191 if os.path.exists(cosim_proto_doc):
192 shutil.copy2(cosim_proto_doc, source_dst)
193 # Copy the cpp/ tree but skip the cmake/ subdirectory: the
194 # esiaccelConfig.cmake.in template is only relevant when (re)building the
195 # full wheel, not when consumers compile the runtime from these sources.
196 shutil.copytree(os.path.join(src_dir, "cpp"),
197 os.path.join(source_dst, "cpp"),
198 ignore=shutil.ignore_patterns("cmake"))
199 shutil.copytree(os.path.join(src_dir, "cosim_dpi_server"),
200 os.path.join(source_dst, "cosim_dpi_server"))
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