| // 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 'dart:io'; |
| |
| import 'package:io/io.dart'; |
| import 'package:path/path.dart' as p; |
| import 'package:source_span/source_span.dart'; |
| import 'package:yaml/yaml.dart'; |
| |
| /// User-provided settings for invoking an executable. |
| class ExecutableSettings { |
| /// Additional arguments to pass to the executable. |
| final List<String> arguments; |
| |
| /// The path to the executable on Linux. |
| /// |
| /// This may be an absolute path or a basename, in which case it will be |
| /// looked up on the system path. It may not be relative. |
| final String _linuxExecutable; |
| |
| /// The path to the executable on Mac OS. |
| /// |
| /// This may be an absolute path or a basename, in which case it will be |
| /// looked up on the system path. It may not be relative. |
| final String _macOSExecutable; |
| |
| /// The path to the executable on Windows. |
| /// |
| /// This may be an absolute path; a basename, in which case it will be looked |
| /// up on the system path; or a relative path, in which case it will be looked |
| /// up relative to the paths in the `LOCALAPPDATA`, `PROGRAMFILES`, and |
| /// `PROGRAMFILES(X64)` environment variables. |
| final String _windowsExecutable; |
| |
| /// The path to the executable for the current operating system. |
| String get executable { |
| if (Platform.isMacOS) return _macOSExecutable; |
| if (!Platform.isWindows) return _linuxExecutable; |
| if (p.isAbsolute(_windowsExecutable)) return _windowsExecutable; |
| if (p.basename(_windowsExecutable) == _windowsExecutable) { |
| return _windowsExecutable; |
| } |
| |
| var prefixes = [ |
| Platform.environment['LOCALAPPDATA'], |
| Platform.environment['PROGRAMFILES'], |
| Platform.environment['PROGRAMFILES(X86)'] |
| ]; |
| |
| for (var prefix in prefixes) { |
| if (prefix == null) continue; |
| |
| var path = p.join(prefix, _windowsExecutable); |
| if (new File(path).existsSync()) return path; |
| } |
| |
| // If we can't find a path that works, return one that doesn't. This will |
| // cause an "executable not found" error to surface. |
| return p.join( |
| prefixes.firstWhere((prefix) => prefix != null, orElse: () => '.'), |
| _windowsExecutable); |
| } |
| |
| /// Parses settings from a user-provided YAML mapping. |
| factory ExecutableSettings.parse(YamlMap settings) { |
| List<String> arguments; |
| var argumentsNode = settings.nodes["arguments"]; |
| if (argumentsNode != null) { |
| if (argumentsNode.value is String) { |
| try { |
| arguments = shellSplit(argumentsNode.value); |
| } on FormatException catch (error) { |
| throw new SourceSpanFormatException( |
| error.message, argumentsNode.span); |
| } |
| } else { |
| throw new SourceSpanFormatException( |
| "Must be a string.", argumentsNode.span); |
| } |
| } |
| |
| String linuxExecutable; |
| String macOSExecutable; |
| String windowsExecutable; |
| var executableNode = settings.nodes["executable"]; |
| if (executableNode != null) { |
| if (executableNode.value is String) { |
| // Don't check this on Windows because people may want to set relative |
| // paths in their global config. |
| if (!Platform.isWindows) _assertNotRelative(executableNode); |
| |
| linuxExecutable = executableNode.value; |
| macOSExecutable = executableNode.value; |
| windowsExecutable = executableNode.value; |
| } else if (executableNode is YamlMap) { |
| linuxExecutable = _getExecutable(executableNode.nodes["linux"]); |
| macOSExecutable = _getExecutable(executableNode.nodes["mac_os"]); |
| windowsExecutable = _getExecutable(executableNode.nodes["windows"], |
| allowRelative: true); |
| } else { |
| throw new SourceSpanFormatException( |
| "Must be a map or a string.", executableNode.span); |
| } |
| } |
| |
| return new ExecutableSettings( |
| arguments: arguments, |
| linuxExecutable: linuxExecutable, |
| macOSExecutable: macOSExecutable, |
| windowsExecutable: windowsExecutable); |
| } |
| |
| /// Asserts that [executableNode] is a string or `null` and returns it. |
| /// |
| /// If [allowRelative] is `false` (the default), asserts that the value isn't |
| /// a relative path. |
| static String _getExecutable(YamlNode executableNode, |
| {bool allowRelative: false}) { |
| if (executableNode == null || executableNode.value == null) return null; |
| if (executableNode.value is! String) { |
| throw new SourceSpanFormatException( |
| "Must be a string.", executableNode.span); |
| } |
| if (!allowRelative) _assertNotRelative(executableNode); |
| return executableNode.value; |
| } |
| |
| /// Throws a [SourceSpanFormatException] if [executableNode]'s value is a |
| /// relative POSIX path that's not just a plain basename. |
| /// |
| /// We loop up basenames on the PATH and we can resolve absolute paths, but we |
| /// have no way of interpreting relative paths. |
| static void _assertNotRelative(YamlScalar executableNode) { |
| var executable = executableNode.value as String; |
| if (!p.posix.isRelative(executable)) return; |
| if (p.posix.basename(executable) == executable) return; |
| |
| throw new SourceSpanFormatException( |
| "Linux and Mac OS executables may not be relative paths.", |
| executableNode.span); |
| } |
| |
| ExecutableSettings( |
| {Iterable<String> arguments, |
| String linuxExecutable, |
| String macOSExecutable, |
| String windowsExecutable}) |
| : arguments = |
| arguments == null ? const [] : new List.unmodifiable(arguments), |
| _linuxExecutable = linuxExecutable, |
| _macOSExecutable = macOSExecutable, |
| _windowsExecutable = windowsExecutable; |
| |
| /// Merges [this] with [other], with [other]'s settings taking priority. |
| ExecutableSettings merge(ExecutableSettings other) => new ExecutableSettings( |
| arguments: arguments.toList()..addAll(other.arguments), |
| linuxExecutable: other._linuxExecutable ?? _linuxExecutable, |
| macOSExecutable: other._macOSExecutable ?? _macOSExecutable, |
| windowsExecutable: other._windowsExecutable ?? _windowsExecutable); |
| } |