Commit 2d8780c3 authored by Chinmay Garde's avatar Chinmay Garde

Merge pull request #1835 from chinmaygarde/master

Tooling updates for dealing with native services distributed in pub packages
parents c30b3cc6 d38bfee6
......@@ -271,7 +271,7 @@ int _buildApk(
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');
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:path/path.dart' as path;
......@@ -173,25 +174,22 @@ class IOSDevice extends Device {
// 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 @@ class IOSDevice extends Device {
]);
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 @@ class IOSSimulator extends Device {
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 @@ class IOSSimulator extends Device {
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 @@ class IOSSimulator extends Device {
return false;
}
printTrace('Successfully started ${app.name} on $id');
printTrace('Successfully started ${app.name} on $id.');
return true;
}
......@@ -575,6 +570,11 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice
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 @@ Future<bool> _buildIOSXcodeProject(ApplicationPackage app, { bool buildForDevice
}
}
bool _servicesEnabled = false;
Future _addServicesToBundle(Directory bundle) async {
if (_servicesEnabled) {
List<Map<String, String>> services = [];
printTrace("Trying to resolve native pub services.");
// Step 1: Parse the service configuration yaml files present in the service
// pub packages.
await parseServiceConfigs(services);
await _fetchFrameworks(services);
_copyFrameworksToBundle(bundle.path, services);
printTrace("Found ${services.length} service definition(s).");
generateServiceDefinitions(bundle.path, services, ios: true);
}
}
// 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);
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
);
}
// 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);
}
void _copyFrameworksToBundle(String destDir, List<Map<String, String>> services) {
// TODO(mpcomplete): check timestamps.
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 basename = path.basename(service['framework-path']);
String destPath = path.join(destDir, basename);
_copyDirRecursive(service['framework-path'], destPath);
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 _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.');
}
};
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);
}
......@@ -13,8 +13,10 @@ import 'artifacts.dart';
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 @@ dynamic _loadYamlFile(String path) {
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 @@ Future<String> getServiceFromUrl(
/// ]
/// }
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;
}
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