tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

mac_toolchain.py (7389B)


      1 #!/usr/bin/env python3
      2 
      3 # Copyright 2018 The Chromium Authors
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 """
      8 If should_use_hermetic_xcode.py emits "1", and the current toolchain is out of
      9 date:
     10  * Downloads the hermetic mac toolchain
     11    * Requires CIPD authentication. Run `cipd auth-login`, use Google account.
     12  * Accepts the license.
     13    * If xcode-select and xcodebuild are not passwordless in sudoers, requires
     14      user interaction.
     15  * Downloads standalone binaries from [a possibly different version of Xcode].
     16 
     17 The toolchain version can be overridden by setting MAC_TOOLCHAIN_REVISION with
     18 the full revision, e.g. 9A235.
     19 """
     20 
     21 import argparse
     22 import os
     23 import platform
     24 import plistlib
     25 import shutil
     26 import subprocess
     27 import sys
     28 
     29 
     30 def LoadPList(path):
     31  """Loads Plist at |path| and returns it as a dictionary."""
     32  with open(path, 'rb') as f:
     33    return plistlib.load(f)
     34 
     35 
     36 # This contains binaries from Xcode 16.2 (16C5032) along with
     37 # the macOS SDK 15.2 (24C94). To build these packages, see comments in
     38 # build/xcode_binaries.yaml.
     39 # To update the version numbers, open Xcode's "About Xcode" or run
     40 # `xcodebuild -version` for the Xcode version, and run
     41 # `xcrun --show-sdk-version` and `xcrun --show-sdk-build-version`for
     42 # the SDK version. To update the _TAG, use the output of the
     43 # `cipd create` command mentioned in xcode_binaries.yaml;
     44 # it's the part after the colon.
     45 
     46 MAC_BINARIES_LABEL = 'infra_internal/ios/xcode/xcode_binaries/mac-amd64'
     47 MAC_BINARIES_TAG = 'o5KxJtacGXzkEoORkVUIOEPgGnL2okJzM4Km91eod9EC'
     48 
     49 # The toolchain will not be downloaded if the minimum OS version is not met. 19
     50 # is the major version number for macOS 10.15. Xcode 15.0 only runs on macOS
     51 # 13.5 and newer, but some bots are still running older OS versions. macOS
     52 # 10.15.4, the OS minimum through Xcode 12.4, still seems to work.
     53 MAC_MINIMUM_OS_VERSION = [19, 4]
     54 
     55 BASE_DIR = os.path.abspath(os.path.dirname(__file__))
     56 TOOLCHAIN_ROOT = os.path.join(BASE_DIR, 'mac_files')
     57 TOOLCHAIN_BUILD_DIR = os.path.join(TOOLCHAIN_ROOT, 'Xcode.app')
     58 
     59 # Always integrity-check the entire SDK. Mac SDK packages are complex and often
     60 # hit edge cases in cipd (eg https://crbug.com/1033987,
     61 # https://crbug.com/915278), and generally when this happens it requires manual
     62 # intervention to fix.
     63 # Note the trailing \n!
     64 PARANOID_MODE = '$ParanoidMode CheckIntegrity\n'
     65 
     66 
     67 def PlatformMeetsHermeticXcodeRequirements():
     68  if sys.platform != 'darwin':
     69    return True
     70  needed = MAC_MINIMUM_OS_VERSION
     71  major_version = [int(v) for v in platform.release().split('.')[:len(needed)]]
     72  return major_version >= needed
     73 
     74 
     75 def _UseHermeticToolchain():
     76  current_dir = os.path.dirname(os.path.realpath(__file__))
     77  script_path = os.path.join(current_dir, 'mac/should_use_hermetic_xcode.py')
     78  proc = subprocess.Popen([script_path, 'mac'], stdout=subprocess.PIPE)
     79  return '1' in proc.stdout.readline().decode()
     80 
     81 
     82 def RequestCipdAuthentication():
     83  """Requests that the user authenticate to access Xcode CIPD packages."""
     84 
     85  print('Access to Xcode CIPD package requires authentication.')
     86  print('-----------------------------------------------------------------')
     87  print()
     88  print('You appear to be a Googler.')
     89  print()
     90  print('I\'m sorry for the hassle, but you may need to do a one-time manual')
     91  print('authentication. Please run:')
     92  print()
     93  print('    cipd auth-login')
     94  print()
     95  print('and follow the instructions.')
     96  print()
     97  print('NOTE: Use your google.com credentials, not chromium.org.')
     98  print()
     99  print('-----------------------------------------------------------------')
    100  print()
    101  sys.stdout.flush()
    102 
    103 
    104 def PrintError(message):
    105  # Flush buffers to ensure correct output ordering.
    106  sys.stdout.flush()
    107  sys.stderr.write(message + '\n')
    108  sys.stderr.flush()
    109 
    110 
    111 def InstallXcodeBinaries():
    112  """Installs the Xcode binaries needed to build Chrome and accepts the license.
    113 
    114  This is the replacement for InstallXcode that installs a trimmed down version
    115  of Xcode that is OS-version agnostic.
    116  """
    117  # First make sure the directory exists. It will serve as the cipd root. This
    118  # also ensures that there will be no conflicts of cipd root.
    119  binaries_root = os.path.join(TOOLCHAIN_ROOT, 'xcode_binaries')
    120  if not os.path.exists(binaries_root):
    121    os.makedirs(binaries_root)
    122 
    123  # 'cipd ensure' is idempotent.
    124  args = ['cipd', 'ensure', '-root', binaries_root, '-ensure-file', '-']
    125 
    126  p = subprocess.Popen(args,
    127                       universal_newlines=True,
    128                       stdin=subprocess.PIPE,
    129                       stdout=subprocess.PIPE,
    130                       stderr=subprocess.PIPE)
    131  stdout, stderr = p.communicate(input=PARANOID_MODE + MAC_BINARIES_LABEL +
    132                                 ' ' + MAC_BINARIES_TAG)
    133  if p.returncode != 0:
    134    print(stdout)
    135    print(stderr)
    136    RequestCipdAuthentication()
    137    return 1
    138 
    139  if sys.platform != 'darwin':
    140    return 0
    141 
    142  # Accept the license for this version of Xcode if it's newer than the
    143  # currently accepted version.
    144  cipd_xcode_version_plist_path = os.path.join(binaries_root,
    145                                               'Contents/version.plist')
    146  cipd_xcode_version_plist = LoadPList(cipd_xcode_version_plist_path)
    147  cipd_xcode_version = cipd_xcode_version_plist['CFBundleShortVersionString']
    148 
    149  cipd_license_path = os.path.join(binaries_root,
    150                                   'Contents/Resources/LicenseInfo.plist')
    151  cipd_license_plist = LoadPList(cipd_license_path)
    152  cipd_license_version = cipd_license_plist['licenseID']
    153 
    154  should_overwrite_license = True
    155  current_license_path = '/Library/Preferences/com.apple.dt.Xcode.plist'
    156  if os.path.exists(current_license_path):
    157    current_license_plist = LoadPList(current_license_path)
    158    xcode_version = current_license_plist.get(
    159        'IDEXcodeVersionForAgreedToGMLicense')
    160    if (xcode_version is not None
    161        and xcode_version.split('.') >= cipd_xcode_version.split('.')):
    162      should_overwrite_license = False
    163 
    164  if not should_overwrite_license:
    165    return 0
    166 
    167  # Use puppet's sudoers script to accept the license if its available.
    168  license_accept_script = '/usr/local/bin/xcode_accept_license.sh'
    169  if os.path.exists(license_accept_script):
    170    args = [
    171        'sudo', license_accept_script, cipd_xcode_version, cipd_license_version
    172    ]
    173    subprocess.check_call(args)
    174    return 0
    175 
    176  # Otherwise manually accept the license. This will prompt for sudo.
    177  print('Accepting new Xcode license. Requires sudo.')
    178  sys.stdout.flush()
    179  args = [
    180      'sudo', 'defaults', 'write', current_license_path,
    181      'IDEXcodeVersionForAgreedToGMLicense', cipd_xcode_version
    182  ]
    183  subprocess.check_call(args)
    184  args = [
    185      'sudo', 'defaults', 'write', current_license_path,
    186      'IDELastGMLicenseAgreedTo', cipd_license_version
    187  ]
    188  subprocess.check_call(args)
    189  args = ['sudo', 'plutil', '-convert', 'xml1', current_license_path]
    190  subprocess.check_call(args)
    191 
    192  return 0
    193 
    194 
    195 def main():
    196  if not _UseHermeticToolchain():
    197    print('Skipping Mac toolchain installation for mac')
    198    return 0
    199 
    200  parser = argparse.ArgumentParser(description='Download hermetic Xcode.')
    201  args = parser.parse_args()
    202 
    203  if not PlatformMeetsHermeticXcodeRequirements():
    204    print('OS version does not support toolchain.')
    205    return 0
    206 
    207  return InstallXcodeBinaries()
    208 
    209 
    210 if __name__ == '__main__':
    211  sys.exit(main())