merge/audit-and-state-map #7
3 changed files with 102 additions and 2 deletions
fix(sdk): refuse an unreadable SDK clearly, and fix the runtime fallback
An SDK directory that exists but cannot be read passed discovery, and the analyzer then died with a raw FileSystemException stack trace. This happens for real under macOS privacy protection: a Flutter SDK under ~/Documents stats fine but every open fails, and every app whose package_config.json names that flutterRoot crashed: Unhandled exception: FileSystemException(path=.../dart-sdk/lib/_internal/allowed_experiments.json; ...) The chosen SDK is now probed before the analysis collection is built, and an unreadable one is a PipeException naming the path, where it was discovered from, and the --sdk remedy. The last-resort fallback to the running Dart SDK took the executable's directory (<sdk>/bin) as the SDK root, which never contains lib/libraries.json, so it could never succeed. sdkFromExecutable now maps <sdk>/bin/dart to <sdk>.
commit
70262a8c2f
|
|
@ -55,6 +55,7 @@ class Analyzer {
|
|||
explicit: sdkOverride,
|
||||
flutterRootHint: project.flutterRoot,
|
||||
);
|
||||
requireReadableSdk(resolution);
|
||||
final collection = AnalysisContextCollection(
|
||||
includedPaths: [project.root],
|
||||
resourceProvider: PhysicalResourceProvider.INSTANCE,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,44 @@ String? _normalizedDirectory(String? path) {
|
|||
return _isDirectory(path) ? p.normalize(p.absolute(path)) : null;
|
||||
}
|
||||
|
||||
/// The SDK root holding a `<sdk>/bin/dart` executable, or null when the
|
||||
/// executable does not sit inside a Dart SDK.
|
||||
///
|
||||
/// The executable's own directory is `<sdk>/bin`, which never contains
|
||||
/// `lib/libraries.json`; checking it meant this fallback could never succeed.
|
||||
String? sdkFromExecutable(String executable) {
|
||||
final root = p.dirname(p.dirname(p.normalize(executable)));
|
||||
return _isDartSdk(root) ? root : null;
|
||||
}
|
||||
|
||||
/// Fails with an actionable error when the chosen SDK cannot be read.
|
||||
///
|
||||
/// Existence is not enough: macOS privacy protection (for example a Flutter SDK
|
||||
/// under ~/Documents) lets `stat` succeed while every open fails, so discovery
|
||||
/// accepted the SDK and the analyzer later died with a raw FileSystemException.
|
||||
void requireReadableSdk(SdkResolution resolution) {
|
||||
final sdk = resolution.sdkPath;
|
||||
if (sdk == null) return;
|
||||
for (final probe in [
|
||||
p.join(sdk, 'version'),
|
||||
p.join(sdk, 'lib', 'libraries.json'),
|
||||
]) {
|
||||
final file = File(probe);
|
||||
if (!file.existsSync()) continue;
|
||||
try {
|
||||
file.openSync().closeSync();
|
||||
} on FileSystemException catch (error) {
|
||||
throw PipeException(
|
||||
'the Dart SDK at $sdk (from ${resolution.source}) cannot be read: '
|
||||
'${error.osError?.message ?? error.message}',
|
||||
remedy:
|
||||
'grant this process access to that directory, or pass '
|
||||
'--sdk <path to a readable dart-sdk>',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a Flutter root, return its bundled Dart SDK if one is present.
|
||||
String? dartSdkInFlutterRoot(String flutterRoot) {
|
||||
final candidate = p.join(flutterRoot, 'bin', 'cache', 'dart-sdk');
|
||||
|
|
@ -144,8 +182,8 @@ SdkResolution resolveSdk({String? explicit, String? flutterRootHint}) {
|
|||
);
|
||||
}
|
||||
|
||||
final runtimeSdk = p.dirname(p.normalize(Platform.resolvedExecutable));
|
||||
if (_isDartSdk(runtimeSdk)) {
|
||||
final runtimeSdk = sdkFromExecutable(Platform.resolvedExecutable);
|
||||
if (runtimeSdk != null) {
|
||||
return SdkResolution(
|
||||
sdkPath: runtimeSdk,
|
||||
flutterRoot: null,
|
||||
|
|
|
|||
61
test/sdk_test.dart
Normal file
61
test/sdk_test.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:pipe/pipe.dart';
|
||||
import 'package:pipe/src/sdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// A directory laid out like a Dart SDK: `bin/dart`, `lib/libraries.json`,
|
||||
/// `version`.
|
||||
Directory _fakeSdk(String label) {
|
||||
final sdk = Directory.systemTemp.createTempSync('pipe-$label');
|
||||
File('${sdk.path}/version').writeAsStringSync('3.13.0\n');
|
||||
File('${sdk.path}/lib/libraries.json')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('{}');
|
||||
File('${sdk.path}/bin/dart').createSync(recursive: true);
|
||||
return sdk;
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('an SDK that exists but cannot be read is a clear error', () {
|
||||
// macOS privacy protection (a Flutter SDK under ~/Documents) lets stat
|
||||
// succeed while every open fails, so discovery accepted the SDK and the
|
||||
// analyzer later died with a raw FileSystemException stack trace.
|
||||
final sdk = _fakeSdk('unreadable-sdk');
|
||||
final version = File('${sdk.path}/version');
|
||||
Process.runSync('chmod', ['000', version.path]);
|
||||
try {
|
||||
version.readAsStringSync();
|
||||
markTestSkipped('chmod 000 does not restrict this process');
|
||||
return;
|
||||
} on FileSystemException {
|
||||
// Expected: the restriction is real, so the check below is meaningful.
|
||||
}
|
||||
|
||||
final resolution = resolveSdk(explicit: sdk.path);
|
||||
expect(
|
||||
() => requireReadableSdk(resolution),
|
||||
throwsA(
|
||||
isA<PipeException>()
|
||||
.having((e) => e.message, 'message', contains(sdk.path))
|
||||
.having((e) => e.remedy, 'remedy', contains('--sdk')),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('a readable SDK passes the check', () {
|
||||
final sdk = _fakeSdk('readable-sdk');
|
||||
expect(
|
||||
() => requireReadableSdk(resolveSdk(explicit: sdk.path)),
|
||||
returnsNormally,
|
||||
);
|
||||
});
|
||||
|
||||
test('the running dart binary maps to its SDK root, not to bin/', () {
|
||||
// dirname(<sdk>/bin/dart) is <sdk>/bin, which never contains
|
||||
// lib/libraries.json, so this fallback could never succeed.
|
||||
final sdk = _fakeSdk('runtime-sdk');
|
||||
expect(sdkFromExecutable('${sdk.path}/bin/dart'), sdk.path);
|
||||
expect(sdkFromExecutable('${sdk.path}/version'), isNull);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue