ONE - On-device Neural Engine
Loading...
Searching...
No Matches
wheel_target_hook.py
Go to the documentation of this file.
1from hatchling.builders.hooks.plugin.interface import BuildHookInterface
2
3from packaging.tags import sys_tags
4import os
5import shutil
6import re
7
8
9class WheelBuildHook(BuildHookInterface):
10 """
11 This file contains the code used by the runtime/pyproject.toml config file. It's executed when the user attempts to build
12 a python wheel for the ONERT API. The code in this file is executed before the target is built.
13 """
14 def initialize(self, version, build_data):
15 super().initialize(version, build_data)
16
17 THIS_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
18 self.DEFAULT_PRODUCT_DIR = os.path.normpath(
19 os.path.join(THIS_FILE_DIR, "../../../Product"))
20
21 self.product_dir = None
22 self.platform = None
23 self.glibc_version = None
24
25 # read the environment variables that can be used to override some build settings
26 self.read_env()
27
28 # a temporary build dir used by the wheel build system
29 tmp_build_dir = "_build_"
30 self.recreate_dir(tmp_build_dir)
31
32 # this is the path where the native libraries are expected to land in the wheel
33 # the files copied to this location will eventually be added to the wheel by the build system
34 # the whole structure of subdirectories will be reflected in the final wheel
35 self.whl_binaries_target_dir = os.path.join(tmp_build_dir, "native")
36
37 # gather the required binaries in the temporary build directory
38 self.prepare_binaries()
39
40 # include all contents of the build directory in the 'onert' subdirectory in the wheel
41 # at this point the temporary build directory should be populated with the required files
42 build_data["force_include"][tmp_build_dir] = "onert"
43
44 build_data["pure_python"] = False
45 build_data["infer_tag"] = False
46 build_data["tag"] = self.create_build_tag()
47
48 def read_env(self):
49 """Read the relevant environment variables or use the defaults"""
50
51 self.product_dir = self._read_env("PRODUCT_DIR")
52 self.platform = self._read_env("PLATFORM")
53 self.glibc_version = self._read_env("GLIBC_VERSION")
54
56 # the main directory in the runtime's build tree containing the .so files
57 # those files need to be copied to whl_binaries_target_dir before they are added to the wheel
58 src_libs_base_dir = self.get_libs_dir()
59
60 self.copy_libraries(src_libs_base_dir, self.whl_binaries_target_dir)
61 self.copy_libraries(src_libs_base_dir, self.whl_binaries_target_dir, "nnfw")
62 self.copy_libraries(src_libs_base_dir, self.whl_binaries_target_dir,
63 "nnfw/backend")
64
65 def get_libs_dir(self):
66 """Retrieve the path of a directory where the required shared libraries are"""
67 runtime_build_dir = self.get_runtime_build_dir()
68 if not os.path.exists(runtime_build_dir):
69 raise FileExistsError(
70 f"The expected runtime build dir does not exist: {runtime_build_dir}")
71
72 print(f" |> runtime_build_dir={runtime_build_dir}")
73
74 possible_lib_dirs = ["lib64", "lib32", "lib"]
75 for lib_dir in possible_lib_dirs:
76 libs_dir_path = os.path.join(runtime_build_dir, lib_dir)
77 if os.path.exists(libs_dir_path):
78 return libs_dir_path
79
80 raise FileNotFoundError(f"No lib directory found in {runtime_build_dir}")
81
83 """Retrieve the path of a directory where the runtime's binaries are (the build tree's root)"""
84
85 if self.product_dir != self.DEFAULT_PRODUCT_DIR:
86 # In case the product directory was passed as an environment variable use this path
87 # as a custom root directory of the build tree
88 return self.product_dir
89 else:
90 # TODO - add the debug build support (via env variables)
91 return os.path.join(self.product_dir, f"{self.platform}-linux.release/out")
92
93 def copy_libraries(self, src_dir, target_dir, subdir=None):
94 """
95 Copy all .so files found in src_dir to the target_dir
96 If subdir is provided copy from src_dir/subdir to target_dir/subdir
97 """
98 if subdir is not None:
99 src_dir = os.path.join(src_dir, subdir)
100 target_dir = os.path.join(target_dir, subdir)
101
102 os.makedirs(target_dir, exist_ok=True)
103
104 for file in filter(lambda file: file.endswith(".so"), os.listdir(src_dir)):
105 src_path = os.path.join(src_dir, file)
106 tgt_path = os.path.join(target_dir, file)
107 shutil.copy(src_path, tgt_path)
108 print(f" |> Copied {src_path} to {tgt_path}")
109
110 def recreate_dir(self, dir_path):
111 """Delete a directory (if it exists) and create it again but empty"""
112 if os.path.exists(dir_path):
113 print(f" |> Deleting existing directory '{dir_path}'...")
114 shutil.rmtree(dir_path)
115 os.makedirs(dir_path)
116
118 """Create the most appropriate build tag that will be used to name the wheel"""
119
120 # first get the tag using the usual way build backends do it
121 tag = next(sys_tags())
122
123 # now create the part of the build tag that will be overridden
124 # use 'manylinux' + glibc version (if provided) + the platform string
125 tag_platform = "manylinux"
126 if self.glibc_version is not None:
127 tag_platform = f"{tag_platform}_{self.glibc_version}_{self.platform}"
128 else:
129 tag_platform = f"{tag_platform}_{self.platform}"
130
131 # compose the final tag and override the tag.platform part with our own string
132 build_tag = f"{tag.interpreter}-{tag.abi}-{tag_platform}"
133 print(f" |> Created build_tag: {build_tag}")
134 return build_tag
135
136 def _read_env(self, env_var_name):
137 validators = {
141 }
142
143 value = os.environ.get(env_var_name, None)
144 validate = validators.get(env_var_name)
145 if validate is not None:
146 return validate(value)
147 else:
148 return value
149
150 def _validate_platform(self, value):
151 supported_platforms = ["x86_64", "armv7l", "aarch64"]
152 if value is None:
153 print(
154 f" |> No platform specified. Creating a wheel for '{supported_platforms[0]}'"
155 )
156 return supported_platforms[0]
157 elif value not in supported_platforms:
158 raise ValueError(f"""Unsupported platform detected: {value}.
159 Please use one of the following values: {','.join(supported_platforms)}"""
160 )
161 else:
162 return value
163
164 def _validate_product_dir(self, value):
165 if value is None:
166 value = self.DEFAULT_PRODUCT_DIR
167
168 if not os.path.exists(value):
169 raise FileNotFoundError(f"The path with the build does not exist: '{value}'")
170 else:
171 return value
172
173 def _validate_glibc_version(self, value):
174 if value is None:
175 return value
176
177 # accept the X.Y format
178 m = re.search(r"^[0-9]+\.[0-9]+$", value)
179 if m is not None:
180 return value.replace(".", "_")
181
182 # or look for X_Y if the previous test failed
183 m = re.search(r"^[0-9]+_[0-9]+$", value)
184 if m is None:
185 raise ValueError(
186 "Incorrect version of glibc specified. Please use the following format: X_Y or X.Y"
187 )
188 else:
189 return value
copy_libraries(self, src_dir, target_dir, subdir=None)
initialize(self, version, build_data)