mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
254 lines
8.3 KiB
Dart
254 lines
8.3 KiB
Dart
// Copyright 2014 The Flutter 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 'package:file/file.dart';
|
|
import 'package:file/local.dart' as local_fs;
|
|
import 'package:meta/meta.dart';
|
|
|
|
import 'common.dart';
|
|
import 'io.dart';
|
|
import 'platform.dart';
|
|
import 'process.dart';
|
|
import 'signals.dart';
|
|
|
|
// package:file/local.dart must not be exported. This exposes LocalFileSystem,
|
|
// which we override to ensure that temporary directories are cleaned up when
|
|
// the tool is killed by a signal.
|
|
export 'package:file/file.dart';
|
|
|
|
/// 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';
|
|
}
|
|
|
|
/// Various convenience file system methods.
|
|
class FileSystemUtils {
|
|
FileSystemUtils({
|
|
required FileSystem fileSystem,
|
|
required Platform platform,
|
|
}) : _fileSystem = fileSystem,
|
|
_platform = platform;
|
|
|
|
final FileSystem _fileSystem;
|
|
|
|
final Platform _platform;
|
|
|
|
/// Appends a number to a filename in order to make it unique under a
|
|
/// directory.
|
|
File getUniqueFile(Directory dir, String baseName, String ext) {
|
|
return _getUniqueFile(dir, baseName, ext);
|
|
}
|
|
|
|
/// Appends a number to a directory name in order to make it unique under a
|
|
/// directory.
|
|
Directory getUniqueDirectory(Directory dir, String baseName) {
|
|
final FileSystem fs = dir.fileSystem;
|
|
int i = 1;
|
|
|
|
while (true) {
|
|
final String name = '${baseName}_${i.toString().padLeft(2, '0')}';
|
|
final Directory directory = fs.directory(_fileSystem.path.join(dir.path, name));
|
|
if (!directory.existsSync()) {
|
|
return directory;
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
/// Escapes [path].
|
|
///
|
|
/// On Windows it replaces all '\' with '\\'. On other platforms, it returns the
|
|
/// path unchanged.
|
|
String escapePath(String path) => _platform.isWindows ? path.replaceAll(r'\', r'\\') : 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);
|
|
}
|
|
|
|
/// Return the absolute path of the user's home directory.
|
|
String? get homeDirPath {
|
|
String? path = _platform.isWindows
|
|
? _platform.environment['USERPROFILE']
|
|
: _platform.environment['HOME'];
|
|
if (path != null) {
|
|
path = _fileSystem.path.absolute(path);
|
|
}
|
|
return path;
|
|
}
|
|
}
|
|
|
|
/// Return a relative path if [fullPath] is contained by the cwd, else return an
|
|
/// absolute path.
|
|
String getDisplayPath(String fullPath, FileSystem fileSystem) {
|
|
final String cwd = fileSystem.currentDirectory.path + fileSystem.path.separator;
|
|
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
|
|
}
|
|
|
|
/// 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`.
|
|
/// Does not recurse over directories if [shouldCopyDirectory] returns `false`.
|
|
void copyDirectory(
|
|
Directory srcDir,
|
|
Directory destDir, {
|
|
bool Function(File srcFile, File destFile)? shouldCopyFile,
|
|
bool Function(Directory)? shouldCopyDirectory,
|
|
void Function(File srcFile, File destFile)? onFileCopied,
|
|
}) {
|
|
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 Link) {
|
|
final Link newLink = destDir.fileSystem.link(newPath);
|
|
newLink.createSync(entity.targetSync());
|
|
} else 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) {
|
|
if (shouldCopyDirectory != null && !shouldCopyDirectory(entity)) {
|
|
continue;
|
|
}
|
|
copyDirectory(
|
|
entity,
|
|
destDir.fileSystem.directory(newPath),
|
|
shouldCopyFile: shouldCopyFile,
|
|
onFileCopied: onFileCopied,
|
|
);
|
|
} else {
|
|
throw Exception('${entity.path} is neither File nor Directory, was ${entity.runtimeType}');
|
|
}
|
|
}
|
|
}
|
|
|
|
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(dir.fileSystem.path.join(dir.path, name));
|
|
if (!file.existsSync()) {
|
|
file.createSync(recursive: true);
|
|
return file;
|
|
}
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
/// Appends a number to a filename in order to make it unique under a
|
|
/// directory.
|
|
File getUniqueFile(Directory dir, String baseName, String ext) {
|
|
return _getUniqueFile(dir, baseName, ext);
|
|
}
|
|
|
|
/// This class extends [local_fs.LocalFileSystem] in order to clean up
|
|
/// directories and files that the tool creates under the system temporary
|
|
/// directory when the tool exits either normally or when killed by a signal.
|
|
class LocalFileSystem extends local_fs.LocalFileSystem {
|
|
LocalFileSystem(this._signals, this._fatalSignals, this.shutdownHooks);
|
|
|
|
@visibleForTesting
|
|
LocalFileSystem.test({
|
|
required Signals signals,
|
|
List<ProcessSignal> fatalSignals = Signals.defaultExitSignals,
|
|
}) : this(signals, fatalSignals, ShutdownHooks());
|
|
|
|
Directory? _systemTemp;
|
|
final Map<ProcessSignal, Object> _signalTokens = <ProcessSignal, Object>{};
|
|
|
|
final ShutdownHooks shutdownHooks;
|
|
|
|
Future<void> dispose() async {
|
|
_tryToDeleteTemp();
|
|
for (final MapEntry<ProcessSignal, Object> signalToken in _signalTokens.entries) {
|
|
await _signals.removeHandler(signalToken.key, signalToken.value);
|
|
}
|
|
_signalTokens.clear();
|
|
}
|
|
|
|
final Signals _signals;
|
|
final List<ProcessSignal> _fatalSignals;
|
|
|
|
void _tryToDeleteTemp() {
|
|
try {
|
|
if (_systemTemp?.existsSync() ?? false) {
|
|
_systemTemp?.deleteSync(recursive: true);
|
|
}
|
|
} on FileSystemException {
|
|
// ignore
|
|
}
|
|
_systemTemp = null;
|
|
}
|
|
|
|
// This getter returns a fresh entry under /tmp, like
|
|
// /tmp/flutter_tools.abcxyz, then the rest of the tool creates /tmp entries
|
|
// under that, like /tmp/flutter_tools.abcxyz/flutter_build_stuff.123456.
|
|
// Right before exiting because of a signal or otherwise, we delete
|
|
// /tmp/flutter_tools.abcxyz, not the whole of /tmp.
|
|
@override
|
|
Directory get systemTempDirectory {
|
|
if (_systemTemp == null) {
|
|
if (!superSystemTempDirectory.existsSync()) {
|
|
throwToolExit('Your system temp directory (${superSystemTempDirectory.path}) does not exist. '
|
|
'Did you set an invalid override in your environment? See issue https://github.com/flutter/flutter/issues/74042 for more context.'
|
|
);
|
|
}
|
|
_systemTemp = superSystemTempDirectory.createTempSync('flutter_tools.')
|
|
..createSync(recursive: true);
|
|
// Make sure that the temporary directory is cleaned up if the tool is
|
|
// killed by a signal.
|
|
for (final ProcessSignal signal in _fatalSignals) {
|
|
final Object token = _signals.addHandler(
|
|
signal,
|
|
(ProcessSignal _) {
|
|
_tryToDeleteTemp();
|
|
},
|
|
);
|
|
_signalTokens[signal] = token;
|
|
}
|
|
// Make sure that the temporary directory is cleaned up when the tool
|
|
// exits normally.
|
|
shutdownHooks.addShutdownHook(
|
|
_tryToDeleteTemp,
|
|
);
|
|
}
|
|
return _systemTemp!;
|
|
}
|
|
|
|
// This only exist because the memory file system does not support a systemTemp that does not exists #74042
|
|
@visibleForTesting
|
|
Directory get superSystemTempDirectory => super.systemTempDirectory;
|
|
}
|