os.dart 8.32 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:archive/archive.dart';
6
import 'context.dart';
7
import 'file_system.dart';
8
import 'io.dart';
9
import 'platform.dart';
10
import 'process.dart';
11
import 'process_manager.dart';
12 13

/// Returns [OperatingSystemUtils] active in the current app context (i.e. zone).
14
OperatingSystemUtils get os => context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
15 16

abstract class OperatingSystemUtils {
17
  factory OperatingSystemUtils() {
18
    if (platform.isWindows) {
19 20
      return new _WindowsUtils();
    } else {
Adam Barth's avatar
Adam Barth committed
21
      return new _PosixUtils();
22 23 24
    }
  }

25 26
  OperatingSystemUtils._private();

27
  /// Make the given file executable. This may be a no-op on some platforms.
28
  ProcessResult makeExecutable(File file);
29

30
  /// Return the path (with symlinks resolved) to the given executable, or null
31
  /// if `which` was not able to locate the binary.
32 33 34 35 36 37
  File which(String execName) {
    final List<File> result = _which(execName);
    if (result == null || result.isEmpty)
      return null;
    return result.first;
  }
38

39 40
  /// Return a list of all paths to `execName` found on the system. Uses the
  /// PATH environment variable.
41
  List<File> whichAll(String execName) => _which(execName, all: true);
42

43 44 45
  /// Return the File representing a new pipe.
  File makePipe(String path);

46 47
  void zip(Directory data, File zipFile);

48
  void unzip(File file, Directory targetDirectory);
49

50 51 52
  /// Returns true if the ZIP is not corrupt.
  bool verifyZip(File file);

53 54
  void unpack(File gzippedTarFile, Directory targetDirectory);

55 56 57
  /// Returns true if the gzip is not corrupt (does not check tar).
  bool verifyGzip(File gzippedFile);

58 59 60 61 62 63 64 65 66 67 68 69 70
  /// Returns a pretty name string for the current operating system.
  ///
  /// If available, the detailed version of the OS is included.
  String get name {
    const Map<String, String> osNames = const <String, String>{
      'macos': 'Mac OS',
      'linux': 'Linux',
      'windows': 'Windows'
    };
    final String osName = platform.operatingSystem;
    return osNames.containsKey(osName) ? osNames[osName] : osName;
  }

71
  List<File> _which(String execName, {bool all: false});
72 73 74

  /// Returns the separator between items in the PATH environment variable.
  String get pathVarSeparator;
75 76
}

77 78 79
class _PosixUtils extends OperatingSystemUtils {
  _PosixUtils() : super._private();

80
  @override
81
  ProcessResult makeExecutable(File file) {
82
    return processManager.runSync(<String>['chmod', 'a+x', file.path]);
83
  }
84

85
  @override
86 87 88 89 90 91
  List<File> _which(String execName, {bool all: false}) {
    final List<String> command = <String>['which'];
    if (all)
      command.add('-a');
    command.add(execName);
    final ProcessResult result = processManager.runSync(command);
92
    if (result.exitCode != 0)
93 94
      return const <File>[];
    return result.stdout.trim().split('\n').map((String path) => fs.file(path.trim())).toList();
95
  }
96

97 98 99 100 101
  @override
  void zip(Directory data, File zipFile) {
    runSync(<String>['zip', '-r', '-q', zipFile.path, '.'], workingDirectory: data.path);
  }

102 103 104 105 106
  // unzip -o -q zipfile -d dest
  @override
  void unzip(File file, Directory targetDirectory) {
    runSync(<String>['unzip', '-o', '-q', file.path, '-d', targetDirectory.path]);
  }
107

108 109 110
  @override
  bool verifyZip(File zipFile) => exitsHappy(<String>['zip', '-T', zipFile.path]);

111 112 113 114 115 116
  // tar -xzf tarball -C dest
  @override
  void unpack(File gzippedTarFile, Directory targetDirectory) {
    runSync(<String>['tar', '-xzf', gzippedTarFile.path, '-C', targetDirectory.path]);
  }

117 118 119
  @override
  bool verifyGzip(File gzippedFile) => exitsHappy(<String>['gzip', '-t', gzippedFile.path]);

120 121 122
  @override
  File makePipe(String path) {
    runSync(<String>['mkfifo', path]);
123
    return fs.file(path);
124
  }
125 126 127 128 129 130 131 132

  String _name;

  @override
  String get name {
    if (_name == null) {
      if (platform.isMacOS) {
        final List<ProcessResult> results = <ProcessResult>[
133 134 135
          processManager.runSync(<String>['sw_vers', '-productName']),
          processManager.runSync(<String>['sw_vers', '-productVersion']),
          processManager.runSync(<String>['sw_vers', '-buildVersion']),
136 137
        ];
        if (results.every((ProcessResult result) => result.exitCode == 0)) {
138 139
          _name = '${results[0].stdout.trim()} ${results[1].stdout
              .trim()} ${results[2].stdout.trim()}';
140 141
        }
      }
142
      _name ??= super.name;
143 144 145
    }
    return _name;
  }
146 147 148

  @override
  String get pathVarSeparator => ':';
149 150
}

151 152 153
class _WindowsUtils extends OperatingSystemUtils {
  _WindowsUtils() : super._private();

154
  // This is a no-op.
155
  @override
156 157
  ProcessResult makeExecutable(File file) {
    return new ProcessResult(0, 0, null, null);
158
  }
159

160
  @override
161 162
  List<File> _which(String execName, {bool all: false}) {
    // `where` always returns all matches, not just the first one.
163
    final ProcessResult result = processManager.runSync(<String>['where', execName]);
164
    if (result.exitCode != 0)
165 166 167 168
      return const <File>[];
    final List<String> lines = result.stdout.trim().split('\n');
    if (all)
      return lines.map((String path) => fs.file(path.trim())).toList();
169
    return <File>[fs.file(lines.first.trim())];
170 171
  }

172 173
  @override
  void zip(Directory data, File zipFile) {
174
    final Archive archive = new Archive();
175 176 177 178
    for (FileSystemEntity entity in data.listSync(recursive: true)) {
      if (entity is! File) {
        continue;
      }
179 180 181
      final File file = entity;
      final String path = file.fileSystem.path.relative(file.path, from: data.path);
      final List<int> bytes = file.readAsBytesSync();
182 183 184 185 186
      archive.addFile(new ArchiveFile(path, bytes.length, bytes));
    }
    zipFile.writeAsBytesSync(new ZipEncoder().encode(archive), flush: true);
  }

187 188
  @override
  void unzip(File file, Directory targetDirectory) {
189
    final Archive archive = new ZipDecoder().decodeBytes(file.readAsBytesSync());
190 191 192
    _unpackArchive(archive, targetDirectory);
  }

193 194 195 196 197 198 199 200 201 202 203 204
  @override
  bool verifyZip(File zipFile) {
    try {
      new ZipDecoder().decodeBytes(zipFile.readAsBytesSync(), verify: true);
    } on FileSystemException catch (_) {
      return false;
    } on ArchiveException catch (_) {
      return false;
    }
    return true;
  }

205 206 207 208 209 210 211
  @override
  void unpack(File gzippedTarFile, Directory targetDirectory) {
    final Archive archive = new TarDecoder().decodeBytes(
      new GZipDecoder().decodeBytes(gzippedTarFile.readAsBytesSync()),
    );
    _unpackArchive(archive, targetDirectory);
  }
212

213 214 215 216 217 218 219 220 221 222 223 224
  @override
  bool verifyGzip(File gzipFile) {
    try {
      new GZipDecoder().decodeBytes(gzipFile.readAsBytesSync(), verify: true);
    } on FileSystemException catch (_) {
      return false;
    } on ArchiveException catch (_) {
      return false;
    }
    return true;
  }

225
  void _unpackArchive(Archive archive, Directory targetDirectory) {
226 227 228 229 230
    for (ArchiveFile archiveFile in archive.files) {
      // The archive package doesn't correctly set isFile.
      if (!archiveFile.isFile || archiveFile.name.endsWith('/'))
        continue;

231
      final File destFile = fs.file(fs.path.join(targetDirectory.path, archiveFile.name));
232 233 234 235
      if (!destFile.parent.existsSync())
        destFile.parent.createSync(recursive: true);
      destFile.writeAsBytesSync(archiveFile.content);
    }
236
  }
237 238 239 240 241

  @override
  File makePipe(String path) {
    throw new UnsupportedError('makePipe is not implemented on Windows.');
  }
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

  String _name;

  @override
  String get name {
    if (_name == null) {
      final ProcessResult result = processManager.runSync(
          <String>['ver'], runInShell: true);
      if (result.exitCode == 0)
        _name = result.stdout.trim();
      else
        _name = super.name;
    }
    return _name;
  }
257 258 259

  @override
  String get pathVarSeparator => ';';
260
}
261

262 263
/// Find and return the project root directory relative to the specified
/// directory or the current working directory if none specified.
264
/// Return null if the project root could not be found
265 266 267
/// or if the project root is the flutter repository root.
String findProjectRoot([String directory]) {
  const String kProjectRootSentinel = 'pubspec.yaml';
268
  directory ??= fs.currentDirectory.path;
269
  while (true) {
270
    if (fs.isFileSync(fs.path.join(directory, kProjectRootSentinel)))
271
      return directory;
272
    final String parent = fs.path.dirname(directory);
273 274
    if (directory == parent)
      return null;
275 276 277
    directory = parent;
  }
}