-
Notifications
You must be signed in to change notification settings - Fork 0
fix: complete Windows compatibility for glob and subprocess tests #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6ea9830
451c81d
2ff616c
8ffb6c9
ad0ad96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,53 @@ class CliArguments { | |
| while (scanner.scanChar($space)) {} | ||
| } | ||
|
|
||
| /// Glob characters that can be escaped with a backslash. | ||
| static const _globEscapedChars = { | ||
| $asterisk, | ||
| $question, | ||
| $lbracket, | ||
| $rbracket, | ||
| $lbrace, | ||
| $rbrace, | ||
| $lparen, | ||
| $rparen, | ||
| $comma, | ||
| $dash, | ||
| $backslash, | ||
| }; | ||
|
|
||
| /// Returns whether [pattern] is likely an absolute Windows path glob. | ||
| /// | ||
| /// We only normalize absolute Windows-style paths so we don't corrupt | ||
| /// shell-style escaped glob characters like `\\*.txt`. | ||
| static bool _isLikelyWindowsAbsolutePathGlob(String pattern) { | ||
| if (!Platform.isWindows || pattern.isEmpty) return false; | ||
| if (RegExp(r'^[A-Za-z](\\)?:[\\/]').hasMatch(pattern)) return true; | ||
| if (RegExp(r'^[A-Za-z]:[\\/]').hasMatch(pattern)) return true; | ||
| if (pattern.startsWith(r'\\') || pattern.startsWith('//')) return true; | ||
| if (pattern.codeUnitAt(0) == $backslash && pattern.length > 1) { | ||
| return !_globEscapedChars.contains(pattern.codeUnitAt(1)); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /// Normalizes a glob pattern to POSIX separators for the glob package. | ||
| /// | ||
| /// `package:glob` expects `/` as path separators on all platforms. | ||
| static String _normalizeGlobPattern(String pattern) { | ||
| if (!_isLikelyWindowsAbsolutePathGlob(pattern)) return pattern; | ||
| // Glob.quote() may escape `C:` as `C\:`, so normalize that first. | ||
| final normalizedDrivePrefix = pattern.replaceFirstMapped(RegExp(r'^([A-Za-z])\\:'), (match) => '${match[1]}:'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The regex Suggested FixChange the regular expression from Prompt for AI Agent |
||
| return normalizedDrivePrefix.replaceAll('\\', '/'); | ||
| } | ||
|
|
||
| static bool _containsGlobSyntax(String pattern) => | ||
| pattern.contains('*') || | ||
| pattern.contains('?') || | ||
| pattern.contains('[') || | ||
| pattern.contains('{') || | ||
| pattern.contains('('); | ||
|
|
||
| /// Scans a single argument. | ||
| static _Argument _scanArg(StringScanner scanner, {required bool glob}) { | ||
| final plainBuffer = StringBuffer(); | ||
|
|
@@ -72,8 +119,17 @@ class CliArguments { | |
| while (true) { | ||
| final next = scanner.peekChar(); | ||
| if (next == $space || next == null) { | ||
| final glob = isGlobActive ? globBuffer?.toString() : null; | ||
| return _Argument(plainBuffer.toString(), glob == null ? null : Glob(glob)); | ||
| final rawGlob = globBuffer?.toString(); | ||
| final normalizedGlob = rawGlob == null ? null : _normalizeGlobPattern(rawGlob); | ||
| final windowsAbsoluteGlob = | ||
| glob && | ||
| !isGlobActive && | ||
| rawGlob != null && | ||
| _isLikelyWindowsAbsolutePathGlob(rawGlob) && | ||
| normalizedGlob != null && | ||
| _containsGlobSyntax(normalizedGlob); | ||
| final globPattern = (isGlobActive || windowsAbsoluteGlob) ? normalizedGlob : null; | ||
| return _Argument(plainBuffer.toString(), globPattern == null ? null : Glob(globPattern)); | ||
| } else if (next == $double_quote || next == $single_quote) { | ||
| scanner.readChar(); | ||
This comment was marked as outdated.
Sorry, something went wrong. |
||
|
|
||
|
|
@@ -157,9 +213,12 @@ class _Argument { | |
| Future<List<String>> resolve({String? root}) async { | ||
| final glob = _glob; | ||
| if (glob != null) { | ||
| final absolute = p.isAbsolute(glob.pattern); | ||
| final absolute = Platform.isWindows | ||
| ? CliArguments._isLikelyWindowsAbsolutePathGlob(glob.pattern) | ||
| : p.isAbsolute(glob.pattern); | ||
| final globbed = [ | ||
| await for (final entity in glob.list(root: root)) absolute ? entity.path : p.relative(entity.path, from: root), | ||
| await for (final entity in glob.list(root: root)) | ||
| absolute ? p.normalize(entity.path) : p.relative(entity.path, from: root), | ||
| ]; | ||
| if (globbed.isNotEmpty) return globbed; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -108,6 +108,23 @@ class Script { | |||||||||||||||||
| : withEnv(() => env, environment!); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (!includeParentEnvironment) { | ||||||||||||||||||
| // Pass an explicit map rather than null so subprocesses don't | ||||||||||||||||||
| // implicitly inherit parent environment variables. | ||||||||||||||||||
| environment ??= <String, String>{}; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (Platform.isWindows) { | ||||||||||||||||||
| // Some Windows executables (including Dart in certain contexts) | ||||||||||||||||||
| // require a minimal base environment to spawn reliably. | ||||||||||||||||||
| final windowsBase = <String, String>{}; | ||||||||||||||||||
| final systemRoot = Platform.environment['SystemRoot'] ?? Platform.environment['SYSTEMROOT']; | ||||||||||||||||||
| final winDir = Platform.environment['WINDIR']; | ||||||||||||||||||
| if (systemRoot != null && systemRoot.isNotEmpty) windowsBase['SystemRoot'] = systemRoot; | ||||||||||||||||||
| if (winDir != null && winDir.isNotEmpty) windowsBase['WINDIR'] = winDir; | ||||||||||||||||||
| environment = {...windowsBase, ...environment!}; | ||||||||||||||||||
|
||||||||||||||||||
| environment = {...windowsBase, ...environment!}; | |
| // Merge the minimal Windows base environment into the existing map | |
| // without replacing it, so any specialized map implementation | |
| // (such as a case-insensitive CanonicalizedMap) is preserved and | |
| // user-provided values continue to override the base. | |
| for (final entry in windowsBase.entries) { | |
| environment!.putIfAbsent(entry.key, () => entry.value); | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function
_isLikelyWindowsAbsolutePathGlobis duplicated in bothlib/cli_script.dartandlib/src/cli_arguments.dartwith slightly different implementations. This violates the DRY (Don't Repeat Yourself) principle and creates a maintenance burden. The two implementations differ in how they check for escaped glob characters: this file uses string-based comparison withpattern[1], whilecli_arguments.dartuses code unit comparison withpattern.codeUnitAt(1). Consider extracting this function to a shared utility module to ensure consistent behavior and easier maintenance.