Tooling updates for dealing with native services distributed in pub packages
diff --git a/packages/flutter_tools/lib/src/commands/apk.dart b/packages/flutter_tools/lib/src/commands/apk.dart
index c051ef0..f8230c0 100644
--- a/packages/flutter_tools/lib/src/commands/apk.dart
+++ b/packages/flutter_tools/lib/src/commands/apk.dart
@@ -271,7 +271,7 @@
builder.compileClassesDex(classesDex, components.jars);
File servicesConfig =
- generateServiceDefinitions(tempDir.path, components.services, ios: false);
+ generateServiceDefinitions(tempDir.path, components.services);
_AssetBuilder assetBuilder = new _AssetBuilder(tempDir, 'assets');
assetBuilder.add(components.icuData, 'icudtl.dat');
diff --git a/packages/flutter_tools/lib/src/ios/device_ios.dart b/packages/flutter_tools/lib/src/ios/device_ios.dart
index f705fd0..c045ec4 100644
--- a/packages/flutter_tools/lib/src/ios/device_ios.dart
+++ b/packages/flutter_tools/lib/src/ios/device_ios.dart
@@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:io';
+import 'dart:convert';
import 'package:path/path.dart' as path;
@@ -173,25 +174,22 @@
// TODO(devoncarew): Handle startPaused, debugPort.
printTrace('Building ${app.name} for $id');
- // Step 1: Install the precompiled application if necessary
+ // Step 1: Install the precompiled application if necessary.
bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: true);
if (!buildResult) {
- printError('Could not build the precompiled application for the device');
+ printError('Could not build the precompiled application for the device.');
return false;
}
- // Step 2: Check that the application exists at the specified path
+ // 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}');
+ 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
+ // Step 3: Attempt to install the application on the device.
int installationResult = await runCommandAndStreamOutput([
'/usr/bin/env',
'ios-deploy',
@@ -202,11 +200,11 @@
]);
if (installationResult != 0) {
- printError('Could not install ${bundle.path} on $id');
+ printError('Could not install ${bundle.path} on $id.');
return false;
}
- printTrace('Installation successful');
+ printTrace('Installation successful.');
return true;
}
@@ -309,33 +307,30 @@
Map<String, dynamic> platformArgs
}) async {
// TODO(chinmaygarde): Use mainPath, route.
- printTrace('Building ${app.name} for $id');
+ printTrace('Building ${app.name} for $id.');
if (clearLogs)
this.clearLogs();
- // Step 1: Build the Xcode project
+ // Step 1: Build the Xcode project.
bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: false);
if (!buildResult) {
- printError('Could not build the application for the simulator');
+ printError('Could not build the application for the simulator.');
return false;
}
- // Step 2: Assert that the Xcode project was successfully built
+ // Step 2: Assert that the Xcode project was successfully built.
Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphonesimulator', 'Runner.app'));
bool bundleExists = await bundle.exists();
if (!bundleExists) {
- printError('Could not find the built application bundle at ${bundle.path}');
+ 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: Install the updated bundle to the simulator
+ // Step 3: Install the updated bundle to the simulator.
SimControl.install(id, path.absolute(bundle.path));
- // Step 4: Prepare launch arguments
+ // Step 4: Prepare launch arguments.
List<String> args = <String>[];
if (checked)
@@ -347,7 +342,7 @@
if (debugPort != observatoryDefaultPort)
args.add("--observatory-port=$debugPort");
- // Step 5: Launch the updated application in the simulator
+ // Step 5: Launch the updated application in the simulator.
try {
SimControl.launch(id, app.id, args);
} catch (error) {
@@ -355,7 +350,7 @@
return false;
}
- printTrace('Successfully started ${app.name} on $id');
+ printTrace('Successfully started ${app.name} on $id.');
return true;
}
@@ -575,6 +570,11 @@
if (!_checkXcodeVersion())
return false;
+ // Before the build, all service definitions must be updated and the dylibs
+ // copied over to a location that is suitable for Xcodebuild to find them.
+
+ await _addServicesToBundle(new Directory(app.localPath));
+
List<String> commands = <String>[
'/usr/bin/env', 'xcrun', 'xcodebuild', '-target', 'Runner', '-configuration', 'Release'
];
@@ -593,54 +593,49 @@
}
}
-bool _servicesEnabled = false;
-
Future _addServicesToBundle(Directory bundle) async {
- if (_servicesEnabled) {
- List<Map<String, String>> services = [];
- await parseServiceConfigs(services);
- await _fetchFrameworks(services);
- _copyFrameworksToBundle(bundle.path, services);
+ List<Map<String, String>> services = [];
+ printTrace("Trying to resolve native pub services.");
- generateServiceDefinitions(bundle.path, services, ios: true);
- }
+ // Step 1: Parse the service configuration yaml files present in the service
+ // pub packages.
+ await parseServiceConfigs(services);
+ printTrace("Found ${services.length} service definition(s).");
+
+ // Step 2: Copy framework dylibs to the correct spot for xcodebuild to pick up.
+ Directory frameworksDirectory = new Directory(path.join(bundle.path, "Frameworks"));
+ await _copyServiceFrameworks(services, frameworksDirectory);
+
+ // Step 3: Copy the service definitions manifest at the correct spot for
+ // xcodebuild to pick up.
+ File manifestFile = new File(path.join(bundle.path, "ServiceDefinitions.json"));
+ _copyServiceDefinitionsManifest(services, manifestFile);
}
-Future _fetchFrameworks(List<Map<String, String>> services) async {
+Future _copyServiceFrameworks(List<Map<String, String>> services, Directory frameworksDirectory) async {
+ printTrace("Copying service frameworks to '${path.absolute(frameworksDirectory.path)}'.");
+ frameworksDirectory.createSync(recursive: true);
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.');
+ String dylibPath = await getServiceFromUrl(service['ios-framework'], service['root'], service['name']);
+ File dylib = new File(dylibPath);
+ printTrace("Copying ${dylib.path} into bundle.");
+ if (!dylib.existsSync()) {
+ printError("The service dylib '${dylib.path}' does not exist.");
+ continue;
}
- };
+ // Shell out so permissions on the dylib are preserved.
+ runCheckedSync(['/bin/cp', dylib.path, frameworksDirectory.path]);
+ }
+}
+
+void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) {
+ printTrace("Creating service definitions manifest at '${manifest.path}'");
+ List<Map<String, String>> jsonServices = services.map((Map<String, String> service) => {
+ 'name': service['name'],
+ // Since we have already moved it to the Frameworks directory. Strip away
+ // the directory and basenames.
+ 'framework': path.basenameWithoutExtension(service['ios-framework'])
+ }).toList();
+ Map<String, dynamic> json = { 'services' : jsonServices };
+ manifest.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
}
diff --git a/packages/flutter_tools/lib/src/services.dart b/packages/flutter_tools/lib/src/services.dart
index f23d68a..ca422b7 100644
--- a/packages/flutter_tools/lib/src/services.dart
+++ b/packages/flutter_tools/lib/src/services.dart
@@ -13,8 +13,10 @@
import 'base/globals.dart';
const String _kFlutterManifestPath = 'flutter.yaml';
+const String _kFlutterServicesManifestPath = 'flutter_services.yaml';
dynamic _loadYamlFile(String path) {
+ printTrace("Looking for YAML at '$path'");
if (!FileSystemEntity.isFileSync(path))
return null;
String manifestString = new File(path).readAsStringSync();
@@ -26,18 +28,24 @@
Future parseServiceConfigs(
List<Map<String, String>> services, { List<File> jars }
) async {
- if (!ArtifactStore.isPackageRootValid)
+ if (!ArtifactStore.isPackageRootValid) {
+ printTrace("Artifact store invalid while parsing service configs");
return;
+ }
dynamic manifest = _loadYamlFile(_kFlutterManifestPath);
- if (manifest == null || manifest['services'] == null)
+ if (manifest == null || manifest['services'] == null) {
+ printTrace("No services specified in the manifest");
return;
+ }
for (String service in manifest['services']) {
String serviceRoot = '${ArtifactStore.packageRoot}/$service';
- dynamic serviceConfig = _loadYamlFile('$serviceRoot/config.yaml');
- if (serviceConfig == null)
+ dynamic serviceConfig = _loadYamlFile('$serviceRoot/$_kFlutterServicesManifestPath');
+ if (serviceConfig == null) {
+ printStatus("No $_kFlutterServicesManifestPath found for service '$serviceRoot'. Skipping.");
continue;
+ }
for (Map<String, String> service in serviceConfig['services']) {
services.add({
@@ -78,22 +86,16 @@
/// ]
/// }
File generateServiceDefinitions(
- String dir, List<Map<String, String>> servicesIn, { bool ios }
+ String dir, List<Map<String, String>> servicesIn
) {
- 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]
+ 'class': service['android-class']
}).toList();
Map<String, dynamic> json = { 'services': services };
- File servicesFile = new File(path.join(dir, filename));
+ File servicesFile = new File(path.join(dir, 'services.json'));
servicesFile.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
return servicesFile;
}