blob: 481c0824002dc6924f27a67c11819caa8abed793 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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';
import 'package:path/path.dart' show Context;
import 'package:platform/platform.dart';
const Map<String, String> _osToPathStyle = const <String, String>{
'linux': 'posix',
'macos': 'posix',
'android': 'posix',
'ios': 'posix',
'fuchsia': 'posix',
'windows': 'windows',
/// Sanatizes the executable path on Windows.
String sanitizeExecutablePath(String executable,
{Platform platform = const LocalPlatform()}) {
if (executable.isEmpty) {
return executable;
if (!platform.isWindows) {
return executable;
if (executable.contains(' ') && !executable.startsWith('"')) {
// Use quoted strings to indicate where the file name ends and the arguments begin;
// otherwise, the file name is ambiguous.
return '"$executable"';
return executable;
/// Searches the `PATH` for the executable that [command] is supposed to launch.
/// This first builds a list of candidate paths where the executable may reside.
/// If [command] is already an absolute path, then the `PATH` environment
/// variable will not be consulted, and the specified absolute path will be the
/// only candidate that is considered.
/// Once the list of candidate paths has been constructed, this will pick the
/// first such path that represents an existent file.
/// Return `null` if there were no viable candidates, meaning the executable
/// could not be found.
/// If [platform] is not specified, it will default to the current platform.
String getExecutablePath(
String command,
String workingDirectory, {
Platform platform: const LocalPlatform(),
FileSystem fs: const LocalFileSystem(),
}) {
assert(_osToPathStyle[platform.operatingSystem] ==;
workingDirectory ??= fs.currentDirectory.path;
Context context =
new Context(style:, current: workingDirectory);
// TODO(goderbauer): refactor when
// is available.
String pathSeparator = platform.isWindows ? ';' : ':';
List<String> extensions = <String>[];
if (platform.isWindows && context.extension(command).isEmpty) {
extensions = platform.environment['PATHEXT'].split(pathSeparator);
List<String> candidates = <String>[];
if (command.contains(context.separator)) {
candidates = _getCandidatePaths(
command, <String>[workingDirectory], extensions, context);
} else {
List<String> searchPath = platform.environment['PATH'].split(pathSeparator);
candidates = _getCandidatePaths(command, searchPath, extensions, context);
return candidates.firstWhere((String path) => fs.file(path).existsSync(),
orElse: () => null);
/// Returns all possible combinations of `$searchPath\$command.$ext` for
/// `searchPath` in [searchPaths] and `ext` in [extensions].
/// If [extensions] is empty, it will just enumerate all
/// `$searchPath\$command`.
/// If [command] is an absolute path, it will just enumerate
/// `$command.$ext`.
Iterable<String> _getCandidatePaths(
String command,
List<String> searchPaths,
List<String> extensions,
Context context,
) {
List<String> withExtensions = extensions.isNotEmpty
? ext) => '$command$ext').toList()
: <String>[command];
if (context.isAbsolute(command)) {
return withExtensions;
return searchPaths
.map((String path) => command) => context.join(path, command)))
.expand((Iterable<String> e) => e)