Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Side by Side Diff: utils/pub/io.dart

Issue 12253054: Get rid of join() and encapsulate File and Directory in io.dart. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Rebase. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/pub/hosted_source.dart ('k') | utils/pub/oauth2.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /// Helper functionality to make working with IO easier. 5 /// Helper functionality to make working with IO easier.
6 library io; 6 library io;
7 7
8 import 'dart:async'; 8 import 'dart:async';
9 import 'dart:io'; 9 import 'dart:io';
10 import 'dart:isolate'; 10 import 'dart:isolate';
11 import 'dart:json'; 11 import 'dart:json';
12 import 'dart:uri'; 12 import 'dart:uri';
13 13
14 import '../../pkg/path/lib/path.dart' as path; 14 import '../../pkg/path/lib/path.dart' as path;
15 import '../../pkg/http/lib/http.dart' show ByteStream; 15 import '../../pkg/http/lib/http.dart' show ByteStream;
16 import 'error_group.dart'; 16 import 'error_group.dart';
17 import 'exit_codes.dart' as exit_codes; 17 import 'exit_codes.dart' as exit_codes;
18 import 'log.dart' as log; 18 import 'log.dart' as log;
19 import 'utils.dart'; 19 import 'utils.dart';
20 20
21 export '../../pkg/http/lib/http.dart' show ByteStream; 21 export '../../pkg/http/lib/http.dart' show ByteStream;
22 22
23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); 23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?");
24 24
25 /// Joins a number of path string parts into a single path. Handles
26 /// platform-specific path separators. Parts can be [String], [Directory], or
27 /// [File] objects.
28 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) {
29 var parts = [part1, part2, part3, part4, part5, part6, part7, part8]
30 .map((part) => part == null ? null : _getPath(part)).toList();
31
32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5],
33 parts[6], parts[7]);
34 }
35
36 /// Returns whether or not [entry] is nested somewhere within [dir]. This just 25 /// Returns whether or not [entry] is nested somewhere within [dir]. This just
37 /// performs a path comparison; it doesn't look at the actual filesystem. 26 /// performs a path comparison; it doesn't look at the actual filesystem.
38 bool isBeneath(String entry, String dir) { 27 bool isBeneath(String entry, String dir) {
39 var relative = path.relative(entry, from: dir); 28 var relative = path.relative(entry, from: dir);
40 return !path.isAbsolute(relative) && path.split(relative)[0] != '..'; 29 return !path.isAbsolute(relative) && path.split(relative)[0] != '..';
41 } 30 }
42 31
43 /// Determines if [path], which can be a [String] file path, a [File], or a 32 /// Determines if a file or directory at [path] exists.
44 /// [Directory] exists on the file system. 33 bool entryExists(String path) => fileExists(path) || dirExists(path);
45 bool entryExists(path) => fileExists(path) || dirExists(path);
46 34
47 /// Determines if [file], which can be a [String] file path or a [File], exists 35 /// Determines if [file] exists on the file system.
48 /// on the file system. 36 bool fileExists(String file) => new File(file).existsSync();
49 bool fileExists(file) => _getFile(file).existsSync();
50 37
51 /// Reads the contents of the text file [file], which can either be a [String] 38 /// Reads the contents of the text file [file].
52 /// or a [File]. 39 String readTextFile(String file) =>
53 String readTextFile(file) => _getFile(file).readAsStringSync(Encoding.UTF_8); 40 new File(file).readAsStringSync(Encoding.UTF_8);
54 41
55 /// Reads the contents of the binary file [file], which can either be a [String] 42 /// Reads the contents of the binary file [file].
56 /// or a [File]. 43 List<int> readBinaryFile(String file) {
57 List<int> readBinaryFile(file) { 44 log.io("Reading binary file $file.");
58 var path = _getPath(file); 45 var contents = new File(file).readAsBytesSync();
59 log.io("Reading binary file $path."); 46 log.io("Read ${contents.length} bytes from $file.");
60 var contents = new File(path).readAsBytesSync();
61 log.io("Read ${contents.length} bytes from $path.");
62 return contents; 47 return contents;
63 } 48 }
64 49
65 /// Creates [file] (which can either be a [String] or a [File]), and writes 50 /// Creates [file] and writes [contents] to it.
66 /// [contents] to it.
67 /// 51 ///
68 /// If [dontLogContents] is true, the contents of the file will never be logged. 52 /// If [dontLogContents] is true, the contents of the file will never be logged.
69 File writeTextFile(file, String contents, {dontLogContents: false}) { 53 String writeTextFile(String file, String contents, {dontLogContents: false}) {
70 var path = _getPath(file);
71 file = new File(path);
72
73 // Sanity check: don't spew a huge file. 54 // Sanity check: don't spew a huge file.
74 log.io("Writing ${contents.length} characters to text file $path."); 55 log.io("Writing ${contents.length} characters to text file $file.");
75 if (!dontLogContents && contents.length < 1024 * 1024) { 56 if (!dontLogContents && contents.length < 1024 * 1024) {
76 log.fine("Contents:\n$contents"); 57 log.fine("Contents:\n$contents");
77 } 58 }
78 59
79 return file..writeAsStringSync(contents); 60 new File(file).writeAsStringSync(contents);
80 }
81
82 /// Deletes [file], which can be a [String] or a [File].
83 File deleteFile(file) => _getFile(file)..delete();
84
85 /// Creates [file] (which can either be a [String] or a [File]), and writes
86 /// [contents] to it.
87 File writeBinaryFile(file, List<int> contents) {
88 var path = _getPath(file);
89 file = new File(path);
90
91 log.io("Writing ${contents.length} bytes to binary file $path.");
92 file.openSync(FileMode.WRITE)
93 ..writeListSync(contents, 0, contents.length)
94 ..closeSync();
95 log.fine("Wrote text file $path.");
96 return file; 61 return file;
97 } 62 }
98 63
99 /// Writes [stream] to a new file at [path], which may be a [String] or a 64 /// Deletes [file].
100 /// [File]. Will replace any file already at that path. Completes when the file 65 void deleteFile(String file) {
101 /// is done being written. 66 new File(file).delete();
102 Future<File> createFileFromStream(Stream<List<int>> stream, path) { 67 }
103 path = _getPath(path);
104 68
105 log.io("Creating $path from stream."); 69 /// Creates [file] and writes [contents] to it.
70 String writeBinaryFile(String file, List<int> contents) {
71 log.io("Writing ${contents.length} bytes to binary file $file.");
72 new File(file).openSync(FileMode.WRITE)
73 ..writeListSync(contents, 0, contents.length)
74 ..closeSync();
75 log.fine("Wrote text file $file.");
76 return file;
77 }
106 78
107 var file = new File(path); 79 /// Writes [stream] to a new file at path [file]. Will replace any file already
108 return stream.pipe(wrapOutputStream(file.openOutputStream())).then((_) { 80 /// at that path. Completes when the file is done being written.
109 log.fine("Created $path from stream."); 81 Future<String> createFileFromStream(Stream<List<int>> stream, String file) {
82 log.io("Creating $file from stream.");
83
84 var stream = new File(file).openOutputStream();
85 return stream.pipe(wrapOutputStream(stream)).then((_) {
86 log.fine("Created $file from stream.");
87 return file;
110 }); 88 });
111 } 89 }
112 90
113 /// Creates a directory [dir]. 91 /// Creates a directory [dir].
114 Directory createDir(dir) => _getDirectory(dir)..createSync(); 92 String createDir(String dir) {
93 new Directory(dir).createSync();
94 return dir;
95 }
115 96
116 /// Ensures that [dirPath] and all its parent directories exist. If they don't 97 /// Ensures that [dirPath] and all its parent directories exist. If they don't
117 /// exist, creates them. 98 /// exist, creates them.
118 Directory ensureDir(dirPath) { 99 String ensureDir(String dirPath) {
119 dirPath = _getPath(dirPath);
120
121 log.fine("Ensuring directory $dirPath exists."); 100 log.fine("Ensuring directory $dirPath exists.");
122 var dir = new Directory(dirPath); 101 var dir = new Directory(dirPath);
123 if (dirPath == '.' || dirExists(dirPath)) return dir; 102 if (dirPath == '.' || dirExists(dirPath)) return dirPath;
124 103
125 ensureDir(path.dirname(dirPath)); 104 ensureDir(path.dirname(dirPath));
126 105
127 try { 106 try {
128 createDir(dir); 107 createDir(dirPath);
129 } on DirectoryIOException catch (ex) { 108 } on DirectoryIOException catch (ex) {
130 // Error 17 means the directory already exists (or 183 on Windows). 109 // Error 17 means the directory already exists (or 183 on Windows).
131 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { 110 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) {
132 log.fine("Got 'already exists' error when creating directory."); 111 log.fine("Got 'already exists' error when creating directory.");
133 } else { 112 } else {
134 throw ex; 113 throw ex;
135 } 114 }
136 } 115 }
137 116
138 return dir; 117 return dirPath;
139 } 118 }
140 119
141 /// Creates a temp directory whose name will be based on [dir] with a unique 120 /// Creates a temp directory whose name will be based on [dir] with a unique
142 /// suffix appended to it. If [dir] is not provided, a temp directory will be 121 /// suffix appended to it. If [dir] is not provided, a temp directory will be
143 /// created in a platform-dependent temporary location. Returns the path of the 122 /// created in a platform-dependent temporary location. Returns the path of the
144 /// created directory. 123 /// created directory.
145 String createTempDir([dir = '']) { 124 String createTempDir([dir = '']) {
146 var tempDir = _getDirectory(dir).createTempSync(); 125 var tempDir = new Directory(dir).createTempSync();
147 log.io("Created temp directory ${tempDir.path}"); 126 log.io("Created temp directory ${tempDir.path}");
148 return tempDir.path; 127 return tempDir.path;
149 } 128 }
150 129
151 /// Asynchronously recursively deletes [dir], which can be a [String] or a 130 /// Asynchronously recursively deletes [dir]. Returns a [Future] that completes
152 /// [Directory]. Returns a [Future] that completes when the deletion is done. 131 /// when the deletion is done.
153 Future<Directory> deleteDir(dir) { 132 Future<String> deleteDir(String dir) {
154 dir = _getDirectory(dir); 133 return _attemptRetryable(() => log.ioAsync("delete directory $dir",
155 134 new Directory(dir).delete(recursive: true).then((_) => dir)));
156 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}",
157 dir.delete(recursive: true)));
158 } 135 }
159 136
160 /// Asynchronously lists the contents of [dir], which can be a [String] 137 /// Asynchronously lists the contents of [dir]. If [recursive] is `true`, lists
161 /// directory path or a [Directory]. If [recursive] is `true`, lists
162 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is 138 /// subdirectory contents (defaults to `false`). If [includeHiddenFiles] is
163 /// `true`, includes files and directories beginning with `.` (defaults to 139 /// `true`, includes files and directories beginning with `.` (defaults to
164 /// `false`). 140 /// `false`).
165 /// 141 ///
166 /// If [dir] is a string, the returned paths are guaranteed to begin with it. 142 /// If [dir] is a string, the returned paths are guaranteed to begin with it.
167 Future<List<String>> listDir(dir, 143 Future<List<String>> listDir(String dir,
168 {bool recursive: false, bool includeHiddenFiles: false}) { 144 {bool recursive: false, bool includeHiddenFiles: false}) {
169 Future<List<String>> doList(Directory dir, Set<String> listedDirectories) { 145 Future<List<String>> doList(String dir, Set<String> listedDirectories) {
170 var contents = <String>[]; 146 var contents = <String>[];
171 var completer = new Completer<List<String>>(); 147 var completer = new Completer<List<String>>();
172 148
173 // Avoid recursive symlinks. 149 // Avoid recursive symlinks.
174 var resolvedPath = new File(dir.path).fullPathSync(); 150 var resolvedPath = new File(dir).fullPathSync();
175 if (listedDirectories.contains(resolvedPath)) { 151 if (listedDirectories.contains(resolvedPath)) {
176 return new Future.immediate([]); 152 return new Future.immediate([]);
177 } 153 }
178 154
179 listedDirectories = new Set<String>.from(listedDirectories); 155 listedDirectories = new Set<String>.from(listedDirectories);
180 listedDirectories.add(resolvedPath); 156 listedDirectories.add(resolvedPath);
181 157
182 log.io("Listing directory ${dir.path}."); 158 log.io("Listing directory $dir.");
183 var lister = dir.list(); 159 var lister = new Directory(dir).list();
184 160
185 lister.onDone = (done) { 161 lister.onDone = (done) {
186 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile 162 // TODO(rnystrom): May need to sort here if it turns out onDir and onFile
187 // aren't guaranteed to be called in a certain order. So far, they seem to . 163 // aren't guaranteed to be called in a certain order. So far, they seem to .
188 if (done) { 164 if (done) {
189 log.fine("Listed directory ${dir.path}:\n" 165 log.fine("Listed directory $dir:\n${contents.join('\n')}");
190 "${contents.join('\n')}");
191 completer.complete(contents); 166 completer.complete(contents);
192 } 167 }
193 }; 168 };
194 169
195 // TODO(nweiz): remove this when issue 4061 is fixed. 170 // TODO(nweiz): remove this when issue 4061 is fixed.
196 var stackTrace; 171 var stackTrace;
197 try { 172 try {
198 throw ""; 173 throw "";
199 } catch (_, localStackTrace) { 174 } catch (_, localStackTrace) {
200 stackTrace = localStackTrace; 175 stackTrace = localStackTrace;
201 } 176 }
202 177
203 var children = []; 178 var children = [];
204 lister.onError = (error) => completer.completeError(error, stackTrace); 179 lister.onError = (error) => completer.completeError(error, stackTrace);
205 lister.onDir = (file) { 180 lister.onDir = (file) {
206 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; 181 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return;
207 file = join(dir, path.basename(file)); 182 file = path.join(dir, path.basename(file));
208 contents.add(file); 183 contents.add(file);
209 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that 184 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that
210 // once we remove the manual recursion, we'll need to explicitly filter 185 // once we remove the manual recursion, we'll need to explicitly filter
211 // out files in hidden directories. 186 // out files in hidden directories.
212 if (recursive) { 187 if (recursive) {
213 children.add(doList(new Directory(file), listedDirectories)); 188 children.add(doList(file, listedDirectories));
214 } 189 }
215 }; 190 };
216 191
217 lister.onFile = (file) { 192 lister.onFile = (file) {
218 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; 193 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return;
219 contents.add(join(dir, path.basename(file))); 194 contents.add(path.join(dir, path.basename(file)));
220 }; 195 };
221 196
222 return completer.future.then((contents) { 197 return completer.future.then((contents) {
223 return Future.wait(children).then((childContents) { 198 return Future.wait(children).then((childContents) {
224 contents.addAll(flatten(childContents)); 199 contents.addAll(flatten(childContents));
225 return contents; 200 return contents;
226 }); 201 });
227 }); 202 });
228 } 203 }
229 204
230 return doList(_getDirectory(dir), new Set<String>()); 205 return doList(dir, new Set<String>());
231 } 206 }
232 207
233 /// Determines if [dir], which can be a [String] directory path or a 208 /// Determines if [dir] exists on the file system.
234 /// [Directory], exists on the file system. 209 bool dirExists(String dir) => new Directory(dir).existsSync();
235 bool dirExists(dir) => _getDirectory(dir).existsSync();
236 210
237 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a 211 /// "Cleans" [dir]. If that directory already exists, it will be deleted. Then a
238 /// new empty directory will be created. Returns a [Future] that completes when 212 /// new empty directory will be created. Returns a [Future] that completes when
239 /// the new clean directory is created. 213 /// the new clean directory is created.
240 Future<Directory> cleanDir(dir) { 214 Future<String> cleanDir(String dir) {
241 return defer(() { 215 return defer(() {
242 if (dirExists(dir)) { 216 if (dirExists(dir)) {
243 // Delete it first. 217 // Delete it first.
244 return deleteDir(dir).then((_) => createDir(dir)); 218 return deleteDir(dir).then((_) => createDir(dir));
245 } else { 219 } else {
246 // Just create it. 220 // Just create it.
247 return createDir(dir); 221 return createDir(dir);
248 } 222 }
249 }); 223 });
250 } 224 }
251 225
252 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with 226 /// Renames (i.e. moves) the directory [from] to [to]. Returns a [Future] with
253 /// the destination directory. 227 /// the destination directory.
254 Future<Directory> renameDir(from, String to) { 228 Future<String> renameDir(String from, String to) {
255 from = _getDirectory(from); 229 log.io("Renaming directory $from to $to.");
256 log.io("Renaming directory ${from.path} to $to.");
257 230
258 return _attemptRetryable(() => from.rename(to)).then((dir) { 231 return _attemptRetryable(() => new Directory(from).rename(to)).then((dir) {
259 log.fine("Renamed directory ${from.path} to $to."); 232 log.fine("Renamed directory $from to $to.");
260 return dir; 233 return to;
261 }); 234 });
262 } 235 }
263 236
264 /// On Windows, we sometimes get failures where the directory is still in use 237 /// On Windows, we sometimes get failures where the directory is still in use
265 /// when we try to do something with it. This is usually because the OS hasn't 238 /// when we try to do something with it. This is usually because the OS hasn't
266 /// noticed yet that a process using that directory has closed. To be a bit 239 /// noticed yet that a process using that directory has closed. To be a bit
267 /// more resilient, we wait and retry a few times. 240 /// more resilient, we wait and retry a few times.
268 /// 241 ///
269 /// Takes a [callback] which returns a future for the operation being attempted. 242 /// Takes a [callback] which returns a future for the operation being attempted.
270 /// If that future completes with an error, it will slepp and then [callback] 243 /// If that future completes with an error, it will slepp and then [callback]
(...skipping 13 matching lines...) Expand all
284 257
285 // Wait a bit and try again. 258 // Wait a bit and try again.
286 log.fine("Operation failed, retrying (attempt $attempts)."); 259 log.fine("Operation failed, retrying (attempt $attempts).");
287 return sleep(500).then(makeAttempt); 260 return sleep(500).then(makeAttempt);
288 }); 261 });
289 } 262 }
290 263
291 return makeAttempt(null); 264 return makeAttempt(null);
292 } 265 }
293 266
294 /// Creates a new symlink that creates an alias from [from] to [to], both of 267 /// Creates a new symlink that creates an alias from [from] to [to]. Returns a
295 /// which can be a [String], [File], or [Directory]. Returns a [Future] which 268 /// [Future] which completes to the symlink file (i.e. [to]).
296 /// completes to the symlink file (i.e. [to]).
297 /// 269 ///
298 /// Note that on Windows, only directories may be symlinked to. 270 /// Note that on Windows, only directories may be symlinked to.
299 Future<File> createSymlink(from, to) { 271 Future<String> createSymlink(String from, String to) {
300 from = _getPath(from);
301 to = _getPath(to);
302
303 log.fine("Creating symlink ($to is a symlink to $from)"); 272 log.fine("Creating symlink ($to is a symlink to $from)");
304 273
305 var command = 'ln'; 274 var command = 'ln';
306 var args = ['-s', from, to]; 275 var args = ['-s', from, to];
307 276
308 if (Platform.operatingSystem == 'windows') { 277 if (Platform.operatingSystem == 'windows') {
309 // Call mklink on Windows to create an NTFS junction point. Only works on 278 // Call mklink on Windows to create an NTFS junction point. Only works on
310 // Vista or later. (Junction points are available earlier, but the "mklink" 279 // Vista or later. (Junction points are available earlier, but the "mklink"
311 // command is not.) I'm using a junction point (/j) here instead of a soft 280 // command is not.) I'm using a junction point (/j) here instead of a soft
312 // link (/d) because the latter requires some privilege shenanigans that 281 // link (/d) because the latter requires some privilege shenanigans that
313 // I'm not sure how to specify from the command line. 282 // I'm not sure how to specify from the command line.
314 command = 'mklink'; 283 command = 'mklink';
315 args = ['/j', to, from]; 284 args = ['/j', to, from];
316 } 285 }
317 286
318 return runProcess(command, args).then((result) { 287 // TODO(rnystrom): Check exit code and output?
319 // TODO(rnystrom): Check exit code and output? 288 return runProcess(command, args).then((result) => to);
320 return new File(to);
321 });
322 } 289 }
323 290
324 /// Creates a new symlink that creates an alias from the `lib` directory of 291 /// Creates a new symlink that creates an alias from the `lib` directory of
325 /// package [from] to [to], both of which can be a [String], [File], or 292 /// package [from] to [to]. Returns a [Future] which completes to the symlink
326 /// [Directory]. Returns a [Future] which completes to the symlink file (i.e. 293 /// file (i.e. [to]). If [from] does not have a `lib` directory, this shows a
327 /// [to]). If [from] does not have a `lib` directory, this shows a warning if 294 /// warning if appropriate and then does nothing.
328 /// appropriate and then does nothing. 295 Future<String> createPackageSymlink(String name, String from, String to,
329 Future<File> createPackageSymlink(String name, from, to,
330 {bool isSelfLink: false}) { 296 {bool isSelfLink: false}) {
331 return defer(() { 297 return defer(() {
332 // See if the package has a "lib" directory. 298 // See if the package has a "lib" directory.
333 from = join(from, 'lib'); 299 from = path.join(from, 'lib');
334 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'."); 300 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'.");
335 if (dirExists(from)) return createSymlink(from, to); 301 if (dirExists(from)) return createSymlink(from, to);
336 302
337 // It's OK for the self link (i.e. the root package) to not have a lib 303 // It's OK for the self link (i.e. the root package) to not have a lib
338 // directory since it may just be a leaf application that only has 304 // directory since it may just be a leaf application that only has
339 // code in bin or web. 305 // code in bin or web.
340 if (!isSelfLink) { 306 if (!isSelfLink) {
341 log.warning('Warning: Package "$name" does not have a "lib" directory so ' 307 log.warning('Warning: Package "$name" does not have a "lib" directory so '
342 'you will not be able to import any libraries from it.'); 308 'you will not be able to import any libraries from it.');
343 } 309 }
344 310
345 return _getFile(to); 311 return to;
346 }); 312 });
347 } 313 }
348 314
349 /// Resolves [target] relative to the location of pub.dart. 315 /// Resolves [target] relative to the location of pub.dart.
350 String relativeToPub(String target) { 316 String relativeToPub(String target) {
351 var scriptPath = new File(new Options().script).fullPathSync(); 317 var scriptPath = new File(new Options().script).fullPathSync();
352 318
353 // Walk up until we hit the "util(s)" directory. This lets us figure out where 319 // Walk up until we hit the "util(s)" directory. This lets us figure out where
354 // we are if this function is called from pub.dart, or one of the tests, 320 // we are if this function is called from pub.dart, or one of the tests,
355 // which also live under "utils", or from the SDK where pub is in "util". 321 // which also live under "utils", or from the SDK where pub is in "util".
356 var utilDir = path.dirname(scriptPath); 322 var utilDir = path.dirname(scriptPath);
357 while (path.basename(utilDir) != 'utils' && 323 while (path.basename(utilDir) != 'utils' &&
358 path.basename(utilDir) != 'util') { 324 path.basename(utilDir) != 'util') {
359 if (path.basename(utilDir) == '') throw 'Could not find path to pub.'; 325 if (path.basename(utilDir) == '') throw 'Could not find path to pub.';
360 utilDir = path.dirname(utilDir); 326 utilDir = path.dirname(utilDir);
361 } 327 }
362 328
363 return path.normalize(join(utilDir, 'pub', target)); 329 return path.normalize(path.join(utilDir, 'pub', target));
364 } 330 }
365 331
366 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr 332 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr
367 // nicer. 333 // nicer.
368 334
369 /// A sink that writes to standard output. Errors piped to this stream will be 335 /// A sink that writes to standard output. Errors piped to this stream will be
370 /// surfaced to the top-level error handler. 336 /// surfaced to the top-level error handler.
371 final StreamSink<List<int>> stdoutSink = _wrapStdio(stdout, "stdout"); 337 final StreamSink<List<int>> stdoutSink = _wrapStdio(stdout, "stdout");
372 338
373 /// A sink that writes to standard error. Errors piped to this stream will be 339 /// A sink that writes to standard error. Errors piped to this stream will be
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
636 } 602 }
637 603
638 /// Sends [signal] to the underlying process. 604 /// Sends [signal] to the underlying process.
639 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => 605 bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) =>
640 _process.kill(signal); 606 _process.kill(signal);
641 } 607 }
642 608
643 /// Calls [fn] with appropriately modified arguments. [fn] should have the same 609 /// Calls [fn] with appropriately modified arguments. [fn] should have the same
644 /// signature as [Process.start], except that the returned [Future] may have a 610 /// signature as [Process.start], except that the returned [Future] may have a
645 /// type other than [Process]. 611 /// type other than [Process].
646 Future _doProcess(Function fn, String executable, List<String> args, workingDir, 612 Future _doProcess(Function fn, String executable, List<String> args,
647 Map<String, String> environment) { 613 String workingDir, Map<String, String> environment) {
648 // TODO(rnystrom): Should dart:io just handle this? 614 // TODO(rnystrom): Should dart:io just handle this?
649 // Spawning a process on Windows will not look for the executable in the 615 // Spawning a process on Windows will not look for the executable in the
650 // system path. So, if executable looks like it needs that (i.e. it doesn't 616 // system path. So, if executable looks like it needs that (i.e. it doesn't
651 // have any path separators in it), then spawn it through a shell. 617 // have any path separators in it), then spawn it through a shell.
652 if ((Platform.operatingSystem == "windows") && 618 if ((Platform.operatingSystem == "windows") &&
653 (executable.indexOf('\\') == -1)) { 619 (executable.indexOf('\\') == -1)) {
654 args = flatten(["/c", executable, args]); 620 args = flatten(["/c", executable, args]);
655 executable = "cmd"; 621 executable = "cmd";
656 } 622 }
657 623
658 final options = new ProcessOptions(); 624 final options = new ProcessOptions();
659 if (workingDir != null) { 625 if (workingDir != null) {
660 options.workingDirectory = _getDirectory(workingDir).path; 626 options.workingDirectory = workingDir;
661 } 627 }
662 628
663 if (environment != null) { 629 if (environment != null) {
664 options.environment = new Map.from(Platform.environment); 630 options.environment = new Map.from(Platform.environment);
665 environment.forEach((key, value) => options.environment[key] = value); 631 environment.forEach((key, value) => options.environment[key] = value);
666 } 632 }
667 633
668 log.process(executable, args); 634 log.process(executable, args);
669 635
670 return fn(executable, args, options); 636 return fn(executable, args, options);
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
706 /// [fn] completes to. 672 /// [fn] completes to.
707 Future withTempDir(Future fn(String path)) { 673 Future withTempDir(Future fn(String path)) {
708 return defer(() { 674 return defer(() {
709 var tempDir = createTempDir(); 675 var tempDir = createTempDir();
710 return fn(tempDir).whenComplete(() { 676 return fn(tempDir).whenComplete(() {
711 return deleteDir(tempDir); 677 return deleteDir(tempDir);
712 }); 678 });
713 }); 679 });
714 } 680 }
715 681
716 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a 682 /// Extracts a `.tar.gz` file from [stream] to [destination]. Returns whether
717 /// directory or a path. Returns whether or not the extraction was successful. 683 /// or not the extraction was successful.
718 Future<bool> extractTarGz(Stream<List<int>> stream, destination) { 684 Future<bool> extractTarGz(Stream<List<int>> stream, String destination) {
719 destination = _getPath(destination);
720
721 log.fine("Extracting .tar.gz stream to $destination."); 685 log.fine("Extracting .tar.gz stream to $destination.");
722 686
723 if (Platform.operatingSystem == "windows") { 687 if (Platform.operatingSystem == "windows") {
724 return _extractTarGzWindows(stream, destination); 688 return _extractTarGzWindows(stream, destination);
725 } 689 }
726 690
727 return startProcess("tar", 691 return startProcess("tar",
728 ["--extract", "--gunzip", "--directory", destination]).then((process) { 692 ["--extract", "--gunzip", "--directory", destination]).then((process) {
729 // Ignore errors on process.std{out,err}. They'll be passed to 693 // Ignore errors on process.std{out,err}. They'll be passed to
730 // process.exitCode, and we don't want them being top-levelled by 694 // process.exitCode, and we don't want them being top-levelled by
(...skipping 23 matching lines...) Expand all
754 // read from stdin instead of a file. Consider resurrecting that version if 718 // read from stdin instead of a file. Consider resurrecting that version if
755 // we can figure out why it fails. 719 // we can figure out why it fails.
756 720
757 // Note: This line of code gets munged by create_sdk.py to be the correct 721 // Note: This line of code gets munged by create_sdk.py to be the correct
758 // relative path to 7zip in the SDK. 722 // relative path to 7zip in the SDK.
759 var pathTo7zip = '../../third_party/7zip/7za.exe'; 723 var pathTo7zip = '../../third_party/7zip/7za.exe';
760 var command = relativeToPub(pathTo7zip); 724 var command = relativeToPub(pathTo7zip);
761 725
762 return withTempDir((tempDir) { 726 return withTempDir((tempDir) {
763 // Write the archive to a temp file. 727 // Write the archive to a temp file.
764 return createFileFromStream(stream, join(tempDir, 'data.tar.gz')).then((_) { 728 var dataFile = path.join(tempDir, 'data.tar.gz');
729 return createFileFromStream(stream, dataFile).then((_) {
765 // 7zip can't unarchive from gzip -> tar -> destination all in one step 730 // 7zip can't unarchive from gzip -> tar -> destination all in one step
766 // first we un-gzip it to a tar file. 731 // first we un-gzip it to a tar file.
767 // Note: Setting the working directory instead of passing in a full file 732 // Note: Setting the working directory instead of passing in a full file
768 // path because 7zip says "A full path is not allowed here." 733 // path because 7zip says "A full path is not allowed here."
769 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir); 734 return runProcess(command, ['e', 'data.tar.gz'], workingDir: tempDir);
770 }).then((result) { 735 }).then((result) {
771 if (result.exitCode != 0) { 736 if (result.exitCode != 0) {
772 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' 737 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n'
773 '${result.stdout.join("\n")}\n' 738 '${result.stdout.join("\n")}\n'
774 '${result.stderr.join("\n")}'; 739 '${result.stderr.join("\n")}';
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
809 contents.forEach((file) => buffer.add('$file\n')); 774 contents.forEach((file) => buffer.add('$file\n'));
810 log.fine(buffer.toString()); 775 log.fine(buffer.toString());
811 776
812 // TODO(nweiz): Propagate errors to the returned stream (including non-zero 777 // TODO(nweiz): Propagate errors to the returned stream (including non-zero
813 // exit codes). See issue 3657. 778 // exit codes). See issue 3657.
814 var controller = new StreamController<List<int>>(); 779 var controller = new StreamController<List<int>>();
815 780
816 if (baseDir == null) baseDir = path.current; 781 if (baseDir == null) baseDir = path.current;
817 baseDir = path.absolute(baseDir); 782 baseDir = path.absolute(baseDir);
818 contents = contents.map((entry) { 783 contents = contents.map((entry) {
819 entry = path.absolute(_getPath(entry)); 784 entry = path.absolute(entry);
820 if (!isBeneath(entry, baseDir)) { 785 if (!isBeneath(entry, baseDir)) {
821 throw 'Entry $entry is not inside $baseDir.'; 786 throw 'Entry $entry is not inside $baseDir.';
822 } 787 }
823 return path.relative(entry, from: baseDir); 788 return path.relative(entry, from: baseDir);
824 }).toList(); 789 }).toList();
825 790
826 if (Platform.operatingSystem != "windows") { 791 if (Platform.operatingSystem != "windows") {
827 var args = ["--create", "--gzip", "--directory", baseDir]; 792 var args = ["--create", "--gzip", "--directory", baseDir];
828 args.addAll(contents.map(_getPath)); 793 args.addAll(contents);
829 // TODO(nweiz): It's possible that enough command-line arguments will make 794 // TODO(nweiz): It's possible that enough command-line arguments will make
830 // the process choke, so at some point we should save the arguments to a 795 // the process choke, so at some point we should save the arguments to a
831 // file and pass them in via --files-from for tar and -i@filename for 7zip. 796 // file and pass them in via --files-from for tar and -i@filename for 7zip.
832 startProcess("tar", args).then((process) { 797 startProcess("tar", args).then((process) {
833 store(process.stdout, controller); 798 store(process.stdout, controller);
834 }).catchError((e) { 799 }).catchError((e) {
835 // We don't have to worry about double-signaling here, since the store() 800 // We don't have to worry about double-signaling here, since the store()
836 // above will only be reached if startProcess succeeds. 801 // above will only be reached if startProcess succeeds.
837 controller.signalError(e.error, e.stackTrace); 802 controller.signalError(e.error, e.stackTrace);
838 controller.close(); 803 controller.close();
839 }); 804 });
840 return new ByteStream(controller.stream); 805 return new ByteStream(controller.stream);
841 } 806 }
842 807
843 withTempDir((tempDir) { 808 withTempDir((tempDir) {
844 // Create the tar file. 809 // Create the tar file.
845 var tarFile = join(tempDir, "intermediate.tar"); 810 var tarFile = path.join(tempDir, "intermediate.tar");
846 var args = ["a", "-w$baseDir", tarFile]; 811 var args = ["a", "-w$baseDir", tarFile];
847 args.addAll(contents.map((entry) => '-i!"$entry"')); 812 args.addAll(contents.map((entry) => '-i!"$entry"'));
848 813
849 // Note: This line of code gets munged by create_sdk.py to be the correct 814 // Note: This line of code gets munged by create_sdk.py to be the correct
850 // relative path to 7zip in the SDK. 815 // relative path to 7zip in the SDK.
851 var pathTo7zip = '../../third_party/7zip/7za.exe'; 816 var pathTo7zip = '../../third_party/7zip/7za.exe';
852 var command = relativeToPub(pathTo7zip); 817 var command = relativeToPub(pathTo7zip);
853 818
854 // We're passing 'baseDir' both as '-w' and setting it as the working 819 // We're passing 'baseDir' both as '-w' and setting it as the working
855 // directory explicitly here intentionally. The former ensures that the 820 // directory explicitly here intentionally. The former ensures that the
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
890 class PubProcessResult { 855 class PubProcessResult {
891 final List<String> stdout; 856 final List<String> stdout;
892 final List<String> stderr; 857 final List<String> stderr;
893 final int exitCode; 858 final int exitCode;
894 859
895 const PubProcessResult(this.stdout, this.stderr, this.exitCode); 860 const PubProcessResult(this.stdout, this.stderr, this.exitCode);
896 861
897 bool get success => exitCode == 0; 862 bool get success => exitCode == 0;
898 } 863 }
899 864
900 /// Gets a dart:io [File] for [entry], which can either already be a File or be
901 /// a path string.
902 File _getFile(entry) {
903 if (entry is File) return entry;
904 if (entry is String) return new File(entry);
905 throw 'Entry $entry is not a supported type.';
906 }
907
908 /// Gets the path string for [entry], which can either already be a path string,
909 /// or be a [File] or [Directory]. Allows working generically with "file-like"
910 /// objects.
911 String _getPath(entry) {
912 if (entry is String) return entry;
913 if (entry is File) return entry.name;
914 if (entry is Directory) return entry.path;
915 throw 'Entry $entry is not a supported type.';
916 }
917
918 /// Gets a [Directory] for [entry], which can either already be one, or be a
919 /// [String].
920 Directory _getDirectory(entry) {
921 if (entry is Directory) return entry;
922 return new Directory(entry);
923 }
924
925 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. 865 /// Gets a [Uri] for [uri], which can either already be one, or be a [String].
926 Uri _getUri(uri) { 866 Uri _getUri(uri) {
927 if (uri is Uri) return uri; 867 if (uri is Uri) return uri;
928 return Uri.parse(uri); 868 return Uri.parse(uri);
929 } 869 }
OLDNEW
« no previous file with comments | « utils/pub/hosted_source.dart ('k') | utils/pub/oauth2.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698