Make Assets more robust across platforms (#14246)

diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index b3dc4f3..22b3481 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -167,7 +167,7 @@
       }
       for (_Asset variant in assetVariants[asset]) {
         assert(variant.assetFileExists);
-        entries[variant.assetEntry] = new DevFSFileContent(variant.assetFile);
+        entries[variant.entryUri.path] = new DevFSFileContent(variant.assetFile);
       }
     }
 
@@ -177,7 +177,7 @@
     }
     for (_Asset asset in materialAssets) {
       assert(asset.assetFileExists);
-      entries[asset.assetEntry] = new DevFSFileContent(asset.assetFile);
+      entries[asset.entryUri.path] = new DevFSFileContent(asset.assetFile);
     }
 
     entries[_kAssetManifestJson] = _createAssetManifest(assetVariants);
@@ -199,38 +199,34 @@
 }
 
 class _Asset {
-  _Asset({ this.base, String assetEntry, this.relativePath, this.source })
-    : _assetEntry = assetEntry;
+  _Asset({ this.baseDir, this.relativeUri, this.entryUri });
 
-  final String _assetEntry;
+  final String baseDir;
 
-  final String base;
+  /// A platform-independent Uri where this asset can be found on disk on the
+  /// host system relative to [baseDir].
+  final Uri relativeUri;
 
-  /// The entry to list in the generated asset manifest.
-  String get assetEntry => _assetEntry ?? relativePath;
-
-  /// Where the resource is on disk relative to [base].
-  final String relativePath;
-
-  final String source;
+  /// A platform-independent Uri representing the entry for the asset manifest.
+  final Uri entryUri;
 
   File get assetFile {
-    return fs.file(source != null ? '$base/$source' : '$base/$relativePath');
+    return fs.file(fs.path.join(baseDir, fs.path.fromUri(relativeUri)));
   }
 
   bool get assetFileExists => assetFile.existsSync();
 
-  /// The delta between what the assetEntry is and the relativePath (e.g.,
+  /// The delta between what the entryUri is and the relativeUri (e.g.,
   /// packages/flutter_gallery).
-  String get symbolicPrefix {
-    if (_assetEntry == null || _assetEntry == relativePath)
+  Uri get symbolicPrefixUri {
+    if (entryUri == relativeUri)
       return null;
-    final int index = _assetEntry.indexOf(relativePath);
-    return index == -1 ? null : _assetEntry.substring(0, index);
+    final int index = entryUri.path.indexOf(relativeUri.path);
+    return index == -1 ? null : new Uri(path: entryUri.path.substring(0, index));
   }
 
   @override
-  String toString() => 'asset: $assetEntry';
+  String toString() => 'asset: $entryUri';
 
   @override
   bool operator ==(dynamic other) {
@@ -239,18 +235,16 @@
     if (other.runtimeType != runtimeType)
       return false;
     final _Asset otherAsset = other;
-    return otherAsset.base == base
-        && otherAsset.assetEntry == assetEntry
-        && otherAsset.relativePath == relativePath
-        && otherAsset.source == source;
+    return otherAsset.baseDir == baseDir
+        && otherAsset.relativeUri == relativeUri
+        && otherAsset.entryUri == entryUri;
   }
 
   @override
   int get hashCode {
-    return base.hashCode
-        ^assetEntry.hashCode
-        ^relativePath.hashCode
-        ^ source.hashCode;
+    return baseDir.hashCode
+        ^relativeUri.hashCode
+        ^ entryUri.hashCode;
   }
 }
 
@@ -272,11 +266,11 @@
 
   for (Map<String, dynamic> family in _getMaterialFonts(fontSet)) {
     for (Map<String, dynamic> font in family['fonts']) {
-      final String assetKey = font['asset'];
+      final Uri entryUri = fs.path.toUri(font['asset']);
       result.add(new _Asset(
-        base: fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'artifacts', 'material_fonts'),
-        source: fs.path.basename(assetKey),
-        relativePath: assetKey
+        baseDir: fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'artifacts', 'material_fonts'),
+        relativeUri: new Uri(path: entryUri.pathSegments.last),
+        entryUri: entryUri
       ));
     }
   }
@@ -361,8 +355,8 @@
   for (_Asset main in assetVariants.keys) {
     final List<String> variants = <String>[];
     for (_Asset variant in assetVariants[main])
-      variants.add(variant.assetEntry);
-    json[main.assetEntry] = variants;
+      variants.add(variant.entryUri.path);
+    json[main.entryUri.path] = variants;
   }
   return new DevFSStringContent(JSON.encode(json));
 }
@@ -400,17 +394,17 @@
   for (Font font in manifest.fonts) {
     final List<FontAsset> packageFontAssets = <FontAsset>[];
     for (FontAsset fontAsset in font.fontAssets) {
-      final String assetPath = fontAsset.asset;
-      if (assetPath.startsWith('packages') &&
-          !fs.isFileSync(packageMap.map[packageName].resolve('../$assetPath').path)) {
+      final Uri assetUri = fontAsset.assetUri;
+      if (assetUri.pathSegments.first == 'packages' &&
+          !fs.isFileSync(fs.path.fromUri(packageMap.map[packageName].resolve('../${assetUri.path}')))) {
         packageFontAssets.add(new FontAsset(
-          fontAsset.asset,
+          fontAsset.assetUri,
           weight: fontAsset.weight,
           style: fontAsset.style,
         ));
       } else {
         packageFontAssets.add(new FontAsset(
-          'packages/$packageName/${fontAsset.asset}',
+          new Uri(pathSegments: <String>['packages', packageName]..addAll(assetUri.pathSegments)),
           weight: fontAsset.weight,
           style: fontAsset.style,
         ));
@@ -486,20 +480,25 @@
   final Map<_Asset, List<_Asset>> result = <_Asset, List<_Asset>>{};
 
   final _AssetDirectoryCache cache = new _AssetDirectoryCache(excludeDirs);
-  for (String assetName in flutterManifest.assets) {
+  for (Uri assetUri in flutterManifest.assets) {
     final _Asset asset = _resolveAsset(
       packageMap,
       assetBase,
-      assetName,
+      assetUri,
       packageName,
     );
     final List<_Asset> variants = <_Asset>[];
     for (String path in cache.variantsFor(asset.assetFile.path)) {
-      final String key = fs.path.relative(path, from: asset.base);
-      String assetEntry;
-      if (asset.symbolicPrefix != null)
-        assetEntry = fs.path.join(asset.symbolicPrefix, key);
-      variants.add(new _Asset(base: asset.base, assetEntry: assetEntry, relativePath: key));
+      final String relativePath = fs.path.relative(path, from: asset.baseDir);
+      final Uri relativeUri = fs.path.toUri(relativePath);
+      final Uri entryUri = asset.symbolicPrefixUri == null
+          ? relativeUri
+          : asset.symbolicPrefixUri.resolveUri(relativeUri);
+      variants.add(new _Asset(
+        baseDir: asset.baseDir,
+        entryUri: entryUri,
+        relativeUri: relativeUri,
+      ));
     }
 
     result[asset] = variants;
@@ -511,11 +510,11 @@
       final _Asset baseAsset = _resolveAsset(
         packageMap,
         assetBase,
-        fontAsset.asset,
+        fontAsset.assetUri,
         packageName,
       );
       if (!baseAsset.assetFileExists) {
-        printError('Error: unable to locate asset entry in pubspec.yaml: "${fontAsset.asset}".');
+        printError('Error: unable to locate asset entry in pubspec.yaml: "${fontAsset.assetUri}".');
         return null;
       }
 
@@ -528,47 +527,42 @@
 
 _Asset _resolveAsset(
   PackageMap packageMap,
-  String assetBase,
-  String asset,
+  String assetsBaseDir,
+  Uri assetUri,
   String packageName,
 ) {
-  if (asset.startsWith('packages/') && !fs.isFileSync(fs.path.join(assetBase, asset))) {
+  final String assetPath = fs.path.fromUri(assetUri);
+  if (assetUri.pathSegments.first == 'packages' && !fs.isFileSync(fs.path.join(assetsBaseDir, assetPath))) {
     // The asset is referenced in the pubspec.yaml as
     // 'packages/PACKAGE_NAME/PATH/TO/ASSET .
-    final _Asset packageAsset = _resolvePackageAsset(asset, packageMap);
+    final _Asset packageAsset = _resolvePackageAsset(assetUri, packageMap);
     if (packageAsset != null)
       return packageAsset;
   }
 
-  final String assetEntry = packageName != null
-    ? 'packages/$packageName/$asset' // Asset from, and declared in $packageName.
-    : null; // Asset from the current application.
-  return new _Asset(base: assetBase, assetEntry: assetEntry, relativePath: asset);
+  return new _Asset(
+    baseDir: assetsBaseDir,
+    entryUri: packageName == null
+        ? assetUri // Asset from the current application.
+        : new Uri(pathSegments: <String>['packages', packageName]..addAll(assetUri.pathSegments)), // Asset from, and declared in $packageName.
+    relativeUri: assetUri,
+  );
 }
 
-_Asset _resolvePackageAsset(String asset, PackageMap packageMap) {
-  assert(asset.startsWith('packages/'));
-  String packageKey = asset.substring('packages/'.length);
-  String relativeAsset = asset;
-
-  final int index = packageKey.indexOf('/');
-  if (index != -1) {
-    relativeAsset = packageKey.substring(index + 1);
-    packageKey = packageKey.substring(0, index);
-
-
-    final Uri uri = packageMap.map[packageKey];
-    if (uri != null && uri.scheme == 'file') {
-      final File file = fs.file(uri);
-      final String base = file.path.substring(0, file.path.length - 1);
+_Asset _resolvePackageAsset(Uri assetUri, PackageMap packageMap) {
+  assert(assetUri.pathSegments.first == 'packages');
+  if (assetUri.pathSegments.length > 1) {
+    final String packageName = assetUri.pathSegments[1];
+    final Uri packageUri = packageMap.map[packageName];
+    if (packageUri != null && packageUri.scheme == 'file') {
       return new _Asset(
-        base: base,
-        assetEntry: asset,
-        relativePath: relativeAsset,
+        baseDir: fs.path.fromUri(packageUri),
+        entryUri: assetUri,
+        relativeUri: new Uri(pathSegments: assetUri.pathSegments.sublist(2)),
       );
     }
   }
   printStatus('Error detected in pubspec.yaml:', emphasis: true);
-  printError('Could not resolve package $packageKey for asset $asset.\n');
+  printError('Could not resolve package for asset $assetUri.\n');
   return null;
 }
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index e26d9f1..30e5d6b 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -52,8 +52,8 @@
    return _flutterDescriptor['fonts'] ?? const <Map<String, dynamic>>[];
   }
 
-  List<String> get assets {
-    return _flutterDescriptor['assets'] ?? const <String>[];
+  List<Uri> get assets {
+    return _flutterDescriptor['assets']?.map(Uri.parse)?.toList() ?? const <Uri>[];
   }
 
   List<Font> _fonts;
@@ -89,7 +89,7 @@
         }
 
         fontAssets.add(new FontAsset(
-          asset,
+          Uri.parse(asset),
           weight: fontFile['weight'],
           style: fontFile['style'],
         ));
@@ -122,10 +122,10 @@
 }
 
 class FontAsset {
-  FontAsset(this.asset, {this.weight, this.style})
-    : assert(asset != null);
+  FontAsset(this.assetUri, {this.weight, this.style})
+    : assert(assetUri != null);
 
-  final String asset;
+  final Uri assetUri;
   final int weight;
   final String style;
 
@@ -137,12 +137,12 @@
     if (style != null)
       descriptor['style'] = style;
 
-    descriptor['asset'] = asset;
+    descriptor['asset'] = assetUri.path;
     return descriptor;
   }
 
   @override
-  String toString() => '$runtimeType(asset: $asset, weight; $weight, style: $style)';
+  String toString() => '$runtimeType(asset: ${assetUri.path}, weight; $weight, style: $style)';
 }
 
 Future<dynamic> _loadFlutterManifest(String manifestPath) async {
@@ -167,4 +167,4 @@
     printError(validator.errors.join('\n'));
     return false;
   }
-}
\ No newline at end of file
+}