Skip to content

Commit b4c7de6

Browse files
authored
[Python] Enable building Python bindings as editable wheels, document it (iree-org#19716)
In order to not need to constantly source PYTHONPATH, and to run the rink of potentially having it set wrong when dealing with multiple IREE builds, and to allow packages like the kernel bunchmarking suite to use a local build without needing to edit requirements.txt, add the ability to build these packages as editableble wheels. This method, newly added to the build documentation, tells CMake to use symbolic links when "installing" the Python packages from the build directroy into a different build directory. In combination with telling copytree to preserve symlinks, this creates Python packages that link back to the build or source directory when the `-e` flag is used on pip. This means that an automated virtual environment switcher, like `pyenv`, will pick up the correct Python bindings automatically and will direct `iree-compiler` and `iree-runtime` shims to the correct build.
1 parent b08d152 commit b4c7de6

File tree

4 files changed

+156
-76
lines changed

4 files changed

+156
-76
lines changed

compiler/setup.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#
2222
# Select CMake options are available from environment variables:
2323
# IREE_ENABLE_CPUINFO
24+
#
25+
# If building from a development tree and aiming to get an "editable" install,
26+
# use the environment option CMAKE_INSTALL_MODE=ABS_SYMLINK on your
27+
# `pip install -e .` invocation.
2428

2529
from gettext import install
2630
import json
@@ -337,7 +341,7 @@ def run(self):
337341
shutil.copytree(
338342
os.path.join(CMAKE_INSTALL_DIR_ABS, "python_packages", "iree_compiler"),
339343
target_dir,
340-
symlinks=False,
344+
symlinks=self.editable_mode,
341345
)
342346
print("Target populated.", file=sys.stderr)
343347

docs/website/docs/building-from-source/getting-started.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,43 @@ cmake --build ../iree-build/
396396

397397
### Using the Python bindings
398398

399+
There are two available methods for installing the Python bindings, either
400+
through creating an editable wheel or through extending `PYTHONPATH`.
401+
402+
#### Option A: Installing the bindings as editable wheels
403+
404+
This method links the files in your build tree into your Python package directory
405+
as an editable wheel.
406+
407+
=== ":fontawesome-brands-linux: Linux"
408+
409+
``` shell
410+
CMAKE_INSTALL_METHOD=ABS_SYMLINK python -m pip install -e ../iree-build/compiler
411+
CMAKE_INSTALL_METHOD=ABS_SYMLINK python -m pip install -e ../iree-build/runtime
412+
```
413+
414+
=== ":fontawesome-brands-apple: macOS"
415+
416+
``` shell
417+
CMAKE_INSTALL_METHOD=ABS_SYMLINK python -m pip install -e ../iree-build/compiler
418+
CMAKE_INSTALL_METHOD=ABS_SYMLINK python -m pip install -e ../iree-build/runtime
419+
```
420+
421+
=== ":fontawesome-brands-windows: Windows"
422+
423+
``` powershell
424+
$env:CMAKE_INSTALL_MODE="ABS_SYMLINK"
425+
python -m pip install -e ..\iree-build\compiler
426+
python -m pip install -e ..\iree-build\runtime
427+
$env:CMAKE_INSTALL_MODE=null
428+
```
429+
430+
#### Option B: Extending PYTHONPATH
431+
432+
This method more effectively captures the state of your build directory,
433+
but is prone to errors arising from forgetting to source the environment
434+
variables.
435+
399436
Extend your `PYTHONPATH` with IREE's `bindings/python` paths and try importing:
400437

401438
=== ":fontawesome-brands-linux: Linux"
@@ -431,13 +468,16 @@ Extend your `PYTHONPATH` with IREE's `bindings/python` paths and try importing:
431468
python -c "import iree.runtime; help(iree.runtime)"
432469
```
433470

471+
#### Tensorflow/TFLite bindings
472+
434473
Using IREE's TensorFlow/TFLite importers requires a few extra steps:
435474

436475
``` shell
437476
# Install test requirements
438477
python -m pip install -r integrations/tensorflow/test/requirements.txt
439478

440479
# Install pure Python packages (no build required)
480+
# You may use `pip install -e` here to create an editable wheel.
441481
python -m pip install integrations/tensorflow/python_projects/iree_tf
442482
python -m pip install integrations/tensorflow/python_projects/iree_tflite
443483

runtime/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ iree_include_cmake_plugin_dirs(
3434
add_subdirectory(src)
3535

3636
if(IREE_BUILD_PYTHON_BINDINGS)
37+
configure_file(pyproject.toml pyproject.toml COPYONLY)
38+
configure_file(setup.py setup.py @ONLY)
3739
add_subdirectory(bindings/python)
3840
endif()

runtime/setup.py

Lines changed: 109 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
# Select CMake options are available from environment variables:
2424
# IREE_HAL_DRIVER_VULKAN
2525
# IREE_ENABLE_CPUINFO
26+
#
27+
# If building from a development tree and aiming to get an "editable" install,
28+
# use the environment option CMAKE_INSTALL_MODE=ABS_SYMLINK on your
29+
# `pip install -e .` invocation.
2630

2731
import json
2832
import os
@@ -41,8 +45,11 @@
4145
from setuptools.command.build_py import build_py as _build_py
4246

4347

44-
def getenv_bool(key, default_value="OFF"):
45-
value = os.getenv(key, default_value)
48+
def getenv_bool(key: str, cmake_arg: str, default_value="OFF"):
49+
if cmake_arg == "" or cmake_arg[0] != "@":
50+
value = cmake_arg
51+
else:
52+
value = os.getenv(key, default_value)
4653
return value.upper() in ["ON", "1", "TRUE"]
4754

4855

@@ -53,7 +60,17 @@ def combine_dicts(*ds):
5360
return result
5461

5562

56-
ENABLE_TRACY = getenv_bool("IREE_RUNTIME_BUILD_TRACY", "ON")
63+
# This file can be run directly from the source tree or it can be CMake
64+
# configured so it can run from the build tree with an already existing
65+
# build tree. We detect the difference based on whether the following
66+
# are expanded by CMake.
67+
CONFIGURED_SOURCE_DIR = "@IREE_SOURCE_DIR@"
68+
CONFIGURED_BINARY_DIR = "@IREE_BINARY_DIR@"
69+
70+
ENABLE_TRACY = getenv_bool(
71+
"IREE_RUNTIME_BUILD_TRACY", "@IREE_RUNTIME_BUILD_TRACY@", "ON"
72+
)
73+
5774
if ENABLE_TRACY:
5875
print(
5976
"*** Enabling Tracy instrumented runtime (disable with IREE_RUNTIME_BUILD_TRACY=OFF)",
@@ -64,7 +81,9 @@ def combine_dicts(*ds):
6481
"*** Tracy instrumented runtime not enabled (enable with IREE_RUNTIME_BUILD_TRACY=ON)",
6582
file=sys.stderr,
6683
)
67-
ENABLE_TRACY_TOOLS = getenv_bool("IREE_RUNTIME_BUILD_TRACY_TOOLS")
84+
ENABLE_TRACY_TOOLS = getenv_bool(
85+
"IREE_RUNTIME_BUILD_TRACY_TOOLS", "@IREE_RUNTIME_BUILD_TRACY_TOOLS@"
86+
)
6887
if ENABLE_TRACY_TOOLS:
6988
print("*** Enabling Tracy tools (may error if missing deps)", file=sys.stderr)
7089
else:
@@ -111,21 +130,33 @@ def check_pip_version():
111130
CMAKE_TRACY_INSTALL_DIR_REL = os.path.join("build", "i", "t")
112131
CMAKE_TRACY_INSTALL_DIR_ABS = os.path.join(SETUPPY_DIR, CMAKE_TRACY_INSTALL_DIR_REL)
113132

114-
IREE_SOURCE_DIR = os.path.join(SETUPPY_DIR, "..")
115-
# Note that setuptools always builds into a "build" directory that
116-
# is a sibling of setup.py, so we just colonize a sub-directory of that
117-
# by default.
118-
BASE_BINARY_DIR = os.getenv(
119-
"IREE_RUNTIME_API_CMAKE_BUILD_DIR", os.path.join(SETUPPY_DIR, "build", "b")
120-
)
121-
IREE_BINARY_DIR = os.path.join(BASE_BINARY_DIR, "d")
122-
IREE_TRACY_BINARY_DIR = os.path.join(BASE_BINARY_DIR, "t")
123-
print(
124-
f"Running setup.py from source tree: "
125-
f"SOURCE_DIR = {IREE_SOURCE_DIR} "
126-
f"BINARY_DIR = {IREE_BINARY_DIR}",
127-
file=sys.stderr,
128-
)
133+
IS_CONFIGURED = CONFIGURED_SOURCE_DIR[0] != "@"
134+
if IS_CONFIGURED:
135+
IREE_SOURCE_DIR = CONFIGURED_SOURCE_DIR
136+
IREE_BINARY_DIR = CONFIGURED_BINARY_DIR
137+
IREE_TRACY_BINARY_DIR = CONFIGURED_BINARY_DIR
138+
print(
139+
f"Running setup.py from build tree: "
140+
f"SOURCE_DIR = {IREE_SOURCE_DIR} "
141+
f"BINARY_DIR = {IREE_BINARY_DIR}",
142+
file=sys.stderr,
143+
)
144+
else:
145+
IREE_SOURCE_DIR = os.path.join(SETUPPY_DIR, "..")
146+
# Note that setuptools always builds into a "build" directory that
147+
# is a sibling of setup.py, so we just colonize a sub-directory of that
148+
# by default.
149+
BASE_BINARY_DIR = os.getenv(
150+
"IREE_RUNTIME_API_CMAKE_BUILD_DIR", os.path.join(SETUPPY_DIR, "build", "b")
151+
)
152+
IREE_BINARY_DIR = os.path.join(BASE_BINARY_DIR, "d")
153+
IREE_TRACY_BINARY_DIR = os.path.join(BASE_BINARY_DIR, "t")
154+
print(
155+
f"Running setup.py from source tree: "
156+
f"SOURCE_DIR = {IREE_SOURCE_DIR} "
157+
f"BINARY_DIR = {IREE_BINARY_DIR}",
158+
file=sys.stderr,
159+
)
129160

130161
# Setup and get version information.
131162
VERSION_FILE = os.path.join(IREE_SOURCE_DIR, "runtime/version.json")
@@ -263,61 +294,62 @@ def build_configuration(cmake_build_dir, cmake_install_dir, extra_cmake_args=())
263294
cfg = os.getenv("IREE_CMAKE_BUILD_TYPE", "Release")
264295
strip_install = cfg == "Release"
265296

266-
# Build from source tree.
267-
os.makedirs(cmake_build_dir, exist_ok=True)
268-
maybe_nuke_cmake_cache(cmake_build_dir, cmake_install_dir)
269-
print(f"CMake build dir: {cmake_build_dir}", file=sys.stderr)
270-
print(f"CMake install dir: {cmake_install_dir}", file=sys.stderr)
271-
cmake_args = [
272-
"-GNinja",
273-
"--log-level=VERBOSE",
274-
f"-DIREE_RUNTIME_OPTIMIZATION_PROFILE={IREE_RUNTIME_OPTIMIZATION_PROFILE}",
275-
"-DIREE_BUILD_PYTHON_BINDINGS=ON",
276-
"-DIREE_BUILD_COMPILER=OFF",
277-
"-DIREE_BUILD_SAMPLES=OFF",
278-
"-DIREE_BUILD_TESTS=OFF",
279-
"-DPython3_EXECUTABLE={}".format(sys.executable),
280-
"-DCMAKE_BUILD_TYPE={}".format(cfg),
281-
get_env_cmake_option(
282-
"IREE_HAL_DRIVER_VULKAN",
283-
"OFF" if platform.system() == "Darwin" else "ON",
284-
),
285-
get_env_cmake_option(
286-
"IREE_HAL_DRIVER_CUDA",
287-
"OFF",
288-
),
289-
get_env_cmake_option(
290-
"IREE_HAL_DRIVER_HIP",
291-
"OFF",
292-
),
293-
get_env_cmake_list("IREE_EXTERNAL_HAL_DRIVERS", ""),
294-
get_env_cmake_option("IREE_ENABLE_CPUINFO", "ON"),
295-
] + list(extra_cmake_args)
296-
add_env_cmake_setting(cmake_args, "IREE_TRACING_PROVIDER")
297-
add_env_cmake_setting(cmake_args, "IREE_TRACING_PROVIDER_H")
298-
299-
# These usually flow through the environment, but we add them explicitly
300-
# so that they show clearly in logs (getting them wrong can have bad
301-
# outcomes).
302-
add_env_cmake_setting(cmake_args, "CMAKE_OSX_ARCHITECTURES")
303-
add_env_cmake_setting(
304-
cmake_args, "MACOSX_DEPLOYMENT_TARGET", "CMAKE_OSX_DEPLOYMENT_TARGET"
305-
)
306-
307-
# Only do a from-scratch configure if not already configured.
308-
cmake_cache_file = os.path.join(cmake_build_dir, "CMakeCache.txt")
309-
if not os.path.exists(cmake_cache_file):
310-
print(f"Configuring with: {cmake_args}", file=sys.stderr)
311-
subprocess.check_call(
312-
["cmake", IREE_SOURCE_DIR] + cmake_args, cwd=cmake_build_dir
297+
if not IS_CONFIGURED:
298+
# Build from source tree.
299+
os.makedirs(cmake_build_dir, exist_ok=True)
300+
maybe_nuke_cmake_cache(cmake_build_dir, cmake_install_dir)
301+
print(f"CMake build dir: {cmake_build_dir}", file=sys.stderr)
302+
print(f"CMake install dir: {cmake_install_dir}", file=sys.stderr)
303+
cmake_args = [
304+
"-GNinja",
305+
"--log-level=VERBOSE",
306+
f"-DIREE_RUNTIME_OPTIMIZATION_PROFILE={IREE_RUNTIME_OPTIMIZATION_PROFILE}",
307+
"-DIREE_BUILD_PYTHON_BINDINGS=ON",
308+
"-DIREE_BUILD_COMPILER=OFF",
309+
"-DIREE_BUILD_SAMPLES=OFF",
310+
"-DIREE_BUILD_TESTS=OFF",
311+
"-DPython3_EXECUTABLE={}".format(sys.executable),
312+
"-DCMAKE_BUILD_TYPE={}".format(cfg),
313+
get_env_cmake_option(
314+
"IREE_HAL_DRIVER_VULKAN",
315+
"OFF" if platform.system() == "Darwin" else "ON",
316+
),
317+
get_env_cmake_option(
318+
"IREE_HAL_DRIVER_CUDA",
319+
"OFF",
320+
),
321+
get_env_cmake_option(
322+
"IREE_HAL_DRIVER_HIP",
323+
"OFF",
324+
),
325+
get_env_cmake_list("IREE_EXTERNAL_HAL_DRIVERS", ""),
326+
get_env_cmake_option("IREE_ENABLE_CPUINFO", "ON"),
327+
] + list(extra_cmake_args)
328+
add_env_cmake_setting(cmake_args, "IREE_TRACING_PROVIDER")
329+
add_env_cmake_setting(cmake_args, "IREE_TRACING_PROVIDER_H")
330+
331+
# These usually flow through the environment, but we add them explicitly
332+
# so that they show clearly in logs (getting them wrong can have bad
333+
# outcomes).
334+
add_env_cmake_setting(cmake_args, "CMAKE_OSX_ARCHITECTURES")
335+
add_env_cmake_setting(
336+
cmake_args, "MACOSX_DEPLOYMENT_TARGET", "CMAKE_OSX_DEPLOYMENT_TARGET"
313337
)
314-
else:
315-
print(f"Not re-configuring (already configured)", file=sys.stderr)
316338

317-
# Build. Since we have restricted to just the runtime, build everything
318-
# so as to avoid fragility with more targeted selection criteria.
319-
subprocess.check_call(["cmake", "--build", "."], cwd=cmake_build_dir)
320-
print("Build complete.", file=sys.stderr)
339+
# Only do a from-scratch configure if not already configured.
340+
cmake_cache_file = os.path.join(cmake_build_dir, "CMakeCache.txt")
341+
if not os.path.exists(cmake_cache_file):
342+
print(f"Configuring with: {cmake_args}", file=sys.stderr)
343+
subprocess.check_call(
344+
["cmake", IREE_SOURCE_DIR] + cmake_args, cwd=cmake_build_dir
345+
)
346+
else:
347+
print(f"Not re-configuring (already configured)", file=sys.stderr)
348+
349+
# Build. Since we have restricted to just the runtime, build everything
350+
# so as to avoid fragility with more targeted selection criteria.
351+
subprocess.check_call(["cmake", "--build", "."], cwd=cmake_build_dir)
352+
print("Build complete.", file=sys.stderr)
321353

322354
# Install the component we care about.
323355
install_args = [
@@ -361,7 +393,9 @@ class CMakeBuildPy(_build_py):
361393
def run(self):
362394
# The super-class handles the pure python build.
363395
super().run()
364-
self.build_default_configuration()
396+
# If we're in an existing build with tracy enabled, don't build the default config.
397+
if not (IS_CONFIGURED and ENABLE_TRACY):
398+
self.build_default_configuration()
365399
if ENABLE_TRACY:
366400
self.build_tracy_configuration()
367401

@@ -388,7 +422,7 @@ def build_default_configuration(self):
388422
"_runtime_libs",
389423
),
390424
target_dir,
391-
symlinks=False,
425+
symlinks=self.editable_mode,
392426
)
393427
print("Target populated.", file=sys.stderr)
394428

@@ -424,7 +458,7 @@ def build_tracy_configuration(self):
424458
"_runtime_libs",
425459
),
426460
target_dir,
427-
symlinks=False,
461+
symlinks=self.editable_mode,
428462
)
429463
print("Target populated.", file=sys.stderr)
430464

0 commit comments

Comments
 (0)