From a7cd12fc199cc50a97e38ad590a3e6ce4cc123ed Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Thu, 29 Oct 2015 20:59:28 -0700 Subject: [PATCH] Remove a number of unused Python scripts Removes sky_tool and shelldb, which are replaced by the flutter command line tool. --- packages/flutter/lib/sky_tool | 1320 --------------------------------- 1 file changed, 1320 deletions(-) delete mode 100755 packages/flutter/lib/sky_tool diff --git a/packages/flutter/lib/sky_tool b/packages/flutter/lib/sky_tool deleted file mode 100755 index 441f92538e..0000000000 --- a/packages/flutter/lib/sky_tool +++ /dev/null @@ -1,1320 +0,0 @@ -#!/usr/bin/env python -# Copyright 2015 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import argparse -import atexit -import errno -import hashlib -import json -import logging -import multiprocessing -import os -import platform -import random -import re -import signal -import socket -import subprocess -import sys -import tempfile -import time -import urlparse - -PACKAGES_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -SKY_ENGINE_DIR = os.path.join(PACKAGES_DIR, 'sky_engine') -APK_DIR = os.path.join(os.path.realpath(SKY_ENGINE_DIR), os.pardir, 'apks') - -SKY_SERVER_PORT = 9888 -OBSERVATORY_PORT = 8181 -ADB_PATH = 'adb' -APK_NAME = 'SkyShell.apk' -ANDROID_PACKAGE = 'org.domokit.sky.shell' -ANDROID_COMPONENT = '%s/%s.SkyActivity' % (ANDROID_PACKAGE, ANDROID_PACKAGE) -SHA1_PATH = '/sdcard/%s/%s.sha1' % (ANDROID_PACKAGE, APK_NAME) - -SKY_SHELL_APP_ID = 'com.google.SkyShell' -IOS_APP_NAME = 'SkyShell.app' - -# FIXME: Do we need to look in $DART_SDK? -DART_PATH = 'dart' -PUB_PATH = 'pub' - -PID_FILE_PATH = '/tmp/sky_tool.pids' -PID_FILE_KEYS = frozenset([ - 'remote_sky_server_port', - 'sky_server_pid', - 'sky_server_port', - 'sky_server_root', -]) - -IOS_SIM_PATH = [ - os.path.join('/Applications', 'iOS Simulator.app', 'Contents', 'MacOS', 'iOS Simulator') -] - -XCRUN_PATH = [ - os.path.join('/usr', 'bin', 'env'), - 'xcrun', -] - -SIMCTL_PATH = XCRUN_PATH + [ - 'simctl', -] - -PLIST_BUDDY_PATH = XCRUN_PATH + [ - 'PlistBuddy', -] - - -def _port_in_use(port): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - return sock.connect_ex(('localhost', port)) == 0 - - -def _start_http_server(port, root): - server_command = [ - PUB_PATH, 'run', 'sky_tools:sky_server', str(port), - ] - logging.info(' '.join(server_command)) - return subprocess.Popen(server_command, cwd=root).pid - - -# This 'strict dictionary' approach is useful for catching typos. -class Pids(object): - def __init__(self, known_keys, contents=None): - self._known_keys = known_keys - self._dict = contents if contents is not None else {} - - def __len__(self): - return len(self._dict) - - def get(self, key, default=None): - assert key in self._known_keys, '%s not in known_keys' % key - return self._dict.get(key, default) - - def __getitem__(self, key): - assert key in self._known_keys, '%s not in known_keys' % key - return self._dict[key] - - def __setitem__(self, key, value): - assert key in self._known_keys, '%s not in known_keys' % key - self._dict[key] = value - - def __delitem__(self, key): - assert key in self._known_keys, '%s not in known_keys' % key - del self._dict[key] - - def __iter__(self): - return iter(self._dict) - - def __contains__(self, key): - assert key in self._known_keys, '%s not in allowed_keys' % key - return key in self._dict - - def clear(self): - self._dict = {} - - def pop(self, key, default=None): - assert key in self._known_keys, '%s not in known_keys' % key - return self._dict.pop(key, default) - - @classmethod - def read_from(cls, path, known_keys): - contents = {} - try: - with open(path, 'r') as pid_file: - contents = json.load(pid_file) - except: - if os.path.exists(path): - logging.warn('Failed to read pid file: %s' % path) - return cls(known_keys, contents) - - def write_to(self, path): - # These keys are required to write a valid file. - if not self._dict.viewkeys() >= { 'sky_server_pid', 'sky_server_port' }: - return - - try: - with open(path, 'w') as pid_file: - json.dump(self._dict, pid_file, indent=2, sort_keys=True) - except: - logging.warn('Failed to write pid file: %s' % path) - - -def _url_for_path(port, root, path): - relative_path = os.path.relpath(path, root) - return 'http://localhost:%s/%s' % (port, relative_path) - - -class SkyLogs(object): - def add_subparser(self, subparsers): - logs_parser = subparsers.add_parser('logs', - help='Show logs for running Sky apps') - logs_parser.add_argument('--clear', action='store_true', dest='clear_logs', - help='Clear log history before reading from logs (currently only implemented for Android)') - logs_parser.set_defaults(func=self.run) - - def run(self, args, pids): - android_log_reader = None - ios_dev_log_reader = None - ios_sim_log_reader = None - - android = AndroidDevice() - if android.is_connected(): - android_log_reader = android.logs(args.clear_logs) - - if IOSDevice.is_connected(): - ios_dev_log_reader = IOSDevice.logs(args.clear_logs) - - if IOSSimulator.is_connected(): - ios_sim_log_reader = IOSSimulator.logs(args.clear_logs) - - if android_log_reader is not None: - try: - android_log_reader.join() - except KeyboardInterrupt: - pass - - if ios_dev_log_reader is not None: - try: - ios_dev_log_reader.join() - except KeyboardInterrupt: - pass - - if ios_sim_log_reader is not None: - try: - ios_sim_log_reader.join() - except KeyboardInterrupt: - pass - - -class InstallSky(object): - def add_subparser(self, subparsers): - install_parser = subparsers.add_parser('install', - help='install SkyShell on Android and iOS devices and simulators') - install_parser.set_defaults(func=self.run) - - def run(self, args, pids): - android = AndroidDevice() - - installed_somewhere = False - # Install on connected Android device - if android.is_connected() and args.android_build_available: - installed_somewhere = installed_somewhere or android.install_apk(android.get_apk_path(args)) - - # Install on connected iOS device - if IOSDevice.is_connected() and args.ios_build_available: - installed_somewhere = installed_somewhere or IOSDevice.install_app(IOSDevice.get_app_path(args)) - - # Install on iOS simulator if it's running - if IOSSimulator.is_booted() and args.ios_sim_build_available: - installed_somewhere = installed_somewhere or IOSSimulator.fork_install_app(IOSSimulator.get_app_path(args)) - - if installed_somewhere: - return 0 - else: - return 2 - - # TODO(iansf): get rid of need for args - def needs_install(self, args): - return AndroidDevice().needs_install(args) or IOSDevice.needs_install(args) or IOSSimulator.needs_install(args) - - -class StartSky(object): - def add_subparser(self, subparsers): - start_parser = subparsers.add_parser('start', - help='launch %s on the device' % APK_NAME) - start_parser.add_argument('--poke', action='store_true') - start_parser.add_argument('--checked', action='store_true') - start_parser.add_argument('project_or_path', nargs='?', type=str, - default='.') - start_parser.set_defaults(func=self.run) - - def run(self, args, pids): - started_sky_somewhere = False - if not args.poke: - StopSky().run(args, pids) - - # Only install if the user did not specify a poke - installer = InstallSky() - if installer.needs_install(args): - started_sky_somewhere = (installer.run(args, pids) == 0) - - project_or_path = os.path.abspath(args.project_or_path) - - if os.path.isdir(project_or_path): - sky_server_root = project_or_path - main_dart = os.path.join(project_or_path, 'lib', 'main.dart') - missing_msg = 'Missing lib/main.dart in project: %s' % project_or_path - else: - sky_server_root = os.getcwd() - main_dart = project_or_path - missing_msg = '%s does not exist.' % main_dart - - if not os.path.isfile(main_dart): - logging.error(missing_msg) - return 2 - - package_root = os.path.join(sky_server_root, 'packages') - if not os.path.isdir(package_root): - logging.error('%s is not a valid packages path.' % package_root) - return 2 - - android = AndroidDevice() - # TODO(iansf): fix this so that we don't have to pass sky_server_root, main_dart, pid, and args. - started_sky_on_android = android.setup_servers(sky_server_root, main_dart, pids, args) - - if started_sky_somewhere or started_sky_on_android: - return 0 - else: - return 2 - - -class StopSky(object): - def add_subparser(self, subparsers): - stop_parser = subparsers.add_parser('stop', - help=('kill all running SkyShell.apk processes')) - stop_parser.set_defaults(func=self.run) - - def _run(self, args): - with open('/dev/null', 'w') as dev_null: - logging.info(' '.join(args)) - subprocess.call(args, stdout=dev_null, stderr=dev_null) - - def run(self, args, pids): - if 'remote_sky_server_port' in pids: - port_string = 'tcp:%s' % pids['remote_sky_server_port'] - self._run([AndroidDevice().adb_path, 'reverse', '--remove', port_string]) - - self._run([AndroidDevice().adb_path, 'shell', 'am', 'force-stop', ANDROID_PACKAGE]) - - try: - # Because the server gets forked by dart, pids.get(['sky_server_pid']) returns an invalid pid, - # so just force things closed. - if platform.system() == 'Darwin': - cmd = ['lsof', '-i', ':%s' % SKY_SERVER_PORT, '-t'] - logging.info(' '.join(cmd)) - pid = subprocess.check_output(cmd) - - # Killing a pid with a shell command from within python is hard, - # so use a library command, but it's still nice to give the - # equivalent command when doing verbose logging. - logging.info('kill %s' % pid) - os.kill(int(pid), signal.SIGTERM) - else: - # This usage of fuser is not valid on OS X - self._run(['fuser', '-k', '%s/tcp' % SKY_SERVER_PORT]) - except subprocess.CalledProcessError as e: - pass - - pids.clear() - -class AndroidDevice(object): - # _state used in this manner gives a simple way to treat AndroidDevice - # as a singleton while easily allowing subclassing for mocks. All - # AndroidDevices created in a given session will share the same state. - _state = {} - def __init__(self): - self.__dict__ = AndroidDevice._state - self._update_paths() - - # Checking for lollipop only needs to be done if we are starting an - # app, but it has an important side effect, which is to discard any - # progress messages if the adb server is restarted. - self._check_for_adb() - self._check_for_lollipop_or_later() - - def _update_paths(self): - if 'adb_path' in self.__dict__: - return - if 'ANDROID_HOME' in os.environ: - android_home_dir = os.environ['ANDROID_HOME'] - adb_location1 = os.path.join(android_home_dir, 'sdk', 'platform-tools', 'adb') - adb_location2 = os.path.join(android_home_dir, 'platform-tools', 'adb') - if os.path.exists(adb_location1): - self.adb_path = adb_location1 - elif os.path.exists(adb_location2): - self.adb_path = adb_location2 - else: - logging.warning('"adb" not found at\n "%s" or\n "%s"\nusing default path "%s"' % (adb_location1, adb_location2, ADB_PATH)) - self.adb_path = ADB_PATH - else: - self.adb_path = ADB_PATH - - def _is_valid_adb_version(self, adb_version): - # Sample output: 'Android Debug Bridge version 1.0.31' - version_fields = re.search('(\d+)\.(\d+)\.(\d+)', adb_version) - if version_fields: - major_version = int(version_fields.group(1)) - minor_version = int(version_fields.group(2)) - patch_version = int(version_fields.group(3)) - if major_version > 1: - return True - if major_version == 1 and minor_version > 0: - return True - if major_version == 1 and minor_version == 0 and patch_version >= 32: - return True - return False - else: - logging.warn('Unrecognized adb version string. Skipping version check.') - return True - - def _check_for_adb(self): - if 'has_valid_adb' in self.__dict__: - return - try: - cmd = [self.adb_path, 'version'] - logging.info(' '.join(cmd)) - adb_version = subprocess.check_output(cmd) - if self._is_valid_adb_version(adb_version): - self.has_valid_adb = True - return - - cmd = ['which', ADB_PATH] - logging.info(' '.join(cmd)) - adb_path = subprocess.check_output(cmd).rstrip() - logging.error('"%s" is too old. Need 1.0.32 or later. ' - 'Try setting ANDROID_HOME to use Android builds. Android builds are unavailable.' % adb_path) - self.has_valid_adb = False - except OSError: - logging.warning('"adb" (from the Android SDK) not in $PATH, Android builds are unavailable.') - self.has_valid_adb = False - - def _check_for_lollipop_or_later(self): - if 'has_valid_android' in self.__dict__: - return - try: - # If the server is automatically restarted, then we get irrelevant - # output lines like this, which we want to ignore: - # adb server is out of date. killing.. - # * daemon started successfully * - cmd = [self.adb_path, 'start-server'] - logging.info(' '.join(cmd)) - subprocess.call(cmd) - - cmd = [self.adb_path, 'shell', 'getprop', 'ro.build.version.sdk'] - logging.info(' '.join(cmd)) - sdk_version = subprocess.check_output(cmd).rstrip() - # Sample output: '22' - if not sdk_version.isdigit(): - logging.error('Unexpected response from getprop: "%s".' % sdk_version) - self.has_valid_android = False - return - - if int(sdk_version) < 22: - logging.error('Version "%s" of the Android SDK is too old. ' - 'Need Lollipop (22) or later. ' % sdk_version) - self.has_valid_android = False - return - except subprocess.CalledProcessError as e: - # adb printed the error, so we print nothing. - self.has_valid_android = False - return - self.has_valid_android = True - - def is_package_installed(self, package_name): - if not self.is_connected(): - return False - pm_path_cmd = [self.adb_path, 'shell', 'pm', 'path', package_name] - logging.info(' '.join(pm_path_cmd)) - return subprocess.check_output(pm_path_cmd).strip() != '' - - def get_device_apk_sha1(self, apk_path): - # We might need to install a new APK, so check SHA1 - cmd = [self.adb_path, 'shell', 'cat', SHA1_PATH] - logging.info(' '.join(cmd)) - return subprocess.check_output(cmd) - - def get_source_sha1(self, apk_path): - return hashlib.sha1(open(apk_path, 'rb').read()).hexdigest() - - # TODO(iansf): get rid of need for args - def get_apk_path(self, args): - if args.android_build_available and args.use_release: - return os.path.join(os.path.normpath(args.sky_src_path), args.android_release_build_path, 'apks', APK_NAME) - elif args.android_build_available and args.local_build: - return os.path.join(os.path.normpath(args.sky_src_path), args.android_debug_build_path, 'apks', APK_NAME) - else: - return os.path.join(APK_DIR, APK_NAME) - - def is_connected(self): - return self.has_valid_android - - def needs_install(self, args): - apk_path = self.get_apk_path(args) - - if not self.is_package_installed(ANDROID_PACKAGE): - logging.info('%s is not on the device. Installing now...' % APK_NAME) - return True - elif self.get_device_apk_sha1(apk_path) != self.get_source_sha1(apk_path): - logging.info('%s on the device is out of date. Installing now...' % APK_NAME) - return True - return False - - def install_apk(self, apk_path): - if not os.path.exists(apk_path): - logging.error('"%s" does not exist.' % apk_path) - return False - - cmd = [self.adb_path, 'install', '-r', apk_path] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - # record the SHA1 of the APK we just pushed - with tempfile.NamedTemporaryFile() as fp: - fp.write(self.get_source_sha1(apk_path)) - fp.seek(0) - cmd = [self.adb_path, 'push', fp.name, SHA1_PATH] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - - return True - - - # TODO(iansf): refactor setup_servers - def setup_servers(self, sky_server_root, main_dart, pids, args): - if not self.is_connected(): - return False - - # Set up port forwarding for observatory - observatory_port_string = 'tcp:%s' % OBSERVATORY_PORT - cmd = [ - self.adb_path, - 'forward', - observatory_port_string, - observatory_port_string - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - - sky_server_port = SKY_SERVER_PORT - pids['sky_server_port'] = sky_server_port - if _port_in_use(sky_server_port): - logging.info(('Port %s already in use. ' - ' Not starting server for %s') % (sky_server_port, sky_server_root)) - else: - sky_server_pid = _start_http_server(sky_server_port, sky_server_root) - pids['sky_server_pid'] = sky_server_pid - pids['sky_server_root'] = sky_server_root - - port_string = 'tcp:%s' % sky_server_port - cmd = [ - self.adb_path, - 'reverse', - port_string, - port_string - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - pids['remote_sky_server_port'] = sky_server_port - - # The load happens on the remote device, use the remote port. - url = _url_for_path(pids['remote_sky_server_port'], sky_server_root, - main_dart) - if args.poke: - url += '?rand=%s' % random.random() - - cmd = [ - self.adb_path, 'shell', - 'am', 'start', - '-a', 'android.intent.action.VIEW', - '-d', url, - ] - - if args.checked: - cmd += ['--ez', 'enable-checked-mode', 'true'] - - cmd += [ANDROID_COMPONENT] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - - return True - - def logs(self, clear=False): - def do_logs(): - if clear: - cmd = [ - self.adb_path, - 'logcat', - '-c' - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - - cmd = [ - self.adb_path, - 'logcat', - '-v', - 'tag', # Only log the tag and the message - '-s', - 'sky', - 'chromium', - ] - logging.info(' '.join(cmd)) - log_process = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE) - while True: - try: - log_line = log_process.stdout.readline() - if log_line == '': - if log_process.poll() != None: - logging.error('The Android logging process has quit unexpectedly. Please call the "logs" command again.') - break - sys.stdout.write('ANDROID: ' + log_line) - sys.stdout.flush() - except KeyboardInterrupt: - break - log_reader = multiprocessing.Process(target=do_logs) - log_reader.daemon = True - log_reader.start() - return log_reader - - -class IOSDevice(object): - _has_ios_deploy = None - @classmethod - def has_ios_deploy(cls): - if cls._has_ios_deploy is not None: - return cls._has_ios_deploy - try: - cmd = [ - 'which', - 'ios-deploy' - ] - logging.info(' '.join(cmd)) - out = subprocess.check_output(cmd) - match = re.search(r'ios-deploy', out) - cls._has_ios_deploy = match is not None - except subprocess.CalledProcessError: - cls._has_ios_deploy = False - return cls._has_ios_deploy - - _is_connected = False - @classmethod - def is_connected(cls): - if not cls.has_ios_deploy(): - return False - if cls._is_connected: - return True - try: - cmd = [ - 'ios-deploy', - '--detect', - '--timeout', - '1' - ] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - cls._is_connected = True - except subprocess.CalledProcessError: - cls._is_connected = False - return cls._is_connected - - @classmethod - def get_app_path(cls, args): - if args.use_release: - return os.path.join(args.sky_src_path, args.ios_release_build_path, IOS_APP_NAME) - else: - return os.path.join(args.sky_src_path, args.ios_debug_build_path, IOS_APP_NAME) - - @classmethod - def needs_install(cls, args): - return cls.is_connected() - - @classmethod - def install_app(cls, ios_app_path): - if not cls.has_ios_deploy(): - return False - try: - cmd = [ - 'ios-deploy', - '--justlaunch', - '--timeout', - '10', # Smaller timeouts cause it to exit before having launched the app - '--bundle', - ios_app_path - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - except subprocess.CalledProcessError: - return False - return True - - @classmethod - def copy_file(cls, bundle_id, local_path, device_path): - if not cls.has_ios_deploy(): - return - try: - cmd = [ - 'ios-deploy', - '-t', - '1', - '--bundle_id', - bundle_id, - '--upload', - local_path, - '--to', - device_path - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - except subprocess.CalledProcessError: - pass - - @classmethod - def logs(cls, clear=False): - try: - cmd = [ - 'which', - 'idevicesyslog' - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - except subprocess.CalledProcessError: - logging.error('"log" command only works with iOS devices if you have installed idevicesyslog. Run "brew install libimobiledevice" to install it with homebrew.') - return None - - def do_logs(): - cmd = [ - 'idevicesyslog', - ] - logging.info(' '.join(cmd)) - log_process = subprocess.Popen(cmd, stdout=subprocess.PIPE) - while True: - try: - log_line = log_process.stdout.readline() - if log_line == '': - if log_process.poll() != None: - logging.error('The iOS logging process has quit unexpectedly. Please call the "logs" command again.') - break - if re.match(r'.*SkyShell.*', log_line) is not None: - sys.stdout.write('IOS DEV: ' + log_line) - sys.stdout.flush() - except KeyboardInterrupt: - break - log_reader = multiprocessing.Process(target=do_logs) - log_reader.daemon = True - log_reader.start() - return log_reader - - -class IOSSimulator(object): - @classmethod - def is_booted(cls): - if platform.system() != 'Darwin': - return False - return cls.get_simulator_device_id() is not None - - @classmethod - def is_connected(cls): - return cls.is_booted() - - _device_id = None - @classmethod - def get_simulator_device_id(cls): - if cls._device_id is not None: - return cls._device_id - cmd = SIMCTL_PATH + [ - 'list', - 'devices', - ] - logging.info(' '.join(cmd)) - out = subprocess.check_output(cmd) - match = re.search(r'[^\(]+\(([^\)]+)\) \(Booted\)', out) - if match is not None and match.group(1) is not None: - cls._device_id = match.group(1) - return cls._device_id - else: - logging.info('No running simulators found') - # TODO: Maybe start the simulator? - return None - if err is not None: - print(err) - exit(-1) - - _simulator_path = None - @classmethod - def get_simulator_path(cls): - if cls._simulator_path is not None: - return cls._simulator_path - home_dir = os.path.expanduser('~') - device_id = cls.get_simulator_device_id() - if device_id is None: - # TODO: Maybe start the simulator? - return None - cls._simulator_path = os.path.join(home_dir, 'Library', 'Developer', 'CoreSimulator', 'Devices', device_id) - return cls._simulator_path - - _simulator_app_id = None - @classmethod - def get_simulator_app_id(cls): - if cls._simulator_app_id is not None: - return cls._simulator_app_id - simulator_path = cls.get_simulator_path() - cmd = [ - 'find', - os.path.join(simulator_path, 'data', 'Containers', 'Data', 'Application'), - '-name', - SKY_SHELL_APP_ID - ] - logging.info(' '.join(cmd)) - out = subprocess.check_output(cmd) - match = re.search(r'Data\/Application\/([^\/]+)\/Documents\/' + SKY_SHELL_APP_ID, out) - if match is not None and match.group(1) is not None: - cls._simulator_app_id = match.group(1) - return cls._simulator_app_id - else: - logging.warning(SKY_SHELL_APP_ID + ' is not installed on the simulator') - # TODO: Maybe install the app? - return None - if err is not None: - print(err) - exit(-1) - - _simulator_app_documents_dir = None - @classmethod - def get_simulator_app_documents_dir(cls): - if cls._simulator_app_documents_dir is not None: - return cls._simulator_app_documents_dir - if not cls.is_booted(): - return None - simulator_path = cls.get_simulator_path() - simulator_app_id = cls.get_simulator_app_id() - if simulator_app_id is None: - return None - cls._simulator_app_documents_dir = os.path.join(simulator_path, 'data', 'Containers', 'Data', 'Application', simulator_app_id, 'Documents') - return cls._simulator_app_documents_dir - - @classmethod - def get_app_path(cls, args): - if args.use_release: - return os.path.join(args.sky_src_path, args.ios_sim_release_build_path, IOS_APP_NAME) - else: - return os.path.join(args.sky_src_path, args.ios_sim_debug_build_path, IOS_APP_NAME) - - @classmethod - def needs_install(cls, args): - return cls.is_booted() - - @classmethod - def logs(cls, clear=False): - def do_logs(): - cmd = [ - 'tail', - '-f', - os.path.expanduser('~/Library/Logs/CoreSimulator/' + cls.get_simulator_device_id() + '/system.log'), - ] - logging.info(' '.join(cmd)) - log_process = subprocess.Popen(cmd, stdout=subprocess.PIPE) - while True: - try: - log_line = log_process.stdout.readline() - if log_line == '': - if log_process.poll() != None: - logging.error('The iOS Simulator logging process has quit unexpectedly. Please call the "logs" command again.') - break - if re.match(r'.*SkyShell.*', log_line) is not None: - sys.stdout.write('IOS SIM: ' + log_line) - sys.stdout.flush() - except KeyboardInterrupt: - break - log_reader = multiprocessing.Process(target=do_logs) - log_reader.daemon = True - log_reader.start() - return log_reader - - @classmethod - def fork_install_app(cls, ios_app_path): - try: - cmd = [ - os.path.abspath(__file__), - 'ios_sim', - '-p', - ios_app_path, - 'launch' - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - return True - except subprocess.CalledProcessError: - return False - - def _process_args(self, args): - if args.ios_sim_build_path is None: - if args.ios_sim_build_available: - if args.use_release: - args.ios_sim_build_path = os.path.join(args.sky_src_path, args.ios_sim_release_build_path, IOS_APP_NAME) - elif args.local_build: - args.ios_sim_build_path = os.path.join(args.sky_src_path, args.ios_sim_debug_build_path, IOS_APP_NAME) - if args.ios_sim_build_path is None: - logging.error('ios_sim commands require a valid -p argument if not using the --release or --local-build global arguments') - sys.exit(2) - - def get_application_identifier(self, path): - cmd = PLIST_BUDDY_PATH + [ - '-c', - 'Print CFBundleIdentifier', - os.path.join(path, 'Info.plist') - ] - logging.info(' '.join(cmd)) - identifier = subprocess.check_output(cmd) - return identifier.strip() - - def is_simulator_booted(self): - cmd = SIMCTL_PATH + [ 'list', 'devices' ] - logging.info(' '.join(cmd)) - devices = subprocess.check_output(cmd).strip().split('\n') - for device in devices: - if re.search(r'\(Booted\)', device): - return True - return False - - # Launch whatever simulator the user last used, rather than try to guess which of their simulators they might want to use - def boot_simulator(self, args, pids): - # Guarantee that the args get processed, since all of the commands funnel through here. - self._process_args(args) - if self.is_simulator_booted(): - return - # Use Popen here because launching the simulator from the command line in this manner doesn't return, so we can't check the result. - if args.ios_sim_path: - logging.info(args.ios_sim_path) - subprocess.Popen(args.ios_sim_path) - else: - logging.info(IOS_SIM_PATH) - subprocess.Popen(IOS_SIM_PATH) - while not self.is_simulator_booted(): - print('Waiting for iOS Simulator to boot...') - time.sleep(0.5) - - def install_app(self, args, pids): - self.boot_simulator(args, pids) - cmd = SIMCTL_PATH + [ - 'install', - 'booted', - args.ios_sim_build_path, - ] - logging.info(' '.join(cmd)) - return subprocess.check_call(cmd) - - def install_launch_and_wait(self, args, pids, wait): - res = self.install_app(args, pids) - if res != 0: - return res - identifier = self.get_application_identifier(args.ios_sim_build_path) - launch_args = SIMCTL_PATH + ['launch'] - if wait: - launch_args += [ '-w' ] - launch_args += [ - 'booted', - identifier, - '-target', - args.target, - '-server', - args.server - ] - logging.info(' '.join(launch_args)) - return subprocess.check_output(launch_args).strip() - - def launch_app(self, args, pids): - self.install_launch_and_wait(args, pids, False) - - def debug_app(self, args, pids): - launch_res = self.install_launch_and_wait(args, pids, True) - launch_pid = re.search('.*: (\d+)', launch_res).group(1) - cmd = XCRUN_PATH + [ - 'lldb', - # TODO(iansf): get this working again - # '-s', - # os.path.join(os.path.dirname(__file__), 'lldb_start_commands.txt'), - '-p', - launch_pid, - ] - logging.info(' '.join(cmd)) - return subprocess.call(cmd) - - def add_subparser(self, subparsers): - simulator_parser = subparsers.add_parser('ios_sim', - help='A script that launches an' - ' application in the simulator and attaches' - ' the debugger to it.') - simulator_parser.add_argument('-p', dest='ios_sim_build_path', required=False, - help='Path to the simulator app build. Defaults to values specified by ' - 'the sky_src_path and ios_sim_[debug|release]_build_path parameters, ' - 'which are normally specified by using the local-build or release ' - 'parameters. Not normally required.') - simulator_parser.add_argument('-t', dest='target', required=False, - default='examples/demo_launcher/lib/main.dart', - help='Sky server-relative path to the Sky app to run. Not normally required.') - simulator_parser.add_argument('-s', dest='server', required=False, - default='localhost:8080', - help='Sky server address. Not normally required.') - simulator_parser.add_argument('--ios_sim_path', dest='ios_sim_path', - help='Path to your iOS Simulator executable. ' - 'Not normally required.') - - subparsers = simulator_parser.add_subparsers() - install_parser = subparsers.add_parser('install', help='Install app') - install_parser.set_defaults(func=self.install_app) - launch_parser = subparsers.add_parser('launch', help='Launch app. Automatically installs.') - launch_parser.set_defaults(func=self.launch_app) - debug_parser = subparsers.add_parser('debug', help='Debug app. Automatically installs and launches.') - debug_parser.set_defaults(func=self.debug_app) - - -class StartListening(object): - def __init__(self): - self.watch_cmd = None - - def add_subparser(self, subparsers): - listen_parser = subparsers.add_parser('listen', - help=('Listen for changes to files and reload the running app on all connected devices')) - listen_parser.set_defaults(func=self.run) - - def watch_dir(self, directory): - if self.watch_cmd is None: - name = platform.system() - if name == 'Linux': - try: - cmd = [ - 'which', - 'inotifywait' - ] - logging.info(' '.join(cmd)) - out = subprocess.check_output(cmd) - except subprocess.CalledProcessError: - logging.error('"listen" command is only useful if you have installed inotifywait on Linux. Run "apt-get install inotify-tools" or equivalent to install it.') - return False - - self.watch_cmd = [ - 'inotifywait', - '-r', - '-e', - 'modify,close_write,move,create,delete', # Only listen for events that matter, to avoid triggering constantly from the editor watching files - directory - ] - elif name == 'Darwin': - try: - cmd = [ - 'which', - 'fswatch' - ] - logging.info(' '.join(cmd)) - out = subprocess.check_output(cmd) - except subprocess.CalledProcessError: - logging.error('"listen" command is only useful if you have installed fswatch on Mac. Run "brew install fswatch" to install it with homebrew.') - return False - - self.watch_cmd = [ - 'fswatch', - '-r', - '-v', - '-1', - directory - ] - else: - logging.error('"listen" command is only available on Mac and Linux.') - return False - - logging.info(' '.join(self.watch_cmd)) - subprocess.check_call(self.watch_cmd) - return True - - def run(self, args, pids): - if args.use_release: - logging.info('Note that the listen command is not compatible with the ' - 'release flag for iOS and iOS simulator builds. If you have ' - 'installed iOS release builds, your Sky app will fail to ' - 'reload while using listen.') - tempdir = tempfile.mkdtemp() - currdir = os.getcwd() - while True: - logging.info('Updating running Sky apps...') - - device = AndroidDevice() - if device.is_connected(): - # Restart the app on Android. Android does not currently restart using skyx files. - cmd = [ - sys.executable, - os.path.abspath(__file__), - 'start', - '--poke' - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - - if args.local_build: - # Currently sending to iOS only works if you are building Sky locally - # since we aren't shipping the sky_snapshot binary yet. - - # Check if we can make a snapshot - sky_snapshot_path = None - if args.ios_sim_build_available: - sky_snapshot_path = os.path.join(args.sky_src_path, args.ios_sim_debug_build_path, 'clang_x64', 'sky_snapshot') - elif args.ios_build_available: - sky_snapshot_path = os.path.join(args.sky_src_path, args.ios_debug_build_path, 'clang_x64', 'sky_snapshot') - - if sky_snapshot_path is not None: - # If we can make a snapshot, do so and then send it to running iOS instances - cmd = [ - sky_snapshot_path, - '--package-root=packages', - '--snapshot=' + os.path.join(tempdir, 'snapshot_blob.bin'), - os.path.join('lib', 'main.dart') - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - - os.chdir(tempdir) - # Turn the snapshot into an app.skyx file - cmd = [ - 'zip', - '-r', - 'app.skyx', - 'snapshot_blob.bin', - 'action', - 'content', - 'navigation' - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - os.chdir(currdir) - - # Copy the app.skyx to the running simulator - simulator_app_documents_dir = IOSSimulator.get_simulator_app_documents_dir() - if simulator_app_documents_dir is not None: - cmd = [ - 'cp', - os.path.join(tempdir, 'app.skyx'), - simulator_app_documents_dir - ] - logging.info(' '.join(cmd)) - subprocess.check_call(cmd) - - # Copy the app.skyx to the attached iOS device - if IOSDevice.is_connected(): - IOSDevice.copy_file(SKY_SHELL_APP_ID, os.path.join(tempdir, 'app.skyx'), 'Documents/app.skyx') - - # Watch filesystem for changes - if not self.watch_dir(currdir): - return - - -class StartTracing(object): - def add_subparser(self, subparsers): - start_tracing_parser = subparsers.add_parser('start_tracing', - help=('start tracing a running sky instance')) - start_tracing_parser.set_defaults(func=self.run) - - def run(self, args, pids): - cmd = [ - ADB_PATH, - 'shell', - 'am', - 'broadcast', - '-a', - 'org.domokit.sky.shell.TRACING_START' - ] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - - -TRACE_COMPLETE_REGEXP = re.compile('Trace complete') -TRACE_FILE_REGEXP = re.compile(r'Saving trace to (?P\S+)') - - -class StopTracing(object): - def add_subparser(self, subparsers): - stop_tracing_parser = subparsers.add_parser('stop_tracing', - help=('stop tracing a running sky instance')) - stop_tracing_parser.set_defaults(func=self.run) - - def run(self, args, pids): - cmd = [ADB_PATH, 'logcat', '-c'] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - - cmd = [ - ADB_PATH, - 'shell', - 'am', - 'broadcast', - '-a', - 'org.domokit.sky.shell.TRACING_STOP' - ] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - - device_path = None - is_complete = False - while not is_complete: - time.sleep(0.2) - cmd = [ADB_PATH, 'logcat', '-d'] - logging.info(' '.join(cmd)) - log = subprocess.check_output(cmd) - if device_path is None: - result = TRACE_FILE_REGEXP.search(log) - if result: - device_path = result.group('path') - is_complete = TRACE_COMPLETE_REGEXP.search(log) is not None - - logging.info('Downloading trace %s ...' % os.path.basename(device_path)) - - if device_path: - cmd = [ADB_PATH, 'root'] - logging.info(' '.join(cmd)) - output = subprocess.check_output(cmd) - match = re.match(r'.*cannot run as root.*', output) - if match is not None: - logging.error('Unable to download trace file %s\n' - 'You need to be able to run adb as root ' - 'on your android device' % device_path) - return 2 - - cmd = [ADB_PATH, 'pull', device_path] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - - cmd = [ADB_PATH, 'shell', 'rm', device_path] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd) - - -class SkyShellRunner(object): - def _check_for_dart(self): - try: - cmd = [DART_PATH, '--version'] - logging.info(' '.join(cmd)) - subprocess.check_output(cmd, stderr=subprocess.STDOUT) - except OSError: - logging.error('"dart" (from the Dart SDK) not in $PATH, cannot continue.') - return False - return True - - def main(self): - logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) - real_path = os.path.realpath(__file__) - if re.match(r'.*src\/sky\/packages\/sky\/', real_path) is not None: - logging.warning('Using overridden sky package located at ' + os.path.dirname(os.path.dirname(real_path))) - - parser = argparse.ArgumentParser(description='Sky App Runner') - parser.add_argument('--verbose', dest='verbose', action='store_true', - help='Noisy logging, including all shell commands executed') - parser.add_argument('--release', dest='use_release', action='store_true', - help='Set this if you are building Sky locally and want to use the release build products. ' - 'When set, attempts to automaticaly determine sky-src-path if sky-src-path is ' - 'not set. Using this flag automatically turns on local-build as well, so you do not ' - 'need to specify both. Note that --release is not compatible with the listen command ' - 'on iOS devices and simulators. Not normally required.') - parser.add_argument('--local-build', dest='local_build', action='store_true', - help='Set this if you are building Sky locally and want to use those build products. ' - 'When set, attempts to automaticaly determine sky-src-path if sky-src-path is ' - 'not set. Not normally required.') - parser.add_argument('--sky-src-path', dest='sky_src_path', - help='Path to your Sky src directory, if you are building Sky locally. ' - 'Ignored if local-build is not set. Not normally required.') - parser.add_argument('--android-debug-build-path', dest='android_debug_build_path', - help='Path to your Android Debug out directory, if you are building Sky locally. ' - 'This path is relative to sky-src-path. Not normally required.', - default='out/android_Debug/') - parser.add_argument('--android-release-build-path', dest='android_release_build_path', - help='Path to your Android Release out directory, if you are building Sky locally. ' - 'This path is relative to sky-src-path. Not normally required.', - default='out/android_Release/') - parser.add_argument('--ios-debug-build-path', dest='ios_debug_build_path', - help='Path to your iOS Debug out directory, if you are building Sky locally. ' - 'This path is relative to sky-src-path. Not normally required.', - default='out/ios_Debug/') - parser.add_argument('--ios-release-build-path', dest='ios_release_build_path', - help='Path to your iOS Release out directory, if you are building Sky locally. ' - 'This path is relative to sky-src-path. Not normally required.', - default='out/ios_Release/') - parser.add_argument('--ios-sim-debug-build-path', dest='ios_sim_debug_build_path', - help='Path to your iOS Simulator Debug out directory, if you are building Sky locally. ' - 'This path is relative to sky-src-path. Not normally required.', - default='out/ios_sim_Debug/') - parser.add_argument('--ios-sim-release-build-path', dest='ios_sim_release_build_path', - help='Path to your iOS Simulator Release out directory, if you are building Sky locally. ' - 'This path is relative to sky-src-path. Not normally required.', - default='out/ios_sim_Release/') - - subparsers = parser.add_subparsers(help='sub-command help') - - for command in [SkyLogs(), InstallSky(), StartSky(), StopSky(), StartListening(), StartTracing(), StopTracing(), IOSSimulator()]: - command.add_subparser(subparsers) - - args = parser.parse_args() - if args.verbose: - logging.getLogger().setLevel(logging.INFO) - - if args.use_release: - args.local_build = True - - # TODO(iansf): args is unfortunately just a global context variable. For now, add some additional context to it. - args.android_build_available = False - args.ios_build_available = False - args.ios_sim_build_available = False - - # Also make sure that args is consistent with machine state for local builds - if args.local_build and args.sky_src_path is None: - real_flutter_path = os.path.realpath(os.path.join(PACKAGES_DIR, 'flutter')) - match = re.match(r'pub.dartlang.org/flutter', real_flutter_path) - if match is not None: - args.local_build = False - else: - sky_src_path = os.path.dirname( - os.path.dirname( - os.path.dirname( - os.path.dirname(real_flutter_path)))) - if sky_src_path == '/' or sky_src_path == '': - args.local_build = False - else: - args.sky_src_path = sky_src_path - - if not args.local_build: - logging.warning('Unable to detect a valid sky install. Disabling local-build flag.\n' - 'The recommended way to use a local build of Sky is to add the following\n' - 'to your pubspec.yaml file and then run pub get again:\n' - 'dependency_overrides:\n' - ' material_design_icons:\n' - ' path: /path/to/flutter/engine/src/sky/packages/material_design_icons\n' - ' flutter:\n' - ' path: /path/to/flutter/engine/src/sky/packages/sky\n') - if args.local_build: - if not os.path.isdir(args.sky_src_path): - logging.warning('The selected sky-src-path (' + args.sky_src_path + ') does not exist.' - 'Disabling local-build flag.') - args.local_build = False - if args.local_build and args.use_release: - if os.path.isdir(os.path.join(args.sky_src_path, args.android_release_build_path)): - args.android_build_available = True - if os.path.isdir(os.path.join(args.sky_src_path, args.ios_release_build_path)): - args.ios_build_available = True - if os.path.isdir(os.path.join(args.sky_src_path, args.ios_sim_release_build_path)): - args.ios_sim_build_available = True - elif args.local_build: - if os.path.isdir(os.path.join(args.sky_src_path, args.android_debug_build_path)): - args.android_build_available = True - if os.path.isdir(os.path.join(args.sky_src_path, args.ios_debug_build_path)): - args.ios_build_available = True - if os.path.isdir(os.path.join(args.sky_src_path, args.ios_sim_debug_build_path)): - args.ios_sim_build_available = True - else: - if os.path.isdir(APK_DIR): - args.android_build_available = True - - if not self._check_for_dart(): - sys.exit(2) - - pids = Pids.read_from(PID_FILE_PATH, PID_FILE_KEYS) - atexit.register(pids.write_to, PID_FILE_PATH) - exit_code = 0 - try: - exit_code = args.func(args, pids) - except subprocess.CalledProcessError as e: - # Don't print a stack trace if the adb command fails. - logging.error(e) - exit_code = 2 - sys.exit(exit_code) - - -if __name__ == '__main__': - sys.exit(SkyShellRunner().main())