os.dart 8.31 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[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
  List<File> _which(String execName, {bool all = false}) {
87 88 89 90 91
    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
      return const <File>[];
94 95
    final String stdout = result.stdout;
    return stdout.trim().split('\n').map((String path) => fs.file(path.trim())).toList();
96
  }
97

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

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

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

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

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

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

  String _name;

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

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

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

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

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

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

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

194 195 196 197 198 199 200 201 202 203 204 205
  @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;
  }

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

214 215 216 217 218 219 220 221 222 223 224 225
  @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;
  }

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

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

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

  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;
  }
258 259 260

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

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