xcode.py 7.14 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# 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.
17
# pylint: disable=invalid-name
18
"""Utility to invoke Xcode compiler toolchain"""
19
from __future__ import absolute_import as _abs
20

Tianqi Chen committed
21
import os
22 23
import sys
import subprocess
24
from .._ffi.base import py_str
25 26
from . import util

Tianqi Chen committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
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 codesign(lib):
    """Codesign the shared libary

    This is an required step for library to be loaded in
    the app.

    Parameters
    ----------
    lib : The path to the library.
    """
    if "TVM_IOS_CODESIGN" not in os.environ:
        raise RuntimeError("Require environment variable TVM_IOS_CODESIGN "
                           " to be the signature")
    signature = os.environ["TVM_IOS_CODESIGN"]
    cmd = ["codesign", "--force", "--sign", signature]
    cmd += [lib]
    proc = subprocess.Popen(cmd,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT)
    (out, _) = proc.communicate()
    if proc.returncode != 0:
        msg = "Codesign error:\n"
70
        msg += py_str(out)
Tianqi Chen committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
        raise RuntimeError(msg)


def create_dylib(output, objects, arch, sdk="macosx"):
    """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 += ["-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"
113
        msg += py_str(out)
Tianqi Chen committed
114 115 116
        raise RuntimeError(msg)


117 118 119
# assign so as default output format
create_dylib.output_format = "dylib"

120
def compile_metal(code, path_target=None, sdk="macosx"):
121 122 123 124 125 126 127 128 129 130
    """Compile metal with CLI tool from env.

    Parameters
    ----------
    code : str
        The cuda code.

    path_target : str, optional
        Output file.

131 132 133
    sdk : str, optional
        The target platform SDK.

134 135 136 137 138 139 140 141 142 143 144 145 146 147
    Return
    ------
    metallib : bytearray
        The bytearray of the metallib
    """
    temp = util.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

148 149 150 151 152
    # 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
153
    cmd1 = ["xcrun", "-sdk", sdk, "metal", "-O3"]
154
    cmd1 += ["-c", temp_code, "-o", temp_ir]
155
    cmd2 = ["xcrun", "-sdk", sdk, "metallib"]
156 157 158 159 160 161 162 163 164
    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")
165
        sys.stderr.write(py_str(out))
166 167 168 169 170
        sys.stderr.flush()
        libbin = None
    else:
        libbin = bytearray(open(file_target, "rb").read())
    return libbin
Tianqi Chen committed
171 172


173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
class XCodeRPCServer(object):
    """Wrapper for RPC server

    Parameters
    ----------
    cmd : list of str
       The command to run

    lock: FileLock
       Lock on the path
    """
    def __init__(self, cmd, lock):
        self.proc = subprocess.Popen(cmd)
        self.lock = lock

    def join(self):
        """Wait server to finish and release its resource
        """
        self.proc.wait()
        self.lock.release()


Tianqi Chen committed
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
def popen_test_rpc(host,
                   port,
                   key,
                   destination,
                   libs=None,
                   options=None):
    """Launch rpc server via xcodebuild test through another process.

    Parameters
    ----------
    host : str
        The address of RPC proxy host.

    port : int
        The port of RPC proxy host

    key : str
        The key of the RPC server

    destination : str
        Destination device of deployment, as in xcodebuild

    libs : list of str
        List of files to be packed into app/Frameworks/tvm
        These can be dylibs that can be loaed remoted by RPC.

    options : list of str
        Additional options to xcodebuild

    Returns
    -------
    proc : Popen
        The test rpc server process.
        Don't do wait() on proc, since it can terminate normally.
    """
    if "TVM_IOS_RPC_ROOT" in os.environ:
        rpc_root = os.environ["TVM_IOS_RPC_ROOT"]
    else:
233
        curr_path = os.path.dirname(os.path.realpath(os.path.expanduser(__file__)))
Tianqi Chen committed
234
        rpc_root = os.path.join(curr_path, "../../../apps/ios_rpc")
235
    proj_path = os.path.realpath(os.path.join(rpc_root, "tvmrpc.xcodeproj"))
Tianqi Chen committed
236 237 238
    if not os.path.exists(proj_path):
        raise RuntimeError("Cannot find tvmrpc.xcodeproj in %s," +
                           (" please set env TVM_IOS_RPC_ROOT correctly" % rpc_root))
239 240 241 242

    # Lock the path so only one file can run
    lock = util.filelock(os.path.join(rpc_root, "ios_rpc.lock"))

Tianqi Chen committed
243 244 245 246 247 248 249 250 251 252 253 254 255
    with open(os.path.join(rpc_root, "rpc_config.txt"), "w") as fo:
        fo.write("%s %d %s\n" % (host, port, key))
        libs = libs if libs else []
        for file_name in libs:
            fo.write("%s\n" % file_name)

    cmd = ["xcrun", "xcodebuild",
           "-scheme", "tvmrpc",
           "-project", proj_path,
           "-destination", destination]
    if options:
        cmd += options
    cmd += ["test"]
256 257

    return XCodeRPCServer(cmd, lock)