text: # Copyright 2016 The Bazel Authors. All rights reserved.
  #
  # Licensed 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.
  """Repository rule to generate host xcode_config and xcode_version targets.
  
     The xcode_config and xcode_version targets are configured for xcodes/SDKs
     installed on the local host.
  """
  
  
  def _search_string(fullstring, prefix, suffix):
    """Returns the substring between two given substrings of a larger string.
  
    Args:
      fullstring: The larger string to search.
      prefix: The substring that should occur directly before the returned string.
      suffix: The substring that should occur direclty after the returned string.
    Returns:
      A string occurring in fullstring exactly prefixed by prefix, and exactly
      terminated by suffix. For example, ("hello goodbye", "lo ", " bye") will
      return "good". If there is no such string, returns the empty string.
    """
  
    prefix_index = fullstring.find(prefix)
    if (prefix_index < 0):
      return ""
    result_start_index = prefix_index + len(prefix)
    suffix_index = fullstring.find(suffix, result_start_index)
    if (suffix_index < 0):
      return ""
    return fullstring[result_start_index:suffix_index]
  
  
  def _search_sdk_output(output, sdkname):
    """Returns the sdk version given xcodebuild stdout and an sdkname."""
    return _search_string(output, "(%s" % sdkname, ")")
  
  
  def _xcode_version_output(repository_ctx, name, version, aliases, developer_dir):
    """Returns a string containing an xcode_version build target."""
    build_contents = ""
    decorated_aliases = []
    error_msg = ""
    for alias in aliases:
      decorated_aliases.append("'%s'" % alias)
    xcodebuild_result = repository_ctx.execute(["xcrun", "xcodebuild", "-version", "-sdk"], 30,
                                               {"DEVELOPER_DIR": developer_dir})
    if (xcodebuild_result.return_code != 0):
      error_msg = (
          "Invoking xcodebuild failed, developer dir: {devdir} ," +
          "return code {code}, stderr: {err}, stdout: {out}").format(
              devdir=developer_dir,
              code=xcodebuild_result.return_code,
              err=xcodebuild_result.stderr,
              out=xcodebuild_result.stdout)
    ios_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "iphoneos")
    tvos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "appletvos")
    macos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "macosx")
    watchos_sdk_version = _search_sdk_output(xcodebuild_result.stdout, "watchos")
    build_contents += "xcode_version(\n  name = '%s'," % name
    build_contents += "\n  version = '%s'," % version
    if aliases:
      build_contents += "\n  aliases = [%s]," % " ,".join(decorated_aliases)
    if ios_sdk_version:
      build_contents += "\n  default_ios_sdk_version = '%s'," % ios_sdk_version
    if tvos_sdk_version:
      build_contents += "\n  default_tvos_sdk_version = '%s'," % tvos_sdk_version
    if macos_sdk_version:
      build_contents += "\n  default_macos_sdk_version = '%s'," % macos_sdk_version
    if watchos_sdk_version:
      build_contents += "\n  default_watchos_sdk_version = '%s'," % watchos_sdk_version
    build_contents += "\n)\n"
    if error_msg:
      build_contents += "\n# Error: " + error_msg.replace("\n", " ") + "\n"
      print(error_msg)
    return build_contents
  
  
  VERSION_CONFIG_STUB = "xcode_config(name = 'host_xcodes')"
  
  
  def run_xcode_locator(repository_ctx, xcode_locator_src_label):
    """Generates xcode-locator from source and runs it.
  
    Builds xcode-locator in the current repository directory.
    Returns the standard output of running xcode-locator with -v, which will
    return information about locally installed Xcode toolchains and the versions
    they are associated with.
  
    This should only be invoked on a darwin OS, as xcode-locator cannot be built
    otherwise.
  
    Args:
      repository_ctx: The repository context.
      xcode_locator_src_label: The label of the source file for xcode-locator.
    Returns:
      A 2-tuple containing:
      output: A list representing installed xcode toolchain information. Each
          element of the list is a struct containing information for one installed
          toolchain. This is an empty list if there was an error building or
          running xcode-locator.
      err: An error string describing the error that occurred when attempting
          to build and run xcode-locator, or None if the run was successful.
    """
    xcodeloc_src_path = str(repository_ctx.path(xcode_locator_src_label))
    xcrun_result = repository_ctx.execute(["env", "-i", "xcrun", "clang", "-fobjc-arc", "-framework",
                                           "CoreServices", "-framework", "Foundation", "-o",
                                           "xcode-locator-bin", xcodeloc_src_path], 30)
  
    if (xcrun_result.return_code != 0):
      suggestion = ""
      if "Agreeing to the Xcode/iOS license" in xcrun_result.stderr:
        suggestion = ("(You may need to sign the xcode license." +
                      " Try running 'sudo xcodebuild -license')")
      error_msg = (
          "Generating xcode-locator-bin failed. {suggestion} " +
          "return code {code}, stderr: {err}, stdout: {out}").format(
              suggestion=suggestion,
              code=xcrun_result.return_code,
              err=xcrun_result.stderr,
              out=xcrun_result.stdout)
      return ([], error_msg.replace("\n", " "))
  
    xcode_locator_result = repository_ctx.execute(["./xcode-locator-bin", "-v"], 30)
    if (xcode_locator_result.return_code != 0):
      error_msg = (
          "Invoking xcode-locator failed, " +
          "return code {code}, stderr: {err}, stdout: {out}").format(
              code=xcode_locator_result.return_code,
              err=xcode_locator_result.stderr,
              out=xcode_locator_result.stdout)
      return ([], error_msg.replace("\n", " "))
    xcode_toolchains = []
    # xcode_dump is comprised of newlines with different installed xcode versions,
    # each line of the form <version>:<comma_separated_aliases>:<developer_dir>.
    xcode_dump = xcode_locator_result.stdout
    for xcodeversion in xcode_dump.split("\n"):
      if ":" in xcodeversion:
        infosplit = xcodeversion.split(":")
        toolchain = struct(
            version = infosplit[0],
            aliases = infosplit[1].split(","),
            developer_dir = infosplit[2]
        )
        xcode_toolchains.append(toolchain)
    return (xcode_toolchains, None)
  
  
  def _darwin_build_file(repository_ctx):
    """Evaluates local system state to create xcode_config and xcode_version targets."""
    xcodebuild_result = repository_ctx.execute(["env", "-i", "xcrun", "xcodebuild", "-version"], 30)
    # "xcodebuild -version" failing may be indicative of no versions of xcode
    # installed, which is an acceptable machine configuration to have for using
    # bazel. Thus no print warning should be emitted here.
    if (xcodebuild_result.return_code != 0):
      error_msg = (
          "Running xcodebuild -version failed, " +
          "return code {code}, stderr: {err}, stdout: {out}").format(
              code=xcodebuild_result.return_code,
              err=xcodebuild_result.stderr,
              out=xcodebuild_result.stdout)
      return VERSION_CONFIG_STUB + "\n# Error: " + error_msg.replace("\n", " ") + "\n"
  
    (toolchains, xcodeloc_err) = run_xcode_locator(repository_ctx,
                                                   Label(repository_ctx.attr.xcode_locator))
  
    if xcodeloc_err:
      return VERSION_CONFIG_STUB + "\n# Error: " + xcodeloc_err + "\n"
  
    default_xcode_version = _search_string(xcodebuild_result.stdout, "Xcode ", "\n")
    default_xcode_target = ""
    target_names = []
    buildcontents = ""
  
    for toolchain in toolchains:
      version = toolchain.version
      aliases = toolchain.aliases
      developer_dir = toolchain.developer_dir
      target_name = "version%s" % version.replace(".", "_")
      buildcontents += _xcode_version_output(repository_ctx, target_name, version, aliases, developer_dir)
      target_names.append("':%s'" % target_name)
      if (version == default_xcode_version or default_xcode_version in aliases):
        default_xcode_target = target_name
    buildcontents += "xcode_config(name = 'host_xcodes',"
    if target_names:
      buildcontents += "\n  versions = [%s]," % ", ".join(target_names)
    if default_xcode_target:
      buildcontents += "\n  default = ':%s'," % default_xcode_target
    buildcontents += "\n)\n"
    return buildcontents
  
  
  def _impl(repository_ctx):
    """Implementation for the local_config_xcode repository rule.
  
    Generates a BUILD file containing a root xcode_config target named 'host_xcodes',
    which points to an xcode_version target for each version of xcode installed on
    the local host machine. If no versions of xcode are present on the machine
    (for instance, if this is a non-darwin OS), creates a stub target.
  
    Args:
      repository_ctx: The repository context.
    """
  
    os_name = repository_ctx.os.name.lower()
    build_contents = "package(default_visibility = ['//visibility:public'])\n\n"
    if (os_name.startswith("mac os")):
      build_contents += _darwin_build_file(repository_ctx)
    else:
      build_contents += VERSION_CONFIG_STUB
    repository_ctx.file("BUILD", build_contents)
  
  xcode_autoconf = repository_rule(
      implementation=_impl,
      local=True,
      attrs={
          "xcode_locator": attr.string(),
      }
  )
  
  
  def xcode_configure(xcode_locator_label):
    """Generates a repository containing host xcode version information."""
    xcode_autoconf(
        name="local_config_xcode",
        xcode_locator=xcode_locator_label
    )
  