Unverified Commit 29490887 authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

Reland: [flutter_tools] File system utilities (#48757)

* Reland: [flutter_tools] File system utilities

* Remove unused imports
parent 5ca4cc88
......@@ -17,7 +17,6 @@ import 'src/base/io.dart';
import 'src/base/logger.dart';
import 'src/base/process.dart';
import 'src/base/terminal.dart';
import 'src/base/utils.dart';
import 'src/context_runner.dart';
import 'src/doctor.dart';
import 'src/globals.dart' as globals;
......@@ -191,7 +190,7 @@ FileSystem crashFileSystem = const LocalFileSystem();
/// Saves the crash report to a local file.
Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrace stackTrace, String doctorText) async {
File crashFile = getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');
File crashFile = fsUtils.getUniqueFile(crashFileSystem.currentDirectory, 'flutter', 'log');
final StringBuffer buffer = StringBuffer();
......@@ -211,7 +210,7 @@ Future<File> _createLocalCrashReport(List<String> args, dynamic error, StackTrac
crashFile.writeAsStringSync(buffer.toString());
} on FileSystemException catch (_) {
// Fallback to the system temporary directory.
crashFile = getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
crashFile = fsUtils.getUniqueFile(crashFileSystem.systemTempDirectory, 'flutter', 'log');
try {
crashFile.writeAsStringSync(buffer.toString());
} on FileSystemException catch (e) {
......
......@@ -98,7 +98,7 @@ class GradleUtils {
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
void injectGradleWrapperIfNeeded(Directory directory) {
copyDirectorySync(
fsUtils.copyDirectorySync(
globals.cache.getArtifactDirectory('gradle_wrapper'),
directory,
shouldCopyFile: (File sourceFile, File destinationFile) {
......@@ -267,10 +267,10 @@ void updateLocalProperties({
}
if (androidSdk != null) {
changeIfNecessary('sdk.dir', escapePath(androidSdk.directory));
changeIfNecessary('sdk.dir', fsUtils.escapePath(androidSdk.directory));
}
changeIfNecessary('flutter.sdk', escapePath(Cache.flutterRoot));
changeIfNecessary('flutter.sdk', fsUtils.escapePath(Cache.flutterRoot));
if (buildInfo != null) {
changeIfNecessary('flutter.buildMode', buildInfo.modeName);
final String buildName = validatedBuildNameForPlatform(
......@@ -296,7 +296,7 @@ void updateLocalProperties({
void writeLocalProperties(File properties) {
final SettingsFile settings = SettingsFile();
if (androidSdk != null) {
settings.values['sdk.dir'] = escapePath(androidSdk.directory);
settings.values['sdk.dir'] = fsUtils.escapePath(androidSdk.directory);
}
settings.writeContents(properties);
}
......
......@@ -11,7 +11,10 @@ import 'utils.dart';
class Config {
Config([File configFile, Logger localLogger]) {
final Logger loggerInstance = localLogger ?? globals.logger;
_configFile = configFile ?? globals.fs.file(globals.fs.path.join(userHomePath(), '.flutter_settings'));
_configFile = configFile ?? globals.fs.file(globals.fs.path.join(
fsUtils.userHomePath,
'.flutter_settings',
));
if (_configFile.existsSync()) {
try {
_values = castStringKeyedMap(json.decode(_configFile.readAsStringSync()));
......
......@@ -4,108 +4,149 @@
import 'package:file/file.dart';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import '../globals.dart' as globals;
import 'common.dart' show throwToolExit;
import 'context.dart';
export 'package:file/file.dart';
export 'package:file/local.dart';
/// Create the ancestor directories of a file path if they do not already exist.
void ensureDirectoryExists(String filePath) {
final String dirPath = globals.fs.path.dirname(filePath);
if (globals.fs.isDirectorySync(dirPath)) {
return;
}
try {
globals.fs.directory(dirPath).createSync(recursive: true);
} on FileSystemException catch (e) {
throwToolExit('Failed to create directory "$dirPath": ${e.osError.message}');
}
/// Exception indicating that a file that was expected to exist was not found.
class FileNotFoundException implements IOException {
const FileNotFoundException(this.path);
final String path;
@override
String toString() => 'File not found: $path';
}
/// Creates `destDir` if needed, then recursively copies `srcDir` to `destDir`,
/// invoking [onFileCopied], if specified, for each source/destination file pair.
///
/// Skips files if [shouldCopyFile] returns `false`.
void copyDirectorySync(
Directory srcDir,
Directory destDir, {
bool shouldCopyFile(File srcFile, File destFile),
void onFileCopied(File srcFile, File destFile),
}) {
if (!srcDir.existsSync()) {
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
}
final FileSystemUtils _defaultFileSystemUtils = FileSystemUtils(
fileSystem: globals.fs,
platform: globals.platform,
);
FileSystemUtils get fsUtils => context.get<FileSystemUtils>() ?? _defaultFileSystemUtils;
/// Various convenience file system methods.
class FileSystemUtils {
FileSystemUtils({
@required FileSystem fileSystem,
@required Platform platform,
}) : _fileSystem = fileSystem,
_platform = platform;
final FileSystem _fileSystem;
final Platform _platform;
if (!destDir.existsSync()) {
destDir.createSync(recursive: true);
/// Create the ancestor directories of a file path if they do not already exist.
void ensureDirectoryExists(String filePath) {
final String dirPath = _fileSystem.path.dirname(filePath);
if (_fileSystem.isDirectorySync(dirPath)) {
return;
}
try {
_fileSystem.directory(dirPath).createSync(recursive: true);
} on FileSystemException catch (e) {
throwToolExit('Failed to create directory "$dirPath": ${e.osError.message}');
}
}
for (final FileSystemEntity entity in srcDir.listSync()) {
final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
if (entity is File) {
final File newFile = destDir.fileSystem.file(newPath);
if (shouldCopyFile != null && !shouldCopyFile(entity, newFile)) {
continue;
/// Creates `destDir` if needed, then recursively copies `srcDir` to
/// `destDir`, invoking [onFileCopied], if specified, for each
/// source/destination file pair.
///
/// Skips files if [shouldCopyFile] returns `false`.
void copyDirectorySync(
Directory srcDir,
Directory destDir, {
bool shouldCopyFile(File srcFile, File destFile),
void onFileCopied(File srcFile, File destFile),
}) {
if (!srcDir.existsSync()) {
throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
}
if (!destDir.existsSync()) {
destDir.createSync(recursive: true);
}
for (final FileSystemEntity entity in srcDir.listSync()) {
final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
if (entity is File) {
final File newFile = destDir.fileSystem.file(newPath);
if (shouldCopyFile != null && !shouldCopyFile(entity, newFile)) {
continue;
}
newFile.writeAsBytesSync(entity.readAsBytesSync());
onFileCopied?.call(entity, newFile);
} else if (entity is Directory) {
copyDirectorySync(
entity,
destDir.fileSystem.directory(newPath),
shouldCopyFile: shouldCopyFile,
onFileCopied: onFileCopied,
);
} else {
throw Exception('${entity.path} is neither File nor Directory');
}
newFile.writeAsBytesSync(entity.readAsBytesSync());
onFileCopied?.call(entity, newFile);
} else if (entity is Directory) {
copyDirectorySync(
entity,
destDir.fileSystem.directory(newPath),
shouldCopyFile: shouldCopyFile,
onFileCopied: onFileCopied,
);
} else {
throw Exception('${entity.path} is neither File nor Directory');
}
}
}
/// Canonicalizes [path].
///
/// This function implements the behavior 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) => globals.fs.path.normalize(globals.fs.path.absolute(path));
/// Escapes [path].
///
/// On Windows it replaces all '\' with '\\'. On other platforms, it returns the
/// path unchanged.
String escapePath(String path) => globals.platform.isWindows ? path.replaceAll('\\', '\\\\') : path;
/// Returns true if the file system [entity] has not been modified since the
/// latest modification to [referenceFile].
///
/// Returns true, if [entity] does not exist.
///
/// Returns false, if [entity] exists, but [referenceFile] does not.
bool isOlderThanReference({ @required FileSystemEntity entity, @required File referenceFile }) {
if (!entity.existsSync()) {
return true;
/// Appends a number to a filename in order to make it unique under a
/// directory.
File getUniqueFile(Directory dir, String baseName, String ext) {
final FileSystem fs = dir.fileSystem;
int i = 1;
while (true) {
final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
final File file = fs.file(_fileSystem.path.join(dir.path, name));
if (!file.existsSync()) {
return file;
}
i++;
}
}
return referenceFile.existsSync()
&& referenceFile.statSync().modified.isAfter(entity.statSync().modified);
}
/// Exception indicating that a file that was expected to exist was not found.
class FileNotFoundException implements IOException {
const FileNotFoundException(this.path);
/// Return a relative path if [fullPath] is contained by the cwd, else return an
/// absolute path.
String getDisplayPath(String fullPath) {
final String cwd = _fileSystem.currentDirectory.path + _fileSystem.path.separator;
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
}
final String path;
/// Escapes [path].
///
/// On Windows it replaces all '\' with '\\'. On other platforms, it returns the
/// path unchanged.
String escapePath(String path) => _platform.isWindows ? path.replaceAll('\\', '\\\\') : path;
@override
String toString() => 'File not found: $path';
}
/// Returns true if the file system [entity] has not been modified since the
/// latest modification to [referenceFile].
///
/// Returns true, if [entity] does not exist.
///
/// Returns false, if [entity] exists, but [referenceFile] does not.
bool isOlderThanReference({
@required FileSystemEntity entity,
@required File referenceFile,
}) {
if (!entity.existsSync()) {
return true;
}
return referenceFile.existsSync()
&& referenceFile.statSync().modified.isAfter(entity.statSync().modified);
}
/// Reads the process environment to find the current user's home directory.
///
/// If the searched environment variables are not set, '.' is returned instead.
String userHomePath() {
final String envKey = globals.platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
return globals.platform.environment[envKey] ?? '.';
/// Reads the process environment to find the current user's home directory.
///
/// If the searched environment variables are not set, '.' is returned instead.
String get userHomePath {
final String envKey = _platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
return _platform.environment[envKey] ?? '.';
}
}
......@@ -33,6 +33,7 @@ import 'dart:io' as io
InternetAddressType,
IOSink,
NetworkInterface,
pid,
Process,
ProcessInfo,
ProcessSignal,
......@@ -252,6 +253,14 @@ Stream<List<int>> get stdin => globals.stdio.stdin;
io.IOSink get stderr => globals.stdio.stderr;
bool get stdinHasTerminal => globals.stdio.stdinHasTerminal;
// TODO(zra): Move pid and writePidFile into `ProcessInfo`.
void writePidFile(String pidFile) {
if (pidFile != null) {
// Write our pid to the file.
globals.fs.file(pidFile).writeAsStringSync(io.pid.toString());
}
}
/// An overridable version of io.ProcessInfo.
abstract class ProcessInfo {
factory ProcessInfo() => _DefaultProcessInfo();
......
......@@ -9,9 +9,7 @@ import 'package:intl/intl.dart';
import 'package:meta/meta.dart';
import '../convert.dart';
import '../globals.dart' as globals;
import 'file_system.dart';
import 'io.dart' as io;
import 'terminal.dart';
/// Convert `foo_bar` to `fooBar`.
......@@ -51,29 +49,10 @@ String getEnumName(dynamic enumItem) {
return index == -1 ? name : name.substring(index + 1);
}
File getUniqueFile(Directory dir, String baseName, String ext) {
final FileSystem fs = dir.fileSystem;
int i = 1;
while (true) {
final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
final File file = fs.file(globals.fs.path.join(dir.path, name));
if (!file.existsSync()) {
return file;
}
i++;
}
}
String toPrettyJson(Object jsonable) {
return const JsonEncoder.withIndent(' ').convert(jsonable) + '\n';
}
/// Return a String - with units - for the size in MB of the given number of bytes.
String getSizeAsMB(int bytesLength) {
return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB';
}
final NumberFormat kSecondsFormat = NumberFormat('0.0');
final NumberFormat kMillisecondsFormat = NumberFormat.decimalPattern();
......@@ -86,11 +65,9 @@ String getElapsedAsMilliseconds(Duration duration) {
return '${kMillisecondsFormat.format(duration.inMilliseconds)}ms';
}
/// Return a relative path if [fullPath] is contained by the cwd, else return an
/// absolute path.
String getDisplayPath(String fullPath) {
final String cwd = globals.fs.currentDirectory.path + globals.fs.path.separator;
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
/// Return a String - with units - for the size in MB of the given number of bytes.
String getSizeAsMB(int bytesLength) {
return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB';
}
/// A class to maintain a list of items, fire events when items are added or
......@@ -315,13 +292,6 @@ String wrapText(String text, { int columnWidth, int hangingIndent, int indent, b
return result.join('\n');
}
void writePidFile(String pidFile) {
if (pidFile != null) {
// Write our pid to the file.
globals.fs.file(pidFile).writeAsStringSync(io.pid.toString());
}
}
// Used to represent a run of ANSI control sequences next to a visible
// character.
class _AnsiRun {
......
......@@ -14,6 +14,7 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
import '../application_package.dart';
import '../base/async_guard.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/net.dart';
......@@ -388,7 +389,10 @@ class _ExperimentalResidentWebRunner extends ResidentWebRunner {
return 1;
}
final String modeName = debuggingOptions.buildInfo.friendlyModeName;
globals.printStatus('Launching ${getDisplayPath(target)} on ${device.device.name} in $modeName mode...');
globals.printStatus(
'Launching ${fsUtils.getDisplayPath(target)} '
'on ${device.device.name} in $modeName mode...',
);
final String effectiveHostname = debuggingOptions.hostname ?? 'localhost';
final int hostPort = debuggingOptions.port == null
? await os.findFreePort()
......@@ -587,7 +591,9 @@ class _DwdsResidentWebRunner extends ResidentWebRunner {
}
final String modeName = debuggingOptions.buildInfo.friendlyModeName;
globals.printStatus(
'Launching ${getDisplayPath(target)} on ${device.device.name} in $modeName mode...');
'Launching ${fsUtils.getDisplayPath(target)} '
'on ${device.device.name} in $modeName mode...',
);
Status buildStatus;
bool statusActive = false;
try {
......
......@@ -82,14 +82,23 @@ class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
.listSync()
.whereType<Directory>();
for (final Directory childDirectory in childDirectories) {
final String path = globals.fs.path.join(testOutputDir, 'packages',
globals.fs.path.basename(childDirectory.path));
copyDirectorySync(childDirectory.childDirectory('lib'), globals.fs.directory(path));
final String path = globals.fs.path.join(
testOutputDir,
'packages',
globals.fs.path.basename(childDirectory.path),
);
fsUtils.copyDirectorySync(
childDirectory.childDirectory('lib'),
globals.fs.directory(path),
);
}
final Directory outputDirectory = rootDirectory
.childDirectory(projectName)
.childDirectory('test');
copyDirectorySync(outputDirectory, globals.fs.directory(globals.fs.path.join(testOutputDir)));
.childDirectory(projectName)
.childDirectory('test');
fsUtils.copyDirectorySync(
outputDirectory,
globals.fs.directory(globals.fs.path.join(testOutputDir)),
);
}
return success;
}
......
......@@ -124,8 +124,8 @@ class FlutterPlugins extends Target {
final FlutterProject project = FlutterProject.fromDirectory(environment.projectDir);
final List<Plugin> plugins = findPlugins(project);
final String pluginManifest = plugins
.map<String>((Plugin p) => '${p.name}=${escapePath(p.path)}')
.join('\n');
.map<String>((Plugin p) => '${p.name}=${fsUtils.escapePath(p.path)}')
.join('\n');
final File flutterPluginsFile = environment.projectDir.childFile('.flutter-plugins');
if (!flutterPluginsFile.existsSync() || flutterPluginsFile.readAsStringSync() != pluginManifest) {
flutterPluginsFile.writeAsStringSync(pluginManifest);
......
......@@ -346,7 +346,10 @@ class Cache {
/// [entity] doesn't exist.
bool isOlderThanToolsStamp(FileSystemEntity entity) {
final File flutterToolsStamp = getStampFileFor('flutter_tools');
return isOlderThanReference(entity: entity, referenceFile: flutterToolsStamp);
return fsUtils.isOlderThanReference(
entity: entity,
referenceFile: flutterToolsStamp,
);
}
bool isUpToDate() => _artifacts.every((ArtifactSet artifact) => artifact.isUpToDate());
......@@ -912,7 +915,7 @@ class AndroidMavenArtifacts extends ArtifactSet {
);
try {
final String gradleExecutable = gradle.absolute.path;
final String flutterSdk = escapePath(Cache.flutterRoot);
final String flutterSdk = fsUtils.escapePath(Cache.flutterRoot);
final RunResult processResult = await processUtils.run(
<String>[
gradleExecutable,
......
......@@ -11,7 +11,6 @@ import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/utils.dart';
import '../cache.dart';
import '../commands/daemon.dart';
import '../compile.dart';
......
......@@ -650,7 +650,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
int _injectGradleWrapper(FlutterProject project) {
int filesCreated = 0;
copyDirectorySync(
fsUtils.copyDirectorySync(
globals.cache.getArtifactDirectory('gradle_wrapper'),
project.android.hostAppGradleRoot,
onFileCopied: (File sourceFile, File destinationFile) {
......
......@@ -8,6 +8,7 @@ import 'package:args/command_runner.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/time.dart';
import '../base/utils.dart';
import '../build_info.dart';
......
......@@ -6,7 +6,6 @@ import 'dart:async';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/utils.dart';
import '../convert.dart';
import '../device.dart';
import '../globals.dart' as globals;
......@@ -110,7 +109,11 @@ class ScreenshotCommand extends FlutterCommand {
}
Future<void> runScreenshot(File outputFile) async {
outputFile ??= getUniqueFile(globals.fs.currentDirectory, 'flutter', 'png');
outputFile ??= fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'png',
);
try {
await device.takeScreenshot(outputFile);
} catch (error) {
......@@ -121,7 +124,11 @@ class ScreenshotCommand extends FlutterCommand {
Future<void> runSkia(File outputFile) async {
final Map<String, dynamic> skp = await _invokeVmServiceRpc('_flutter.screenshotSkp');
outputFile ??= getUniqueFile(globals.fs.currentDirectory, 'flutter', 'skp');
outputFile ??= fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'skp',
);
final IOSink sink = outputFile.openWrite();
sink.add(base64.decode(skp['skp'] as String));
await sink.close();
......@@ -131,7 +138,11 @@ class ScreenshotCommand extends FlutterCommand {
Future<void> runRasterizer(File outputFile) async {
final Map<String, dynamic> response = await _invokeVmServiceRpc('_flutter.screenshot');
outputFile ??= getUniqueFile(globals.fs.currentDirectory, 'flutter', 'png');
outputFile ??= fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'png',
);
final IOSink sink = outputFile.openWrite();
sink.add(base64.decode(response['screenshot'] as String));
await sink.close();
......
......@@ -189,7 +189,7 @@ class ArtifactUnpacker {
final String sourcePath = globals.fs.path.join(sourceDirectory, entityName);
final String targetPath = globals.fs.path.join(targetDirectory, entityName);
if (entityName.endsWith('/')) {
copyDirectorySync(
fsUtils.copyDirectorySync(
globals.fs.directory(sourcePath),
globals.fs.directory(targetPath),
);
......
......@@ -551,7 +551,10 @@ Future<XcodeBuildResult> buildXcodeProject({
// (for example, kernel binary files produced from previous run).
globals.fs.directory(outputDir).deleteSync(recursive: true);
}
copyDirectorySync(globals.fs.directory(expectedOutputDirectory), globals.fs.directory(outputDir));
fsUtils.copyDirectorySync(
globals.fs.directory(expectedOutputDirectory),
globals.fs.directory(outputDir),
);
} else {
globals.printError('Build succeeded but the expected app at $expectedOutputDirectory not found');
}
......
......@@ -25,7 +25,10 @@ abstract class PersistentToolState {
class _DefaultPersistentToolState implements PersistentToolState {
_DefaultPersistentToolState([File configFile]) :
_config = Config(configFile ?? globals.fs.file(globals.fs.path.join(userHomePath(), _kFileName)));
_config = Config(configFile ?? globals.fs.file(globals.fs.path.join(
fsUtils.userHomePath,
_kFileName,
)));
static const String _kFileName = '.flutter_tool_state';
static const String _kRedisplayWelcomeMessage = 'redisplay-welcome-message';
......
......@@ -325,7 +325,7 @@ bool _writeFlutterPluginsList(FlutterProject project, List<Plugin> plugins) {
pluginNames.add(plugin.name);
}
for (final Plugin plugin in plugins) {
flutterPluginsBuffer.write('${plugin.name}=${escapePath(plugin.path)}\n');
flutterPluginsBuffer.write('${plugin.name}=${fsUtils.escapePath(plugin.path)}\n');
directAppDependencies.add(<String, dynamic>{
'name': plugin.name,
// Extract the plugin dependencies which happen to be plugins.
......
......@@ -467,7 +467,10 @@ class IosProject implements XcodeBasedProject {
if (!isModule) {
return;
}
final bool pubspecChanged = isOlderThanReference(entity: ephemeralDirectory, referenceFile: parent.pubspecFile);
final bool pubspecChanged = fsUtils.isOlderThanReference(
entity: ephemeralDirectory,
referenceFile: parent.pubspecFile,
);
final bool toolingChanged = globals.cache.isOlderThanToolsStamp(ephemeralDirectory);
if (!pubspecChanged && !toolingChanged) {
return;
......@@ -478,22 +481,37 @@ class IosProject implements XcodeBasedProject {
.childDirectory('engine');
_deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'library'), ephemeralDirectory);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory,
);
// Add ephemeral host app, if a editable host app does not already exist.
if (!_editableDirectory.existsSync()) {
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'host_app_ephemeral'), ephemeralDirectory);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
ephemeralDirectory,
);
if (hasPlugins(parent)) {
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), ephemeralDirectory);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
ephemeralDirectory,
);
}
// Copy podspec and framework from engine cache. The actual build mode
// doesn't actually matter as it will be overwritten by xcode_backend.sh.
// However, cocoapods will run before that script and requires something
// to be in this location.
final Directory framework = globals.fs.directory(globals.artifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios, mode: BuildMode.debug));
final Directory framework = globals.fs.directory(
globals.artifacts.getArtifactPath(Artifact.flutterFramework,
platform: TargetPlatform.ios,
mode: BuildMode.debug,
));
if (framework.existsSync()) {
final File podspec = framework.parent.childFile('Flutter.podspec');
copyDirectorySync(framework, engineDest.childDirectory('Flutter.framework'));
fsUtils.copyDirectorySync(
framework,
engineDest.childDirectory('Flutter.framework'),
);
podspec.copySync(engineDest.childFile('Flutter.podspec').path);
}
}
......@@ -505,20 +523,36 @@ class IosProject implements XcodeBasedProject {
throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
}
_deleteIfExistsSync(ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'library'), ephemeralDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'host_app_ephemeral'), _editableDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'), _editableDirectory);
_overwriteFromTemplate(globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'), _editableDirectory);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'library'),
ephemeralDirectory,
);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
_editableDirectory,
);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
_editableDirectory,
);
_overwriteFromTemplate(
globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'),
_editableDirectory,
);
await _updateGeneratedXcodeConfigIfNeeded();
await injectPlugins(parent);
}
@override
File get generatedXcodePropertiesFile => _flutterLibRoot.childDirectory('Flutter').childFile('Generated.xcconfig');
File get generatedXcodePropertiesFile => _flutterLibRoot
.childDirectory('Flutter')
.childFile('Generated.xcconfig');
Directory get pluginRegistrantHost {
return isModule
? _flutterLibRoot.childDirectory('Flutter').childDirectory('FlutterPluginRegistrant')
? _flutterLibRoot
.childDirectory('Flutter')
.childDirectory('FlutterPluginRegistrant')
: hostAppRoot.childDirectory(_hostAppBundleName);
}
......@@ -632,8 +666,10 @@ class AndroidProject {
}
bool _shouldRegenerateFromTemplate() {
return isOlderThanReference(entity: ephemeralDirectory, referenceFile: parent.pubspecFile)
|| globals.cache.isOlderThanToolsStamp(ephemeralDirectory);
return fsUtils.isOlderThanReference(
entity: ephemeralDirectory,
referenceFile: parent.pubspecFile,
) || globals.cache.isOlderThanToolsStamp(ephemeralDirectory);
}
Future<void> makeHostAppEditable() async {
......
......@@ -405,7 +405,10 @@ class FlutterDevice {
}) async {
final bool prebuiltMode = hotRunner.applicationBinary != null;
final String modeName = hotRunner.debuggingOptions.buildInfo.friendlyModeName;
globals.printStatus('Launching ${getDisplayPath(hotRunner.mainPath)} on ${device.name} in $modeName mode...');
globals.printStatus(
'Launching ${fsUtils.getDisplayPath(hotRunner.mainPath)} '
'on ${device.name} in $modeName mode...',
);
final TargetPlatform targetPlatform = await device.targetPlatform;
package = await ApplicationPackageFactory.instance.getPackageForPlatform(
......@@ -472,9 +475,15 @@ class FlutterDevice {
final bool prebuiltMode = coldRunner.applicationBinary != null;
if (coldRunner.mainPath == null) {
assert(prebuiltMode);
globals.printStatus('Launching ${package.displayName} on ${device.name} in $modeName mode...');
globals.printStatus(
'Launching ${package.displayName} '
'on ${device.name} in $modeName mode...',
);
} else {
globals.printStatus('Launching ${getDisplayPath(coldRunner.mainPath)} on ${device.name} in $modeName mode...');
globals.printStatus(
'Launching ${fsUtils.getDisplayPath(coldRunner.mainPath)} '
'on ${device.name} in $modeName mode...',
);
}
if (package == null) {
......@@ -849,8 +858,15 @@ abstract class ResidentRunner {
Future<void> screenshot(FlutterDevice device) async {
assert(device.device.supportsScreenshot);
final Status status = globals.logger.startProgress('Taking screenshot for ${device.device.name}...', timeout: timeoutConfiguration.fastOperation);
final File outputFile = getUniqueFile(globals.fs.currentDirectory, 'flutter', 'png');
final Status status = globals.logger.startProgress(
'Taking screenshot for ${device.device.name}...',
timeout: timeoutConfiguration.fastOperation,
);
final File outputFile = fsUtils.getUniqueFile(
globals.fs.currentDirectory,
'flutter',
'png',
);
try {
if (supportsServiceProtocol && isRunningDebug) {
await device.refreshViews();
......@@ -881,7 +897,9 @@ abstract class ResidentRunner {
}
final int sizeKB = outputFile.lengthSync() ~/ 1024;
status.stop();
globals.printStatus('Screenshot written to ${globals.fs.path.relative(outputFile.path)} (${sizeKB}kB).');
globals.printStatus(
'Screenshot written to ${globals.fs.path.relative(outputFile.path)} (${sizeKB}kB).',
);
} catch (error) {
status.cancel();
globals.printError('Error taking screenshot: $error');
......
......@@ -160,7 +160,7 @@ class TestCompiler {
// The idea is to keep the cache file up-to-date and include as
// much as possible in an effort to re-use as many packages as
// possible.
ensureDirectoryExists(testFilePath);
fsUtils.ensureDirectoryExists(testFilePath);
await outputFile.copy(testFilePath);
}
request.result.complete(kernelReadyToRun.path);
......
......@@ -4,41 +4,41 @@
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import '../../src/common.dart';
import '../../src/context.dart';
class MockPlatform extends Mock implements Platform {}
void main() {
group('ensureDirectoryExists', () {
MemoryFileSystem fs;
FileSystemUtils fsUtils;
setUp(() {
fs = MemoryFileSystem();
fsUtils = FileSystemUtils(
fileSystem: fs,
platform: MockPlatform(),
);
});
testUsingContext('recursively creates a directory if it does not exist', () async {
ensureDirectoryExists('foo/bar/baz.flx');
testWithoutContext('recursively creates a directory if it does not exist', () async {
fsUtils.ensureDirectoryExists('foo/bar/baz.flx');
expect(fs.isDirectorySync('foo/bar'), true);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('throws tool exit on failure to create', () async {
testWithoutContext('throws tool exit on failure to create', () async {
fs.file('foo').createSync();
expect(() => ensureDirectoryExists('foo/bar.flx'), throwsToolExit());
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
expect(() => fsUtils.ensureDirectoryExists('foo/bar.flx'), throwsToolExit());
});
});
group('copyDirectorySync', () {
/// Test file_systems.copyDirectorySync() using MemoryFileSystem.
/// Copies between 2 instances of file systems which is also supported by copyDirectorySync().
test('test directory copy', () async {
testWithoutContext('test directory copy', () async {
final MemoryFileSystem sourceMemoryFs = MemoryFileSystem();
const String sourcePath = '/some/origin';
final Directory sourceDirectory = await sourceMemoryFs.directory(sourcePath).create(recursive: true);
......@@ -52,7 +52,12 @@ void main() {
final MemoryFileSystem targetMemoryFs = MemoryFileSystem();
const String targetPath = '/some/non-existent/target';
final Directory targetDirectory = targetMemoryFs.directory(targetPath);
copyDirectorySync(sourceDirectory, targetDirectory);
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: sourceMemoryFs,
platform: MockPlatform(),
);
fsUtils.copyDirectorySync(sourceDirectory, targetDirectory);
expect(targetDirectory.existsSync(), true);
targetMemoryFs.currentDirectory = targetPath;
......@@ -66,16 +71,21 @@ void main() {
expect(sourceMemoryFs.directory(sourcePath).listSync().length, 3);
});
testUsingContext('Skip files if shouldCopyFile returns false', () {
final Directory origin = globals.fs.directory('/origin');
testWithoutContext('Skip files if shouldCopyFile returns false', () {
final MemoryFileSystem fileSystem = MemoryFileSystem();
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: fileSystem,
platform: MockPlatform(),
);
final Directory origin = fileSystem.directory('/origin');
origin.createSync();
globals.fs.file(globals.fs.path.join('origin', 'a.txt')).writeAsStringSync('irrelevant');
globals.fs.directory('/origin/nested').createSync();
globals.fs.file(globals.fs.path.join('origin', 'nested', 'a.txt')).writeAsStringSync('irrelevant');
globals.fs.file(globals.fs.path.join('origin', 'nested', 'b.txt')).writeAsStringSync('irrelevant');
fileSystem.file(fileSystem.path.join('origin', 'a.txt')).writeAsStringSync('irrelevant');
fileSystem.directory('/origin/nested').createSync();
fileSystem.file(fileSystem.path.join('origin', 'nested', 'a.txt')).writeAsStringSync('irrelevant');
fileSystem.file(fileSystem.path.join('origin', 'nested', 'b.txt')).writeAsStringSync('irrelevant');
final Directory destination = globals.fs.directory('/destination');
copyDirectorySync(origin, destination, shouldCopyFile: (File origin, File dest) {
final Directory destination = fileSystem.directory('/destination');
fsUtils.copyDirectorySync(origin, destination, shouldCopyFile: (File origin, File dest) {
return origin.basename == 'b.txt';
});
......@@ -85,53 +95,30 @@ void main() {
expect(destination.childFile('a.txt').existsSync(), isFalse);
expect(destination.childDirectory('nested').childFile('a.txt').existsSync(), isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
ProcessManager: () => FakeProcessManager.any(),
});
});
group('canonicalizePath', () {
test('does not lowercase on Windows', () {
String path = 'C:\\Foo\\bAr\\cOOL.dart';
expect(canonicalizePath(path), path);
// globals.fs.path.canonicalize does lowercase on Windows
expect(globals.fs.path.canonicalize(path), isNot(path));
path = '..\\bar\\.\\\\Foo';
final String expected = globals.fs.path.join(globals.fs.currentDirectory.parent.absolute.path, 'bar', 'Foo');
expect(canonicalizePath(path), expected);
// globals.fs.path.canonicalize should return the same result (modulo casing)
expect(globals.fs.path.canonicalize(path), expected.toLowerCase());
}, testOn: 'windows');
test('does not lowercase on posix', () {
String path = '/Foo/bAr/cOOL.dart';
expect(canonicalizePath(path), path);
// globals.fs.path.canonicalize and canonicalizePath should be the same on Posix
expect(globals.fs.path.canonicalize(path), path);
path = '../bar/.//Foo';
final String expected = globals.fs.path.join(globals.fs.currentDirectory.parent.absolute.path, 'bar', 'Foo');
expect(canonicalizePath(path), expected);
}, testOn: 'posix');
});
group('escapePath', () {
testUsingContext('on Windows', () {
expect(escapePath('C:\\foo\\bar\\cool.dart'), 'C:\\\\foo\\\\bar\\\\cool.dart');
expect(escapePath('foo\\bar\\cool.dart'), 'foo\\\\bar\\\\cool.dart');
expect(escapePath('C:/foo/bar/cool.dart'), 'C:/foo/bar/cool.dart');
}, overrides: <Type, Generator>{
Platform: () => FakePlatform(operatingSystem: 'windows'),
testWithoutContext('on Windows', () {
final MemoryFileSystem fileSystem = MemoryFileSystem();
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: fileSystem,
platform: FakePlatform(operatingSystem: 'windows'),
);
expect(fsUtils.escapePath('C:\\foo\\bar\\cool.dart'), 'C:\\\\foo\\\\bar\\\\cool.dart');
expect(fsUtils.escapePath('foo\\bar\\cool.dart'), 'foo\\\\bar\\\\cool.dart');
expect(fsUtils.escapePath('C:/foo/bar/cool.dart'), 'C:/foo/bar/cool.dart');
});
testUsingContext('on Linux', () {
expect(escapePath('/foo/bar/cool.dart'), '/foo/bar/cool.dart');
expect(escapePath('foo/bar/cool.dart'), 'foo/bar/cool.dart');
expect(escapePath('foo\\cool.dart'), 'foo\\cool.dart');
}, overrides: <Type, Generator>{
Platform: () => FakePlatform(operatingSystem: 'linux'),
testWithoutContext('on Linux', () {
final MemoryFileSystem fileSystem = MemoryFileSystem();
final FileSystemUtils fsUtils = FileSystemUtils(
fileSystem: fileSystem,
platform: FakePlatform(operatingSystem: 'linux'),
);
expect(fsUtils.escapePath('/foo/bar/cool.dart'), '/foo/bar/cool.dart');
expect(fsUtils.escapePath('foo/bar/cool.dart'), 'foo/bar/cool.dart');
expect(fsUtils.escapePath('foo\\cool.dart'), 'foo\\cool.dart');
});
});
}
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