185 lines
5.5 KiB
Python
185 lines
5.5 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
|
# or more contributor license agreements. See the NOTICE file
|
|
# distributed with this work for additional information
|
|
# regarding copyright ownership. The ASF licenses this file
|
|
# to you under the Apache License, Version 2.0 (the
|
|
# "License"); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing,
|
|
# software distributed under the License is distributed on an
|
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
# KIND, either express or implied. See the License for the
|
|
# specific language governing permissions and limitations
|
|
# under the License.
|
|
# pylint: disable=invalid-name
|
|
# ruff: noqa: E501, RUF005
|
|
"""Utility to invoke Xcode compiler toolchain"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
from . import utils
|
|
|
|
|
|
def xcrun(cmd):
|
|
"""Run xcrun and return the output.
|
|
|
|
Parameters
|
|
----------
|
|
cmd : list of str
|
|
The command sequence.
|
|
|
|
Returns
|
|
-------
|
|
out : str
|
|
The output string.
|
|
"""
|
|
cmd = ["xcrun"] + cmd
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
(out, _) = proc.communicate()
|
|
return out.strip()
|
|
|
|
|
|
def __get_min_os_version(sdk):
|
|
if sdk == "macosx":
|
|
return None
|
|
if sdk in ("iphoneos", "iphonesimulator"):
|
|
return "13.0"
|
|
raise RuntimeError(f"Unsupported sdk: {sdk}")
|
|
|
|
|
|
def __get_min_os_version_cmd(sdk, min_os_version):
|
|
if min_os_version is None:
|
|
min_os_version = __get_min_os_version(sdk)
|
|
if min_os_version is not None:
|
|
return "-mios-version-min=" + min_os_version
|
|
return ""
|
|
|
|
|
|
def create_dylib(output, objects, arch, sdk="macosx", min_os_version=None):
|
|
"""Create dynamic library.
|
|
|
|
Parameters
|
|
----------
|
|
output : str
|
|
The target shared library.
|
|
|
|
objects : list
|
|
List of object files.
|
|
|
|
options : str
|
|
The additional options.
|
|
|
|
arch : str
|
|
Target major architectures
|
|
|
|
sdk : str
|
|
The sdk to be used.
|
|
"""
|
|
clang = xcrun(["-sdk", sdk, "-find", "clang"])
|
|
sdk_path = xcrun(["-sdk", sdk, "--show-sdk-path"])
|
|
cmd = [clang]
|
|
cmd += ["-dynamiclib"]
|
|
cmd += ["-arch", arch]
|
|
cmd += ["-isysroot", sdk_path]
|
|
cmd += [__get_min_os_version_cmd(sdk, min_os_version)]
|
|
cmd += ["-o", output]
|
|
if isinstance(objects, str):
|
|
cmd += [objects]
|
|
else:
|
|
cmd += objects
|
|
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
(out, _) = proc.communicate()
|
|
|
|
if proc.returncode != 0:
|
|
msg = "Compilation error:\n"
|
|
msg += out.decode("utf-8", errors="replace")
|
|
raise RuntimeError(msg)
|
|
|
|
|
|
# assign so as default output format
|
|
create_dylib.output_format = "dylib"
|
|
|
|
|
|
def compile_metal(code, path_target=None, sdk="macosx", min_os_version=None):
|
|
"""Compile Metal with CLI tool from env.
|
|
|
|
Parameters
|
|
----------
|
|
code : str
|
|
The Metal code.
|
|
|
|
path_target : str, optional
|
|
Output file.
|
|
|
|
sdk : str, optional
|
|
The target platform SDK.
|
|
|
|
Return
|
|
------
|
|
metallib : bytearray
|
|
The bytearray of the metallib
|
|
"""
|
|
temp = utils.tempdir()
|
|
temp_code = temp.relpath("my_lib.metal")
|
|
temp_ir = temp.relpath("my_lib.air")
|
|
temp_target = temp.relpath("my_lib.metallib")
|
|
|
|
with open(temp_code, "w") as out_file:
|
|
out_file.write(code)
|
|
file_target = path_target if path_target else temp_target
|
|
|
|
# See:
|
|
# - https://developer.apple.com/documentation/metal/gpu_functions_libraries/building_a_library_with_metal_s_command-line_tools#overview # pylint: disable=line-too-long
|
|
#
|
|
# xcrun -sdk macosx metal -c MyLibrary.metal -o MyLibrary.air
|
|
# xcrun -sdk macosx metallib MyLibrary.air -o MyLibrary.metallib
|
|
min_target = __get_min_os_version_cmd(sdk, min_os_version)
|
|
if sdk == "macosx":
|
|
language_version = "-std=macos-metal2.3"
|
|
elif sdk in ("iphoneos", "iphonesimulator"):
|
|
language_version = "-std=ios-metal2.3"
|
|
else:
|
|
raise RuntimeError(f"Unsupported sdk: {sdk}")
|
|
cmd1 = ["xcrun", "-sdk", sdk, "metal", language_version, min_target, "-O3"]
|
|
cmd1 += ["-c", temp_code, "-o", temp_ir]
|
|
cmd2 = ["xcrun", "-sdk", sdk, "metallib"]
|
|
cmd2 += [temp_ir, "-o", file_target]
|
|
proc = subprocess.Popen(
|
|
" ".join(cmd1) + ";" + " ".join(cmd2),
|
|
shell=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
(out, _) = proc.communicate()
|
|
if proc.returncode != 0:
|
|
sys.stderr.write("Compilation error:\n")
|
|
sys.stderr.write(out.decode("utf-8", errors="replace"))
|
|
sys.stderr.flush()
|
|
libbin = None
|
|
else:
|
|
libbin = bytearray(open(file_target, "rb").read())
|
|
return libbin
|
|
|
|
|
|
def compile_coreml(model, model_name="main", out_dir="."):
|
|
"""Compile coreml model and return the compiled model path."""
|
|
mlmodel_path = os.path.join(out_dir, model_name + ".mlmodel")
|
|
mlmodelc_path = os.path.join(out_dir, model_name + ".mlmodelc")
|
|
metadata = {"inputs": list(model.input_description), "outputs": list(model.output_description)}
|
|
# Use the description field to send info to CoreML runtime
|
|
model.short_description = json.dumps(metadata)
|
|
model.save(mlmodel_path)
|
|
|
|
res = xcrun(["coremlcompiler", "compile", mlmodel_path, out_dir])
|
|
if not os.path.isdir(mlmodelc_path):
|
|
raise RuntimeError(f"Compile failed: {res}")
|
|
|
|
return mlmodelc_path
|