// Copyright 2017 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 'dart:async'; import 'dart:convert'; import 'dart:io' as dart_io; import 'package:file/file.dart'; import 'package:file/local.dart' as io; import 'package:path/path.dart' as path; import 'utils.dart'; const String _kProvisioningConfigFileEnvironmentVariable = 'FLUTTER_DEVICELAB_XCODE_PROVISIONING_CONFIG'; const String _kTestXcconfigFileName = 'TestConfig.xcconfig'; const FileSystem _fs = io.LocalFileSystem(); /// Patches the given Xcode project adding provisioning certificates and team /// information required to build and run the project, if /// FLUTTER_DEVICELAB_XCODE_PROVISIONING_CONFIG is set. If it is not set, /// we rely on automatic signing by Xcode. Future<void> prepareProvisioningCertificates(String flutterProjectPath) async { final String certificateConfig = await _readProvisioningConfigFile(); if (certificateConfig == null) { // No cert config available, rely on automatic signing by Xcode. return; } await _patchXcconfigFilesIfNotPatched(flutterProjectPath); final File testXcconfig = _fs.file(path.join(flutterProjectPath, 'ios/Flutter/$_kTestXcconfigFileName')); await testXcconfig.writeAsString(certificateConfig); } Future<void> runPodInstallForCustomPodfile(String flutterProjectPath) async { final String iosPath = path.join(flutterProjectPath, 'ios'); exec('pod', <String>['install', '--project-directory=$iosPath']); } Future<void> _patchXcconfigFilesIfNotPatched(String flutterProjectPath) async { final List<File> xcconfigFiles = <File>[ _fs.file(path.join(flutterProjectPath, 'ios/Flutter/Flutter.xcconfig')), _fs.file(path.join(flutterProjectPath, 'ios/Flutter/Debug.xcconfig')), _fs.file(path.join(flutterProjectPath, 'ios/Flutter/Release.xcconfig')), ]; bool xcconfigFileExists = false; for (final File file in xcconfigFiles) { if (await file.exists()) { xcconfigFileExists = true; const String include = '#include "$_kTestXcconfigFileName"'; final String contents = await file.readAsString(); final bool alreadyPatched = contents.contains(include); if (!alreadyPatched) { final IOSink patchOut = file.openWrite(mode: FileMode.append); patchOut.writeln(); // in case EOF is not preceded by line break patchOut.writeln(include); await patchOut.close(); } } } if (!xcconfigFileExists) { final String candidatesFormatted = xcconfigFiles.map<String>((File f) => f.path).join(', '); throw 'Failed to locate a xcconfig file to patch with provisioning profile ' 'info. Tried: $candidatesFormatted'; } } Future<String> _readProvisioningConfigFile() async { void throwUsageError(String specificMessage) { throw ''' ================================================================================ You are attempting to build an XCode project, which requires a provisioning certificate and team information. The test framework attempted to locate an .xcconfig file whose path is defined by the environment variable $_kProvisioningConfigFileEnvironmentVariable. $specificMessage ================================================================================ '''.trim(); } if (!dart_io.Platform.environment.containsKey(_kProvisioningConfigFileEnvironmentVariable)) { print(''' $_kProvisioningConfigFileEnvironmentVariable variable is not defined in your environment. Relying on automatic signing by Xcode... '''.trim()); return null; } final String filePath = dart_io.Platform.environment[_kProvisioningConfigFileEnvironmentVariable]; final File file = _fs.file(filePath); if (!(await file.exists())) { throwUsageError(''' File not found: $filePath It is defined by environment variable $_kProvisioningConfigFileEnvironmentVariable '''.trim()); } return file.readAsString(); } void _checkExitCode(int code) { if (code != 0) { throw Exception( 'Unexpected exit code = $code!', ); } } Future<void> _execAndCheck(String executable, List<String> args) async { _checkExitCode(await exec(executable, args)); } // Measure the CPU/GPU percentage for [duration] while a Flutter app is running // on an iOS device (e.g., right after a Flutter driver test has finished, which // doesn't close the Flutter app, and the Flutter app has an indefinite // animation). The return should have a format like the following json // ``` // {"gpu_percentage":12.6,"cpu_percentage":18.15} // ``` Future<Map<String, dynamic>> measureIosCpuGpu({ Duration duration = const Duration(seconds: 10), String deviceId, }) async { await _execAndCheck('pub', <String>[ 'global', 'activate', 'gauge', '0.1.4', ]); await _execAndCheck('pub', <String>[ 'global', 'run', 'gauge', 'ioscpugpu', 'new', if (deviceId != null) ...<String>['-w', deviceId], '-l', '${duration.inMilliseconds}', ]); return json.decode(file('$cwd/result.json').readAsStringSync()); } Future<String> dylibSymbols(String pathToDylib) { return eval('nm', <String>['-g', pathToDylib]); }