Commit ad1c497c authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Do not lower-case paths during canonicalization. (#9571)

* Do not lower-case paths during canonicalization.

This breaks hot reload on some platfroms with case insensitive file systems.

* Add unit tests
parent 31828609
...@@ -134,3 +134,11 @@ Directory getReplaySource(String dirname, String basename) { ...@@ -134,3 +134,11 @@ Directory getReplaySource(String dirname, String basename) {
throwToolExit('Invalid replay-from location: $dirname ("$basename" does not exist)'); throwToolExit('Invalid replay-from location: $dirname ("$basename" does not exist)');
return dir; return dir;
} }
/// Canonicalizes [path].
///
/// This function implements the behaviour of `canonicalize` from
/// `package:path`. However, unlike the original, it does not change the ASCII
/// case of the path. Changing the case can break hot reload in some situations,
/// for an example see: https://github.com/flutter/flutter/issues/9539.
String canonicalizePath(String path) => fs.path.normalize(fs.path.absolute(path));
...@@ -9,9 +9,9 @@ import '../dart/package_map.dart'; ...@@ -9,9 +9,9 @@ import '../dart/package_map.dart';
class DartDependencySetBuilder { class DartDependencySetBuilder {
DartDependencySetBuilder(String mainScriptPath, String packagesFilePath) : DartDependencySetBuilder(String mainScriptPath, String packagesFilePath) :
_mainScriptPath = fs.path.canonicalize(mainScriptPath), _mainScriptPath = canonicalizePath(mainScriptPath),
_mainScriptUri = fs.path.toUri(mainScriptPath), _mainScriptUri = fs.path.toUri(mainScriptPath),
_packagesFilePath = fs.path.canonicalize(packagesFilePath); _packagesFilePath = canonicalizePath(packagesFilePath);
final String _mainScriptPath; final String _mainScriptPath;
final String _packagesFilePath; final String _packagesFilePath;
...@@ -48,7 +48,7 @@ class DartDependencySetBuilder { ...@@ -48,7 +48,7 @@ class DartDependencySetBuilder {
} }
resolvedUri = newResolvedUri; resolvedUri = newResolvedUri;
} }
final String path = fs.path.canonicalize(resolvedUri.toFilePath()); final String path = canonicalizePath(resolvedUri.toFilePath());
if (!dependencies.contains(path)) { if (!dependencies.contains(path)) {
if (!fs.isFileSync(path)) { if (!fs.isFileSync(path)) {
throw new DartDependencyException( throw new DartDependencyException(
......
...@@ -499,7 +499,6 @@ class DevFS { ...@@ -499,7 +499,6 @@ class DevFS {
String relativePath, String relativePath,
Uri directoryUriOnDevice, { Uri directoryUriOnDevice, {
bool ignoreDotFiles: true, bool ignoreDotFiles: true,
Set<String> fileFilter
}) { }) {
if (file is Directory) { if (file is Directory) {
// Skip non-files. // Skip non-files.
...@@ -510,13 +509,6 @@ class DevFS { ...@@ -510,13 +509,6 @@ class DevFS {
// Skip dot files. // Skip dot files.
return true; return true;
} }
if (fileFilter != null) {
final String canonicalizeFilePath = fs.path.canonicalize(file.absolute.path);
if ((fileFilter != null) && !fileFilter.contains(canonicalizeFilePath)) {
// Skip files that are not included in the filter.
return true;
}
}
return false; return false;
} }
...@@ -542,8 +534,7 @@ class DevFS { ...@@ -542,8 +534,7 @@ class DevFS {
directoryUriOnDevice = directoryUriOnDevice =
_directoryUriOnDevice(directoryUriOnDevice, directory); _directoryUriOnDevice(directoryUriOnDevice, directory);
try { try {
final String absoluteDirectoryPath = final String absoluteDirectoryPath = canonicalizePath(directory.path);
fs.path.canonicalize(fs.path.absolute(directory.path));
// For each file in the file filter. // For each file in the file filter.
for (String filePath in fileFilter) { for (String filePath in fileFilter) {
if (!filePath.startsWith(absoluteDirectoryPath)) { if (!filePath.startsWith(absoluteDirectoryPath)) {
...@@ -600,7 +591,7 @@ class DevFS { ...@@ -600,7 +591,7 @@ class DevFS {
} }
final String relativePath = final String relativePath =
fs.path.relative(file.path, from: directory.path); fs.path.relative(file.path, from: directory.path);
if (_shouldSkip(file, relativePath, directoryUriOnDevice, ignoreDotFiles: ignoreDotFiles, fileFilter: fileFilter)) { if (_shouldSkip(file, relativePath, directoryUriOnDevice, ignoreDotFiles: ignoreDotFiles)) {
continue; continue;
} }
final Uri deviceUri = directoryUriOnDevice.resolveUri(fs.path.toUri(relativePath)); final Uri deviceUri = directoryUriOnDevice.resolveUri(fs.path.toUri(relativePath));
......
...@@ -27,8 +27,8 @@ void main() { ...@@ -27,8 +27,8 @@ void main() {
final DartDependencySetBuilder builder = final DartDependencySetBuilder builder =
new DartDependencySetBuilder(mainPath, packagesPath); new DartDependencySetBuilder(mainPath, packagesPath);
final Set<String> dependencies = builder.build(); final Set<String> dependencies = builder.build();
expect(dependencies.contains(fs.path.canonicalize(mainPath)), isTrue); expect(dependencies.contains(canonicalizePath(mainPath)), isTrue);
expect(dependencies.contains(fs.path.canonicalize(fs.path.join(testPath, 'foo.dart'))), isTrue); expect(dependencies.contains(canonicalizePath(fs.path.join(testPath, 'foo.dart'))), isTrue);
}); });
testUsingContext('syntax_error', () { testUsingContext('syntax_error', () {
...@@ -73,5 +73,14 @@ void main() { ...@@ -73,5 +73,14 @@ void main() {
expect(error.toString(), contains('pubspec.yaml')); expect(error.toString(), contains('pubspec.yaml'));
} }
}); });
testUsingContext('does not change ASCI casing of path', () {
final String testPath = fs.path.join(dataPath, 'asci_casing');
final String mainPath = fs.path.join(testPath, 'main.dart');
final String packagesPath = fs.path.join(testPath, '.packages');
final DartDependencySetBuilder builder = new DartDependencySetBuilder(mainPath, packagesPath);
final Set<String> deps = builder.build();
expect(deps, contains(endsWith('This_Import_Has_fuNNy_casING.dart')));
});
}); });
} }
// Copyright 2017 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.
String dummy = 'Hello';
// Copyright 2017 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 'This_Import_Has_fuNNy_casING.dart';
void main() {
print(dummy);
}
...@@ -9,6 +9,7 @@ import 'package:file/file.dart'; ...@@ -9,6 +9,7 @@ import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/vmservice.dart'; import 'package:flutter_tools/src/vmservice.dart';
...@@ -121,6 +122,21 @@ void main() { ...@@ -121,6 +122,21 @@ void main() {
FileSystem: () => fs, FileSystem: () => fs,
}); });
testUsingContext('add new file to local file system and preserve unusal file name casing', () async {
final String filePathWithUnusalCasing = fs.path.join('FooBar', 'TEST.txt');
final File file = fs.file(fs.path.join(basePath, filePathWithUnusalCasing));
await file.parent.create(recursive: true);
file.writeAsBytesSync(<int>[1, 2, 3, 4, 5, 6, 7]);
final int bytes = await devFS.update();
devFSOperations.expectMessages(<String>[
'writeFile test FooBar/TEST.txt',
]);
expect(devFS.assetPathsToEvict, isEmpty);
expect(bytes, 7);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('modify existing file on local file system', () async { testUsingContext('modify existing file on local file system', () async {
await devFS.update(); await devFS.update();
final File file = fs.file(fs.path.join(basePath, filePath)); final File file = fs.file(fs.path.join(basePath, filePath));
...@@ -181,7 +197,7 @@ void main() { ...@@ -181,7 +197,7 @@ void main() {
fileFilter.addAll(fs.directory(pkgUri) fileFilter.addAll(fs.directory(pkgUri)
.listSync(recursive: true) .listSync(recursive: true)
.where((FileSystemEntity file) => file is File) .where((FileSystemEntity file) => file is File)
.map((FileSystemEntity file) => fs.path.canonicalize(file.path)) .map((FileSystemEntity file) => canonicalizePath(file.path))
.toList()); .toList());
} }
final int bytes = await devFS.update(fileFilter: fileFilter); final int bytes = await devFS.update(fileFilter: fileFilter);
......
...@@ -59,4 +59,30 @@ void main() { ...@@ -59,4 +59,30 @@ void main() {
expect(sourceMemoryFs.directory(sourcePath).listSync().length, 3); expect(sourceMemoryFs.directory(sourcePath).listSync().length, 3);
}); });
}); });
group('canonicalizePath', () {
test('does not lowercase on Windows', () {
String path = 'C:\\Foo\\bAr\\cOOL.dart';
expect(canonicalizePath(path), path);
// fs.path.canonicalize does lowercase on Windows
expect(fs.path.canonicalize(path), isNot(path));
path = '..\\bar\\.\\\\Foo';
final String expected = fs.path.join(fs.currentDirectory.parent.absolute.path, 'bar', 'Foo');
expect(canonicalizePath(path), expected);
// fs.path.canonicalize should return the same result (modulo casing)
expect(fs.path.canonicalize(path), expected.toLowerCase());
}, testOn: 'windows');
test('does not lowercase on posix', () {
String path = '/Foo/bAr/cOOL.dart';
expect(canonicalizePath(path), path);
// fs.path.canonicalize and canonicalizePath should be the same on Posix
expect(fs.path.canonicalize(path), path);
path = '../bar/.//Foo';
final String expected = fs.path.join(fs.currentDirectory.parent.absolute.path, 'bar', 'Foo');
expect(canonicalizePath(path), expected);
}, testOn: 'posix');
});
} }
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