1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:file/file.dart';
6import 'package:file/local.dart' as local_fs;
7import 'package:meta/meta.dart';
8
9import 'common.dart';
10import 'io.dart';
11import 'platform.dart';
12import 'process.dart';
13import 'signals.dart';
14
15// package:file/local.dart must not be exported. This exposes LocalFileSystem,
16// which we override to ensure that temporary directories are cleaned up when
17// the tool is killed by a signal.
18export 'package:file/file.dart';
19
20/// Exception indicating that a file that was expected to exist was not found.
21class FileNotFoundException implements IOException {
22 const FileNotFoundException(this.path);
23
24 final String path;
25
26 @override
27 String toString() => 'File not found: $path';
28}
29
30/// Various convenience file system methods.
31class FileSystemUtils {
32 FileSystemUtils({
33 required FileSystem fileSystem,
34 required Platform platform,
35 }) : _fileSystem = fileSystem,
36 _platform = platform;
37
38 final FileSystem _fileSystem;
39
40 final Platform _platform;
41
42 /// Appends a number to a filename in order to make it unique under a
43 /// directory.
44 File getUniqueFile(Directory dir, String baseName, String ext) {
45 return _getUniqueFile(dir, baseName, ext);
46 }
47
48 /// Appends a number to a directory name in order to make it unique under a
49 /// directory.
50 Directory getUniqueDirectory(Directory dir, String baseName) {
51 final FileSystem fs = dir.fileSystem;
52 int i = 1;
53
54 while (true) {
55 final String name = '${baseName}_${i.toString().padLeft(2, '0')}';
56 final Directory directory = fs.directory(_fileSystem.path.join(dir.path, name));
57 if (!directory.existsSync()) {
58 return directory;
59 }
60 i += 1;
61 }
62 }
63
64 /// Escapes [path].
65 ///
66 /// On Windows it replaces all '\' with '\\'. On other platforms, it returns the
67 /// path unchanged.
68 String escapePath(String path) => _platform.isWindows ? path.replaceAll(r'\', r'\\') : path;
69
70 /// Returns true if the file system [entity] has not been modified since the
71 /// latest modification to [referenceFile].
72 ///
73 /// Returns true, if [entity] does not exist.
74 ///
75 /// Returns false, if [entity] exists, but [referenceFile] does not.
76 bool isOlderThanReference({
77 required FileSystemEntity entity,
78 required File referenceFile,
79 }) {
80 if (!entity.existsSync()) {
81 return true;
82 }
83 return referenceFile.existsSync()
84 && referenceFile.statSync().modified.isAfter(entity.statSync().modified);
85 }
86
87 /// Return the absolute path of the user's home directory.
88 String? get homeDirPath {
89 String? path = _platform.isWindows
90 ? _platform.environment['USERPROFILE']
91 : _platform.environment['HOME'];
92 if (path != null) {
93 path = _fileSystem.path.absolute(path);
94 }
95 return path;
96 }
97}
98
99/// Return a relative path if [fullPath] is contained by the cwd, else return an
100/// absolute path.
101String getDisplayPath(String fullPath, FileSystem fileSystem) {
102 final String cwd = fileSystem.currentDirectory.path + fileSystem.path.separator;
103 return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
104}
105
106/// Creates `destDir` if needed, then recursively copies `srcDir` to
107/// `destDir`, invoking [onFileCopied], if specified, for each
108/// source/destination file pair.
109///
110/// Skips files if [shouldCopyFile] returns `false`.
111/// Does not recurse over directories if [shouldCopyDirectory] returns `false`.
112///
113/// If [followLinks] is false, then any symbolic links found are reported as
114/// [Link] objects, rather than as directories or files, and are not recursed into.
115///
116/// If [followLinks] is true, then working links are reported as directories or
117/// files, depending on what they point to.
118void copyDirectory(
119 Directory srcDir,
120 Directory destDir, {
121 bool Function(File srcFile, File destFile)? shouldCopyFile,
122 bool Function(Directory)? shouldCopyDirectory,
123 void Function(File srcFile, File destFile)? onFileCopied,
124 bool followLinks = true,
125}) {
126 if (!srcDir.existsSync()) {
127 throw Exception('Source directory "${srcDir.path}" does not exist, nothing to copy');
128 }
129
130 if (!destDir.existsSync()) {
131 destDir.createSync(recursive: true);
132 }
133
134 for (final FileSystemEntity entity in srcDir.listSync(followLinks: followLinks)) {
135 final String newPath = destDir.fileSystem.path.join(destDir.path, entity.basename);
136 if (entity is Link) {
137 final Link newLink = destDir.fileSystem.link(newPath);
138 newLink.createSync(entity.targetSync());
139 } else if (entity is File) {
140 final File newFile = destDir.fileSystem.file(newPath);
141 if (shouldCopyFile != null && !shouldCopyFile(entity, newFile)) {
142 continue;
143 }
144 newFile.writeAsBytesSync(entity.readAsBytesSync());
145 onFileCopied?.call(entity, newFile);
146 } else if (entity is Directory) {
147 if (shouldCopyDirectory != null && !shouldCopyDirectory(entity)) {
148 continue;
149 }
150 copyDirectory(
151 entity,
152 destDir.fileSystem.directory(newPath),
153 shouldCopyFile: shouldCopyFile,
154 onFileCopied: onFileCopied,
155 followLinks: followLinks,
156 );
157 } else {
158 throw Exception('${entity.path} is neither File nor Directory, was ${entity.runtimeType}');
159 }
160 }
161}
162
163File _getUniqueFile(Directory dir, String baseName, String ext) {
164 final FileSystem fs = dir.fileSystem;
165 int i = 1;
166
167 while (true) {
168 final String name = '${baseName}_${i.toString().padLeft(2, '0')}.$ext';
169 final File file = fs.file(dir.fileSystem.path.join(dir.path, name));
170 if (!file.existsSync()) {
171 file.createSync(recursive: true);
172 return file;
173 }
174 i += 1;
175 }
176}
177
178/// Appends a number to a filename in order to make it unique under a
179/// directory.
180File getUniqueFile(Directory dir, String baseName, String ext) {
181 return _getUniqueFile(dir, baseName, ext);
182}
183
184/// This class extends [local_fs.LocalFileSystem] in order to clean up
185/// directories and files that the tool creates under the system temporary
186/// directory when the tool exits either normally or when killed by a signal.
187class LocalFileSystem extends local_fs.LocalFileSystem {
188 LocalFileSystem(this._signals, this._fatalSignals, this.shutdownHooks);
189
190 @visibleForTesting
191 LocalFileSystem.test({
192 required Signals signals,
193 List<ProcessSignal> fatalSignals = Signals.defaultExitSignals,
194 }) : this(signals, fatalSignals, ShutdownHooks());
195
196 Directory? _systemTemp;
197 final Map<ProcessSignal, Object> _signalTokens = <ProcessSignal, Object>{};
198
199 final ShutdownHooks shutdownHooks;
200
201 Future<void> dispose() async {
202 _tryToDeleteTemp();
203 for (final MapEntry<ProcessSignal, Object> signalToken in _signalTokens.entries) {
204 await _signals.removeHandler(signalToken.key, signalToken.value);
205 }
206 _signalTokens.clear();
207 }
208
209 final Signals _signals;
210 final List<ProcessSignal> _fatalSignals;
211
212 void _tryToDeleteTemp() {
213 try {
214 if (_systemTemp?.existsSync() ?? false) {
215 _systemTemp?.deleteSync(recursive: true);
216 }
217 } on FileSystemException {
218 // ignore
219 }
220 _systemTemp = null;
221 }
222
223 // This getter returns a fresh entry under /tmp, like
224 // /tmp/flutter_tools.abcxyz, then the rest of the tool creates /tmp entries
225 // under that, like /tmp/flutter_tools.abcxyz/flutter_build_stuff.123456.
226 // Right before exiting because of a signal or otherwise, we delete
227 // /tmp/flutter_tools.abcxyz, not the whole of /tmp.
228 @override
229 Directory get systemTempDirectory {
230 if (_systemTemp == null) {
231 if (!superSystemTempDirectory.existsSync()) {
232 throwToolExit('Your system temp directory (${superSystemTempDirectory.path}) does not exist. '
233 'Did you set an invalid override in your environment? See issue https://github.com/flutter/flutter/issues/74042 for more context.'
234 );
235 }
236 _systemTemp = superSystemTempDirectory.createTempSync('flutter_tools.')
237 ..createSync(recursive: true);
238 // Make sure that the temporary directory is cleaned up if the tool is
239 // killed by a signal.
240 for (final ProcessSignal signal in _fatalSignals) {
241 final Object token = _signals.addHandler(
242 signal,
243 (ProcessSignal _) {
244 _tryToDeleteTemp();
245 },
246 );
247 _signalTokens[signal] = token;
248 }
249 // Make sure that the temporary directory is cleaned up when the tool
250 // exits normally.
251 shutdownHooks.addShutdownHook(
252 _tryToDeleteTemp,
253 );
254 }
255 return _systemTemp!;
256 }
257
258 // This only exist because the memory file system does not support a systemTemp that does not exists #74042
259 @visibleForTesting
260 Directory get superSystemTempDirectory => super.systemTempDirectory;
261}
262

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com