|
7 | 7 | import sh |
8 | 8 | import shutil |
9 | 9 | import fnmatch |
| 10 | +import zipfile |
10 | 11 | import urllib.request |
11 | 12 | from urllib.request import urlretrieve |
12 | | -from os import listdir, unlink, environ, curdir, walk |
| 13 | +from os import listdir, unlink, environ, curdir, walk, remove |
13 | 14 | from sys import stdout |
14 | 15 | import time |
15 | 16 | try: |
|
20 | 21 | import packaging.version |
21 | 22 |
|
22 | 23 | from pythonforandroid.logger import ( |
23 | | - logger, info, warning, debug, shprint, info_main) |
| 24 | + logger, info, warning, debug, shprint, info_main, error) |
24 | 25 | from pythonforandroid.util import ( |
25 | 26 | current_directory, ensure_dir, BuildInterruptingException, rmdir, move, |
26 | 27 | touch) |
@@ -169,13 +170,14 @@ def versioned_url(self): |
169 | 170 | return None |
170 | 171 | return self.url.format(version=self.version) |
171 | 172 |
|
172 | | - def download_file(self, url, target, cwd=None): |
| 173 | + def download_file(self, url, target, cwd=None, msg=True): |
173 | 174 | """ |
174 | 175 | (internal) Download an ``url`` to a ``target``. |
175 | 176 | """ |
176 | 177 | if not url: |
177 | 178 | return |
178 | | - info('Downloading {} from {}'.format(self.name, url)) |
| 179 | + if msg: |
| 180 | + info('Downloading {} from {}'.format(self.name, url)) |
179 | 181 |
|
180 | 182 | if cwd: |
181 | 183 | target = join(cwd, target) |
@@ -458,7 +460,6 @@ def unpack(self, arch): |
458 | 460 | # apparently happens sometimes with |
459 | 461 | # github zips |
460 | 462 | pass |
461 | | - import zipfile |
462 | 463 | fileh = zipfile.ZipFile(extraction_filename, 'r') |
463 | 464 | root_directory = fileh.filelist[0].filename.split('/')[0] |
464 | 465 | if root_directory != basename(directory_name): |
@@ -1126,7 +1127,184 @@ def get_recipe_env(self, arch, with_flags_in_cc=True): |
1126 | 1127 |
|
1127 | 1128 | return env |
1128 | 1129 |
|
| 1130 | +class RustCompiledComponentsRecipe(PythonRecipe): |
| 1131 | + |
| 1132 | + # Rust toolchain codes |
| 1133 | + # https://doc.rust-lang.org/nightly/rustc/platform-support.html |
| 1134 | + RUST_ARCH_CODES = { |
| 1135 | + "arm64-v8a": "aarch64-linux-android", |
| 1136 | + "armeabi-v7a": "armv7-linux-androideabi", |
| 1137 | + "x86_64": "x86_64-linux-android", |
| 1138 | + "x86": "i686-linux-android", |
| 1139 | + } |
| 1140 | + |
| 1141 | + # Build python wheel using `maturin` instead |
| 1142 | + # of default `python -m build [...]` |
| 1143 | + # Note: This option requires `maturin` to be installed in host system |
| 1144 | + use_maturin = False |
| 1145 | + |
| 1146 | + # Directory where to find built wheel |
| 1147 | + # For normal build: "dist/*-linux_*.whl" |
| 1148 | + # For maturin: "target/wheels/*-linux_*.whl" |
| 1149 | + built_wheel_pattern = None |
| 1150 | + |
| 1151 | + # Required are required for building |
| 1152 | + # These unpacked in hostpython's site-packages |
| 1153 | + # before building |
| 1154 | + # Warning: Don't specify arch specific wheels here |
| 1155 | + hostpython_wheels = {} |
1129 | 1156 |
|
| 1157 | + call_hostpython_via_targetpython = False |
| 1158 | + |
| 1159 | + def __init__(self, *arg, **kwargs): |
| 1160 | + super().__init__(*arg, **kwargs) |
| 1161 | + self.append_deps_if_absent(["python3"]) |
| 1162 | + |
| 1163 | + # Check for host deps |
| 1164 | + if not hasattr(sh, "rustup"): |
| 1165 | + error( |
| 1166 | + "`rustup` was not found on host system. Please install it using:" |
| 1167 | + "\n`curl https://sh.rustup.rs -sSf | sh`\n" |
| 1168 | + ) |
| 1169 | + exit(1) |
| 1170 | + |
| 1171 | + if self.use_maturin and not hasattr(sh, "maturin"): |
| 1172 | + error( |
| 1173 | + "maturin was not found on host system, please install with pip and rerun" |
| 1174 | + ) |
| 1175 | + exit(1) |
| 1176 | + |
| 1177 | + if not self.use_maturin: |
| 1178 | + self.hostpython_wheels["build"] = ( |
| 1179 | + "https://files.pythonhosted.org/packages/" |
| 1180 | + "93/dd/b464b728b866aaa62785a609e0dd8c72201d62c5f7c53e7c20f4dceb085f/" |
| 1181 | + "build-1.0.3-py3-none-any.whl" |
| 1182 | + ) |
| 1183 | + # Rust build requires setuptools-rust |
| 1184 | + self.hostpython_wheels["setuptools_rust"] = ( |
| 1185 | + "https://files.pythonhosted.org/packages/" |
| 1186 | + "e0/34/d88a7ceb193fbcee6c8992d1b1e33ed20361027e07fea1676efc45ec7a43/" |
| 1187 | + "setuptools_rust-1.8.1-py3-none-any.whl" |
| 1188 | + ) |
| 1189 | + self.hostpython_wheels["wheel"] = ( |
| 1190 | + "https://files.pythonhosted.org/packages/" |
| 1191 | + "c7/c3/55076fc728723ef927521abaa1955213d094933dc36d4a2008d5101e1af5/" |
| 1192 | + "wheel-0.42.0-py3-none-any.whl" |
| 1193 | + ) |
| 1194 | + |
| 1195 | + if self.built_wheel_pattern is None: |
| 1196 | + self.built_wheel_pattern = ( |
| 1197 | + "target/wheels/*-linux_*.whl" |
| 1198 | + if self.use_maturin else |
| 1199 | + "dist/*-linux_*.whl" |
| 1200 | + ) |
| 1201 | + |
| 1202 | + def install_hostpython_wheels(self): |
| 1203 | + workdir = join(dirname(self.real_hostpython_location), 'Lib', 'site-packages') |
| 1204 | + temp_zip = join(workdir, "temp.zip") |
| 1205 | + info("Processing hostpython wheels: {}".format(str(list(self.hostpython_wheels.keys())))) |
| 1206 | + for folder in self.hostpython_wheels.keys(): |
| 1207 | + self.download_file(self.hostpython_wheels[folder], temp_zip, msg=False) |
| 1208 | + with zipfile.ZipFile(join(workdir, temp_zip), "r") as zip_ref: |
| 1209 | + zip_ref.extractall(workdir) |
| 1210 | + remove(temp_zip) |
| 1211 | + |
| 1212 | + def append_deps_if_absent(self, deps): |
| 1213 | + # Just to ensure if 'build' and 'python' is present in deps |
| 1214 | + for dep in deps: |
| 1215 | + if dep not in self.depends: |
| 1216 | + self.depends.append(dep) |
| 1217 | + |
| 1218 | + def get_recipe_env(self, arch): |
| 1219 | + env = super().get_recipe_env(arch) |
| 1220 | + |
| 1221 | + # Set rust build target |
| 1222 | + build_target = self.RUST_ARCH_CODES[arch.arch] |
| 1223 | + cargo_linker_name = "CARGO_TARGET_{}_LINKER".format( |
| 1224 | + build_target.upper().replace("-", "_") |
| 1225 | + ) |
| 1226 | + env["CARGO_BUILD_TARGET"] = build_target |
| 1227 | + env[cargo_linker_name] = join( |
| 1228 | + self.ctx.ndk.llvm_prebuilt_dir, |
| 1229 | + "bin", |
| 1230 | + "{}{}-clang".format( |
| 1231 | + # NDK's Clang format |
| 1232 | + build_target.replace("7", "7a") |
| 1233 | + if build_target.startswith("armv7") |
| 1234 | + else build_target, |
| 1235 | + self.ctx.ndk_api, |
| 1236 | + ), |
| 1237 | + ) |
| 1238 | + env["RUSTFLAGS"] = "-Clink-args=-L{} -L{}".format( |
| 1239 | + # App libs dir |
| 1240 | + self.ctx.get_libs_dir(arch.arch), |
| 1241 | + # Python lib dir |
| 1242 | + join( |
| 1243 | + Recipe.get_recipe("python3", self.ctx).get_build_dir(arch.arch), |
| 1244 | + "android-build", |
| 1245 | + ) |
| 1246 | + ) |
| 1247 | + env["PYO3_CROSS_LIB_DIR"] = realpath(glob.glob(join( |
| 1248 | + Recipe.get_recipe("python3", self.ctx).get_build_dir(arch.arch), |
| 1249 | + 'android-build', "build", |
| 1250 | + "lib.linux-*-3.11/" |
| 1251 | + ))[0]) |
| 1252 | + |
| 1253 | + info_main("Ensuring rust build toolchain") |
| 1254 | + shprint(sh.rustup, "target", "add", build_target) |
| 1255 | + |
| 1256 | + # Add host python to PATH |
| 1257 | + env["PATH"] = ("{hostpython_dir}:{old_path}").format( |
| 1258 | + hostpython_dir=Recipe.get_recipe( |
| 1259 | + "hostpython3", self.ctx |
| 1260 | + ).get_path_to_python(), |
| 1261 | + old_path=env["PATH"], |
| 1262 | + ) |
| 1263 | + return env |
| 1264 | + |
| 1265 | + def build_arch(self, arch): |
| 1266 | + build_dir = self.get_build_dir(arch.arch) |
| 1267 | + env = self.get_recipe_env(arch) |
| 1268 | + self.install_hostpython_wheels() |
| 1269 | + python_recipe = Recipe.get_recipe("python3", self.ctx) |
| 1270 | + built_wheel = None |
| 1271 | + |
| 1272 | + #TODO: explain why |
| 1273 | + env['PYTHONPATH'] = join(dirname(self.real_hostpython_location), 'Lib', 'site-packages') |
| 1274 | + |
| 1275 | + with current_directory(build_dir): |
| 1276 | + if self.use_maturin: |
| 1277 | + shprint( |
| 1278 | + sh.maturin, |
| 1279 | + "build", |
| 1280 | + "-i", |
| 1281 | + "python{}".format( |
| 1282 | + ".".join(python_recipe.version.split(".")[:2]) |
| 1283 | + ), |
| 1284 | + "--skip-auditwheel", |
| 1285 | + _env=env, |
| 1286 | + ) |
| 1287 | + else: |
| 1288 | + shprint( |
| 1289 | + sh.Command(self.hostpython_location), |
| 1290 | + "-m", |
| 1291 | + "build", |
| 1292 | + "--no-isolation", |
| 1293 | + "--skip-dependency-check", |
| 1294 | + "--wheel", |
| 1295 | + _env=env, |
| 1296 | + ) |
| 1297 | + # Find the built wheel |
| 1298 | + built_wheel = realpath(glob.glob(self.built_wheel_pattern)[0]) |
| 1299 | + |
| 1300 | + if built_wheel: |
| 1301 | + info("Unzipping built wheel '{}'".format(basename(built_wheel))) |
| 1302 | + |
| 1303 | + # Unzip .whl file into site-packages |
| 1304 | + with zipfile.ZipFile(built_wheel, "r") as zip_ref: |
| 1305 | + zip_ref.extractall(self.ctx.get_python_install_dir(arch.arch)) |
| 1306 | + info("Successfully installed '{}'".format(basename(built_wheel))) |
| 1307 | + |
1130 | 1308 | class TargetPythonRecipe(Recipe): |
1131 | 1309 | '''Class for target python recipes. Sets ctx.python_recipe to point to |
1132 | 1310 | itself, so as to know later what kind of Python was built or used.''' |
|
0 commit comments