Commit d38bfee6 authored by Chinmay Garde's avatar Chinmay Garde

Tooling updates for dealing with native services distributed in pub packages

parent c30b3cc6
...@@ -271,7 +271,7 @@ int _buildApk( ...@@ -271,7 +271,7 @@ int _buildApk(
builder.compileClassesDex(classesDex, components.jars); builder.compileClassesDex(classesDex, components.jars);
File servicesConfig = File servicesConfig =
generateServiceDefinitions(tempDir.path, components.services, ios: false); generateServiceDefinitions(tempDir.path, components.services);
_AssetBuilder assetBuilder = new _AssetBuilder(tempDir, 'assets'); _AssetBuilder assetBuilder = new _AssetBuilder(tempDir, 'assets');
assetBuilder.add(components.icuData, 'icudtl.dat'); assetBuilder.add(components.icuData, 'icudtl.dat');
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:convert';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
...@@ -173,25 +174,22 @@ class IOSDevice extends Device { ...@@ -173,25 +174,22 @@ class IOSDevice extends Device {
// TODO(devoncarew): Handle startPaused, debugPort. // TODO(devoncarew): Handle startPaused, debugPort.
printTrace('Building ${app.name} for $id'); 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); bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: true);
if (!buildResult) { if (!buildResult) {
printError('Could not build the precompiled application for the device'); printError('Could not build the precompiled application for the device.');
return false; 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')); Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphoneos', 'Runner.app'));
bool bundleExists = bundle.existsSync(); bool bundleExists = bundle.existsSync();
if (!bundleExists) { 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; return false;
} }
// Step 2.5: Copy any third-party sevices to the app bundle. // Step 3: Attempt to install the application on the device.
await _addServicesToBundle(bundle);
// Step 3: Attempt to install the application on the device
int installationResult = await runCommandAndStreamOutput([ int installationResult = await runCommandAndStreamOutput([
'/usr/bin/env', '/usr/bin/env',
'ios-deploy', 'ios-deploy',
...@@ -202,11 +200,11 @@ class IOSDevice extends Device { ...@@ -202,11 +200,11 @@ class IOSDevice extends Device {
]); ]);
if (installationResult != 0) { if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id'); printError('Could not install ${bundle.path} on $id.');
return false; return false;
} }
printTrace('Installation successful'); printTrace('Installation successful.');
return true; return true;
} }
...@@ -309,33 +307,30 @@ class IOSSimulator extends Device { ...@@ -309,33 +307,30 @@ class IOSSimulator extends Device {
Map<String, dynamic> platformArgs Map<String, dynamic> platformArgs
}) async { }) async {
// TODO(chinmaygarde): Use mainPath, route. // TODO(chinmaygarde): Use mainPath, route.
printTrace('Building ${app.name} for $id'); printTrace('Building ${app.name} for $id.');
if (clearLogs) if (clearLogs)
this.clearLogs(); this.clearLogs();
// Step 1: Build the Xcode project // Step 1: Build the Xcode project.
bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: false); bool buildResult = await _buildIOSXcodeProject(app, buildForDevice: false);
if (!buildResult) { if (!buildResult) {
printError('Could not build the application for the simulator'); printError('Could not build the application for the simulator.');
return false; 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')); Directory bundle = new Directory(path.join(app.localPath, 'build', 'Release-iphonesimulator', 'Runner.app'));
bool bundleExists = await bundle.exists(); bool bundleExists = await bundle.exists();
if (!bundleExists) { 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; return false;
} }
// Step 2.5: Copy any third-party sevices to the app bundle. // Step 3: Install the updated bundle to the simulator.
await _addServicesToBundle(bundle);
// Step 3: Install the updated bundle to the simulator
SimControl.install(id, path.absolute(bundle.path)); SimControl.install(id, path.absolute(bundle.path));
// Step 4: Prepare launch arguments // Step 4: Prepare launch arguments.
List<String> args = <String>[]; List<String> args = <String>[];
if (checked) if (checked)
...@@ -347,7 +342,7 @@ class IOSSimulator extends Device { ...@@ -347,7 +342,7 @@ class IOSSimulator extends Device {
if (debugPort != observatoryDefaultPort) if (debugPort != observatoryDefaultPort)
args.add("--observatory-port=$debugPort"); args.add("--observatory-port=$debugPort");
// Step 5: Launch the updated application in the simulator // Step 5: Launch the updated application in the simulator.
try { try {
SimControl.launch(id, app.id, args); SimControl.launch(id, app.id, args);
} catch (error) { } catch (error) {
...@@ -355,7 +350,7 @@ class IOSSimulator extends Device { ...@@ -355,7 +350,7 @@ class IOSSimulator extends Device {
return false; return false;
} }
printTrace('Successfully started ${app.name} on $id'); printTrace('Successfully started ${app.name} on $id.');
return true; return true;
} }
...@@ -575,6 +570,11 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice ...@@ -575,6 +570,11 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice
if (!_checkXcodeVersion()) if (!_checkXcodeVersion())
return false; 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>[ List<String> commands = <String>[
'/usr/bin/env', 'xcrun', 'xcodebuild', '-target', 'Runner', '-configuration', 'Release' '/usr/bin/env', 'xcrun', 'xcodebuild', '-target', 'Runner', '-configuration', 'Release'
]; ];
...@@ -593,54 +593,49 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice ...@@ -593,54 +593,49 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice
} }
} }
bool _servicesEnabled = false;
Future _addServicesToBundle(Directory bundle) async { Future _addServicesToBundle(Directory bundle) async {
if (_servicesEnabled) { List<Map<String, String>> services = [];
List<Map<String, String>> services = []; printTrace("Trying to resolve native pub services.");
await parseServiceConfigs(services);
await _fetchFrameworks(services); // Step 1: Parse the service configuration yaml files present in the service
_copyFrameworksToBundle(bundle.path, services); // pub packages.
await parseServiceConfigs(services);
generateServiceDefinitions(bundle.path, services, ios: true); 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) { for (Map<String, String> service in services) {
String frameworkUrl = service['framework']; String dylibPath = await getServiceFromUrl(service['ios-framework'], service['root'], service['name']);
service['framework-path'] = await getServiceFromUrl( File dylib = new File(dylibPath);
frameworkUrl, service['root'], service['name'], unzip: true printTrace("Copying ${dylib.path} into bundle.");
); if (!dylib.existsSync()) {
} printError("The service dylib '${dylib.path}' does not exist.");
} continue;
}
void _copyFrameworksToBundle(String destDir, List<Map<String, String>> services) { // Shell out so permissions on the dylib are preserved.
// TODO(mpcomplete): check timestamps. runCheckedSync(['/bin/cp', dylib.path, frameworksDirectory.path]);
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) { void _copyServiceDefinitionsManifest(List<Map<String, String>> services, File manifest) {
Directory fromDir = new Directory(fromPath); printTrace("Creating service definitions manifest at '${manifest.path}'");
if (!fromDir.existsSync()) List<Map<String, String>> jsonServices = services.map((Map<String, String> service) => {
throw new Exception('Source directory "${fromDir.path}" does not exist'); 'name': service['name'],
// Since we have already moved it to the Frameworks directory. Strip away
Directory toDir = new Directory(toPath); // the directory and basenames.
if (!toDir.existsSync()) 'framework': path.basenameWithoutExtension(service['ios-framework'])
toDir.createSync(recursive: true); }).toList();
Map<String, dynamic> json = { 'services' : jsonServices };
for (FileSystemEntity entity in fromDir.listSync()) { manifest.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
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.');
}
};
} }
...@@ -13,8 +13,10 @@ import 'artifacts.dart'; ...@@ -13,8 +13,10 @@ import 'artifacts.dart';
import 'base/globals.dart'; import 'base/globals.dart';
const String _kFlutterManifestPath = 'flutter.yaml'; const String _kFlutterManifestPath = 'flutter.yaml';
const String _kFlutterServicesManifestPath = 'flutter_services.yaml';
dynamic _loadYamlFile(String path) { dynamic _loadYamlFile(String path) {
printTrace("Looking for YAML at '$path'");
if (!FileSystemEntity.isFileSync(path)) if (!FileSystemEntity.isFileSync(path))
return null; return null;
String manifestString = new File(path).readAsStringSync(); String manifestString = new File(path).readAsStringSync();
...@@ -26,18 +28,24 @@ dynamic _loadYamlFile(String path) { ...@@ -26,18 +28,24 @@ dynamic _loadYamlFile(String path) {
Future parseServiceConfigs( Future parseServiceConfigs(
List<Map<String, String>> services, { List<File> jars } List<Map<String, String>> services, { List<File> jars }
) async { ) async {
if (!ArtifactStore.isPackageRootValid) if (!ArtifactStore.isPackageRootValid) {
printTrace("Artifact store invalid while parsing service configs");
return; return;
}
dynamic manifest = _loadYamlFile(_kFlutterManifestPath); dynamic manifest = _loadYamlFile(_kFlutterManifestPath);
if (manifest == null || manifest['services'] == null) if (manifest == null || manifest['services'] == null) {
printTrace("No services specified in the manifest");
return; return;
}
for (String service in manifest['services']) { for (String service in manifest['services']) {
String serviceRoot = '${ArtifactStore.packageRoot}/$service'; String serviceRoot = '${ArtifactStore.packageRoot}/$service';
dynamic serviceConfig = _loadYamlFile('$serviceRoot/config.yaml'); dynamic serviceConfig = _loadYamlFile('$serviceRoot/$_kFlutterServicesManifestPath');
if (serviceConfig == null) if (serviceConfig == null) {
printStatus("No $_kFlutterServicesManifestPath found for service '$serviceRoot'. Skipping.");
continue; continue;
}
for (Map<String, String> service in serviceConfig['services']) { for (Map<String, String> service in serviceConfig['services']) {
services.add({ services.add({
...@@ -78,22 +86,16 @@ Future<String> getServiceFromUrl( ...@@ -78,22 +86,16 @@ Future<String> getServiceFromUrl(
/// ] /// ]
/// } /// }
File generateServiceDefinitions( 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 = List<Map<String, String>> services =
servicesIn.map((Map<String, String> service) => { servicesIn.map((Map<String, String> service) => {
'name': service['name'], 'name': service['name'],
keyOut: service[keyIn] 'class': service['android-class']
}).toList(); }).toList();
Map<String, dynamic> json = { 'services': services }; 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); servicesFile.writeAsStringSync(JSON.encode(json), mode: FileMode.WRITE, flush: true);
return servicesFile; return servicesFile;
} }
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