#!/usr/bin/env python
# coding: utf-8

# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import print_function

import argparse
import os
import re
import subprocess
import sys
import textwrap


def _AsVersion(version_str):
    return tuple((int(s) for s in re.findall(r'(\d+)', version_str)))


def _RunXCRun(args, sdk=None):
    xcrun_args = ['xcrun']
    if sdk is not None:
        xcrun_args.extend(['--sdk', sdk])
    xcrun_args.extend(args)
    return subprocess.check_output(xcrun_args).decode('utf-8').rstrip()


def _SDKPath(sdk=None):
    return _RunXCRun(['--show-sdk-path'], sdk)


def _SDKVersion(sdk=None):
    return _AsVersion(_RunXCRun(['--show-sdk-version'], sdk))


class DidNotMeetCriteria(Exception):
    pass


def _FindPlatformSDKWithMinimumVersion(platform, minimum_sdk_version_str):
    minimum_sdk_version = _AsVersion(minimum_sdk_version_str)

    # Try the SDKs that Xcode knows about.
    xcodebuild_showsdks_subprocess = subprocess.Popen(
        ['xcodebuild', '-showsdks'],
        stdout=subprocess.PIPE,
        stderr=open(os.devnull, 'w'))
    xcodebuild_showsdks_output = (
        xcodebuild_showsdks_subprocess.communicate()[0].decode('utf-8'))
    if xcodebuild_showsdks_subprocess.returncode == 0:
        # Collect strings instead of version objects to preserve the precise
        # format used to identify each SDK.
        sdk_version_strs = []
        for line in xcodebuild_showsdks_output.splitlines():
            match = re.match(
                '[ \t].+[ \t]-sdk ' + re.escape(platform) + '(.+)$', line)
            if match:
                sdk_version_str = match.group(1)
                if _AsVersion(sdk_version_str) >= minimum_sdk_version:
                    sdk_version_strs.append(sdk_version_str)

        if len(sdk_version_strs) == 0:
            raise DidNotMeetCriteria({
                'minimum': minimum_sdk_version_str,
                'platform': platform
            })
        sdk_version_str = sorted(sdk_version_strs, key=_AsVersion)[0]
        sdk_path = _SDKPath(platform + sdk_version_str)
        sdk_version = _AsVersion(sdk_version_str)
    else:
        # Xcode may not be installed. If the command-line tools are installed,
        # use the system’s default SDK if it meets the requirements.
        sdk_path = _SDKPath()
        sdk_version = _SDKVersion()
        if sdk_version < minimum_sdk_version:
            raise DidNotMeetCriteria({
                'minimum': minimum_sdk_version_str,
                'platform': platform,
                'sdk_path': sdk_path,
                'sdk_version': str(sdk_version)
            })

    return (sdk_version, sdk_path)


def main(args):
    parser = argparse.ArgumentParser(
        description='Find an appropriate platform SDK',
        epilog='Two lines will be written to standard output: the version of '
        'the selected SDK, and its path.')
    parser.add_argument('--developer-dir',
                        help='path to Xcode or Command Line Tools')
    parser.add_argument('--exact', help='an exact SDK version to find')
    parser.add_argument('--minimum', help='the minimum SDK version to find')
    parser.add_argument('--path', help='a known SDK path to validate')
    parser.add_argument('--platform',
                        default='macosx',
                        help='the platform to target')
    parsed = parser.parse_args(args)

    if parsed.developer_dir is not None:
        os.environ['DEVELOPER_DIR'] = parsed.developer_dir

    if (os.environ.get('DEVELOPER_DIR') is None and
            subprocess.call(['xcode-select', '--print-path'],
                            stdout=open(os.devnull, 'w'),
                            stderr=open(os.devnull, 'w')) != 0):
        # This is friendlier than letting the first invocation of xcrun or
        # xcodebuild show the UI prompting to install developer tools at an
        # inopportune time.
        hint = 'Install Xcode and run "sudo xcodebuild -license"'
        if parsed.platform == 'macosx':
            hint += (
                ', or install Command Line Tools with "xcode-select --install"')
        hint += (
            '. If necessary, run "sudo xcode-select --switch" to select an '
            'active developer tools installation.')
        hint = '\n'.join(textwrap.wrap(hint, 80))
        print(os.path.basename(sys.argv[0]) + ': No developer tools found.\n' +
              hint,
              file=sys.stderr)
        return 1

    if parsed.path is not None:
        # _SDKVersion() doesn’t work with a relative pathname argument or one
        # that’s a symbolic link. Such paths are suitable for other purposes,
        # like “clang -isysroot”, so use an absolute non-symbolic link path for
        # _SDKVersion(), but preserve the user’s path in sdk_path.
        sdk_version = _SDKVersion(os.path.realpath(parsed.path))
        sdk_path = parsed.path
    elif parsed.exact is None and parsed.minimum is None:
        # Use the platform’s default SDK.
        sdk_version = _SDKVersion(parsed.platform)
        sdk_path = _SDKPath(parsed.platform)
    elif parsed.exact is not None:
        sdk_version = _SDKVersion(parsed.platform + parsed.exact)
        sdk_path = _SDKPath(parsed.platform + parsed.exact)
    else:
        (sdk_version,
         sdk_path) = _FindPlatformSDKWithMinimumVersion(parsed.platform,
                                                        parsed.minimum)

    # These checks may be redundant depending on how the SDK was chosen.
    if ((parsed.exact is not None and sdk_version != _AsVersion(parsed.exact))
            or (parsed.minimum is not None and
                sdk_version < _AsVersion(parsed.minimum))):
        raise DidNotMeetCriteria({
            'developer_dir': parsed.developer_dir,
            'exact': parsed.exact,
            'minimum': parsed.minimum,
            'path': parsed.path,
            'platform': parsed.platform,
            'sdk_path': sdk_path,
            'sdk_version': str(sdk_version)
        })

    # Nobody wants trailing slashes. This is true even if “/” is the SDK: it’s
    # better to return an empty string, which will be interpreted as “no
    # sysroot.”
    sdk_path = sdk_path.rstrip(os.path.sep)

    print(sdk_version)
    print(sdk_path)

    return 0


if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
