Commit a02d583d authored by Matt Perry's avatar Matt Perry

Refactor service-related code to be shared between android and IOS.

The IOS code is totally untested. Need a Mac to test it.
parent 96405fb4
......@@ -253,26 +253,34 @@ class ArtifactStore {
}
/// Download a file from the given url and write it to the cache.
static Future _downloadFileToCache(Uri url, File cachedFile) async {
/// If [unzip] is true, treat the url as a zip file, and unzip it to the
/// directory given.
static Future _downloadFileToCache(Uri url, FileSystemEntity cachedFile, bool unzip) async {
if (!cachedFile.parent.existsSync())
cachedFile.parent.createSync(recursive: true);
List<int> fileBytes = await _downloadFile(url);
cachedFile.writeAsBytesSync(fileBytes, flush: true);
if (unzip) {
if (cachedFile is Directory && !cachedFile.existsSync())
cachedFile.createSync(recursive: true);
Archive archive = new ZipDecoder().decodeBytes(fileBytes);
for (ArchiveFile archiveFile in archive) {
File subFile = new File(path.join(cachedFile.path, archiveFile.name));
subFile.writeAsBytesSync(archiveFile.content, flush: true);
}
} else {
File asFile = new File(cachedFile.path);
asFile.writeAsBytesSync(fileBytes, flush: true);
}
}
/// Download the artifacts.zip archive for the given platform from GCS
/// and extract it to the local cache.
static Future _doDownloadArtifactsFromZip(String platform) async {
String url = getCloudStorageBaseUrl(platform) + 'artifacts.zip';
List<int> zipBytes = await _downloadFile(Uri.parse(url));
Archive archive = new ZipDecoder().decodeBytes(zipBytes);
Directory cacheDir = _getCacheDirForPlatform(platform);
for (ArchiveFile archiveFile in archive) {
File cacheFile = new File(path.join(cacheDir.path, archiveFile.name));
cacheFile.writeAsBytesSync(archiveFile.content, flush: true);
}
await _downloadFileToCache(Uri.parse(url), cacheDir, true);
for (Artifact artifact in knownArtifacts) {
if (artifact.platform == platform && artifact.executable) {
......@@ -334,7 +342,7 @@ class ArtifactStore {
return cachedFile.path;
}
static Future<String> getThirdPartyFile(String urlStr, String cacheSubdir) async {
static Future<String> getThirdPartyFile(String urlStr, String cacheSubdir, bool unzip) async {
Uri url = Uri.parse(urlStr);
Directory baseDir = _getBaseCacheDir();
Directory cacheDir = new Directory(path.join(
......@@ -343,7 +351,7 @@ class ArtifactStore {
path.join(cacheDir.path, url.pathSegments[url.pathSegments.length-1]));
if (!cachedFile.existsSync()) {
try {
await _downloadFileToCache(url, cachedFile);
await _downloadFileToCache(url, cachedFile, unzip);
} catch (e) {
printError('Failed to fetch third-party artifact: $url: $e');
throw new ProcessExit(2);
......
......@@ -3,11 +3,9 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import '../android/device_android.dart';
import '../application_package.dart';
......@@ -19,6 +17,7 @@ import '../build_configuration.dart';
import '../device.dart';
import '../flx.dart' as flx;
import '../runner/flutter_command.dart';
import '../services.dart';
import '../toolchain.dart';
import 'start.dart';
......@@ -27,7 +26,6 @@ const String _kDefaultOutputPath = 'build/app.apk';
const String _kDefaultResourcesPath = 'apk/res';
const String _kFlutterManifestPath = 'flutter.yaml';
const String _kPubspecYamlPath = 'pubspec.yaml';
const String _kPackagesStatusPath = '.packages';
// Alias of the key provided in the Chromium debug keystore
......@@ -144,14 +142,6 @@ class ApkKeystoreInfo {
ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
}
// TODO(mpcomplete): find a better home for this.
dynamic _loadYamlFile(String path) {
if (!FileSystemEntity.isFileSync(path))
return null;
String manifestString = new File(path).readAsStringSync();
return loadYaml(manifestString);
}
class ApkCommand extends FlutterCommand {
final String name = 'apk';
final String description = 'Build an Android APK package.';
......@@ -214,37 +204,6 @@ class ApkCommand extends FlutterCommand {
}
}
Future _findServices(_ApkComponents components) async {
if (!ArtifactStore.isPackageRootValid)
return;
dynamic manifest = _loadYamlFile(_kFlutterManifestPath);
if (manifest['services'] == null)
return;
for (String service in manifest['services']) {
String serviceRoot = '${ArtifactStore.packageRoot}/$service/apk';
dynamic serviceConfig = _loadYamlFile('$serviceRoot/config.yaml');
if (serviceConfig == null || serviceConfig['jars'] == null)
continue;
components.services.addAll(serviceConfig['services']);
for (String jar in serviceConfig['jars']) {
if (jar.startsWith("android-sdk:")) {
// Jar is something shipped in the standard android SDK.
jar = jar.replaceAll('android-sdk:', '${components.androidSdk.path}/');
components.jars.add(new File(jar));
} else if (jar.startsWith("http")) {
// Jar is a URL to download.
String cachePath = await ArtifactStore.getThirdPartyFile(jar, service);
components.jars.add(new File(cachePath));
} else {
// Assume jar is a path relative to the service's root dir.
components.jars.add(new File(path.join(serviceRoot, jar)));
}
}
}
}
Future<_ApkComponents> _findApkComponents(
BuildConfiguration config, String enginePath, String manifest, String resources
) async {
......@@ -283,7 +242,9 @@ Future<_ApkComponents> _findApkComponents(
components.debugKeystore = new File(artifactPaths[3]);
components.resources = new Directory(resources);
await _findServices(components);
await parseServiceConfigs(components.services,
jars: components.jars,
androidSdk: components.androidSdk.path);
if (!components.resources.existsSync()) {
// TODO(eseidel): This level should be higher when path is manually set.
......@@ -313,24 +274,6 @@ Future<_ApkComponents> _findApkComponents(
return components;
}
// Outputs a services.json file for the flutter engine to read. Format:
// {
// services: [
// { name: string, class: string },
// ...
// ]
// }
void _generateServicesConfig(File servicesConfig, List<Map<String, String>> servicesIn) {
List<Map<String, String>> services =
servicesIn.map((Map<String, String> service) => {
'name': service['name'],
'class': service['registration-class']
}).toList();
Map<String, dynamic> json = { 'services': services };
servicesConfig.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
}
int _buildApk(
_ApkComponents components, String flxPath, ApkKeystoreInfo keystore, String outputFile
) {
......@@ -341,8 +284,8 @@ int _buildApk(
File classesDex = new File('${tempDir.path}/classes.dex');
builder.compileClassesDex(classesDex, components.jars);
File servicesConfig = new File('${tempDir.path}/services.json');
_generateServicesConfig(servicesConfig, components.services);
File servicesConfig =
generateServiceDefinitions(tempDir.path, components.services, ios: false);
_AssetBuilder assetBuilder = new _AssetBuilder(tempDir, 'assets');
assetBuilder.add(components.icuData, 'icudtl.dat');
......@@ -438,7 +381,7 @@ Future<int> buildAndroid({
String flxPath: '',
ApkKeystoreInfo keystore
}) async {
if (!_needsRebuild(outputFile, manifest)) {
if (!force && !_needsRebuild(outputFile, manifest)) {
printTrace('APK up to date. Skipping build step.');
return 0;
}
......
......@@ -13,6 +13,7 @@ import '../base/globals.dart';
import '../base/process.dart';
import '../build_configuration.dart';
import '../device.dart';
import '../services.dart';
import '../toolchain.dart';
import 'simulator.dart';
......@@ -171,7 +172,6 @@ class IOSDevice extends Device {
// Step 1: Install the precompiled application if necessary
bool buildResult = await _buildIOSXcodeProject(app, true);
if (!buildResult) {
printError('Could not build the precompiled application for the device');
return false;
......@@ -179,13 +179,15 @@ class IOSDevice extends Device {
// Step 2: Check that the application exists at the specified path
Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphoneos', 'Runner.app'));
bool bundleExists = bundle.existsSync();
if (!bundleExists) {
printError('Could not find the built application bundle at ${bundle.path}');
return false;
}
// Step 2.5: Copy any third-party sevices to the app bundle.
await _addServicesToBundle(bundle);
// Step 3: Attempt to install the application on the device
int installationResult = await runCommandAndStreamOutput([
'/usr/bin/env',
......@@ -324,6 +326,9 @@ class IOSSimulator extends Device {
return false;
}
// Step 2.5: Copy any third-party sevices to the app bundle.
await _addServicesToBundle(bundle);
// Step 3: Install the updated bundle to the simulator
SimControl.install(id, path.absolute(bundle.path));
......@@ -536,3 +541,53 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, bool isDevice) async
return false;
}
}
bool enabled = false;
Future _addServicesToBundle(Directory bundle) async {
if (enabled) {
List<Map<String, String>> services = [];
await parseServiceConfigs(services);
await _fetchFrameworks(services);
_copyFrameworksToBundle(bundle.path, services);
generateServiceDefinitions(bundle.path, services, ios: true);
}
}
Future _fetchFrameworks(List<Map<String, String>> services) async {
for (Map<String, String> service in services) {
String frameworkUrl = service['framework'];
service['framework-path'] = await getServiceFromUrl(
frameworkUrl, service['root'], service['name'], unzip: true);
}
}
void _copyFrameworksToBundle(String destDir, List<Map<String, String>> services) {
// TODO(mpcomplete): check timestamps.
for (Map<String, String> service in services) {
String basename = path.basename(service['framework-path']);
String destPath = path.join(destDir, basename);
_copyDirRecursive(service['framework-path'], destPath);
}
}
void _copyDirRecursive(String fromPath, String toPath) {
Directory fromDir = new Directory(fromPath);
if (!fromDir.existsSync())
throw new Exception('Source directory "${fromDir.path}" does not exist');
Directory toDir = new Directory(toPath);
if (!toDir.existsSync())
toDir.createSync(recursive: true);
for (FileSystemEntity entity in fromDir.listSync()) {
String newPath = '${toDir.path}/${path.basename(entity.path)}';
if (entity is File) {
entity.copySync(newPath);
} else if (entity is Directory) {
_copyDirRecursive(entity.path, newPath);
} else {
throw new Exception('Unsupported file type for recursive copy.');
}
};
}
// 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 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import 'artifacts.dart';
const String _kFlutterManifestPath = 'flutter.yaml';
dynamic _loadYamlFile(String path) {
if (!FileSystemEntity.isFileSync(path))
return null;
String manifestString = new File(path).readAsStringSync();
return loadYaml(manifestString);
}
/// Loads all services specified in `flutter.yaml`. Parses each service config file,
/// storing metadata in [services] and the list of jar files in [jars].
Future parseServiceConfigs(
List<Map<String, String>> services, { List<File> jars, String androidSdk }
) async {
if (!ArtifactStore.isPackageRootValid)
return;
dynamic manifest = _loadYamlFile(_kFlutterManifestPath);
if (manifest['services'] == null)
return;
for (String service in manifest['services']) {
String serviceRoot = '${ArtifactStore.packageRoot}/$service';
dynamic serviceConfig = _loadYamlFile('$serviceRoot/config.yaml');
if (serviceConfig == null)
continue;
for (Map<String, String> service in serviceConfig['services']) {
services.add({
'root': serviceRoot,
'name': service['name'],
'android-class': service['android-class'],
'ios-framework': service['ios-framework']
});
}
if (jars != null) {
for (String jar in serviceConfig['jars'])
jars.add(new File(await getServiceFromUrl(jar, serviceRoot, service, androidSdk: androidSdk, unzip: false)));
}
}
}
Future<String> getServiceFromUrl(
String url, String rootDir, String serviceName, { String androidSdk, bool unzip: false }
) async {
if (url.startsWith("android-sdk:")) {
// It's something shipped in the standard android SDK.
return url.replaceAll('android-sdk:', '$androidSdk/');
} else if (url.startsWith("http")) {
// It's a regular file to download.
return await ArtifactStore.getThirdPartyFile(url, serviceName, unzip);
} else {
// Assume url is a path relative to the service's root dir.
return path.join(rootDir, url);
}
}
/// Outputs a services.json file for the flutter engine to read. Format:
/// {
/// services: [
/// { name: string, framework: string },
/// ...
/// ]
/// }
File generateServiceDefinitions(
String dir, List<Map<String, String>> servicesIn, { bool ios }
) {
assert(ios != null);
String keyOut = ios ? 'framework' : 'class';
String keyIn = ios ? 'framework-path' : 'android-class';
// TODO(mpcomplete): we should use the same filename for consistency.
String filename = ios ? 'ServiceDefinitions.json' : 'services.json';
List<Map<String, String>> services =
servicesIn.map((Map<String, String> service) => {
'name': service['name'],
keyOut: service[keyIn]
}).toList();
Map<String, dynamic> json = { 'services': services };
File servicesFile = new File(path.join(dir, filename));
servicesFile.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
return servicesFile;
}
services:
- name: gcm::GcmService
registration-class: org.domokit.gcm.RegistrationIntentService$MojoService
android-class: org.domokit.gcm.RegistrationIntentService$MojoService
jars:
- android-sdk:extras/google/google_play_services/libproject/google-play-services_lib/libs/google-play-services.jar
- android-sdk:extras/android/support/v13/android-support-v13.jar
......@@ -8,6 +8,3 @@ jars:
- android-sdk:extras/android/support/v7/mediarouter/libs/android-support-v7-mediarouter.jar
- https://storage.googleapis.com/mojo_infra/flutter/7bce54b79ef9ee57999cc8e258d664a88723d56d/android-arm/gcm/gcm_lib.dex.jar
- https://storage.googleapis.com/mojo_infra/flutter/7bce54b79ef9ee57999cc8e258d664a88723d56d/android-arm/gcm/interfaces_java.dex.jar
todo: >
Maybe we should link to a URL to download the jars. Might need different
versions per platform, etc.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment