Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/flutter_tools/lib/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions packages/flutter_tools/lib/src/android/gradle_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand All @@ -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);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/flutter_tools/lib/src/base/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
207 changes: 124 additions & 83 deletions packages/flutter_tools/lib/src/base/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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] ?? '.';
}
}
9 changes: 9 additions & 0 deletions packages/flutter_tools/lib/src/base/io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import 'dart:io' as io
InternetAddressType,
IOSink,
NetworkInterface,
pid,
Process,
ProcessInfo,
ProcessSignal,
Expand Down Expand Up @@ -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();
Expand Down
36 changes: 3 additions & 33 deletions packages/flutter_tools/lib/src/base/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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();

Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Loading