blob: f2eaffc4eee2d82055ead13ee1e1ca97a58dec6b [file] [log] [blame]
// Copyright (c) 2023, 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' show File, IOException;
import 'expect_json.dart';
typedef PackageConfigEntry = ({String name, Uri rootUri, Uri packageUri});
typedef PackageConfig = List<PackageConfigEntry>;
/// Searches in [packageDir] and all directories above for a
/// `.dart_tool/package_config.json` file. Returns the Uri of that file if
/// found.
///
/// Returns `null` if no file was found.
Uri? findPackageConfig(Uri packageDir) {
if (!packageDir.isScheme('file')) {
throw ArgumentError(
'Expected [packageDir] to be a file URI, got $packageDir',
);
}
if (!packageDir.path.endsWith('/')) {
packageDir = packageDir.replace(path: '${packageDir.path}/');
}
while (true) {
final packageConfigCandidate =
packageDir.resolve('.dart_tool/package_config.json');
try {
if (File.fromUri(packageConfigCandidate).existsSync()) {
return packageConfigCandidate;
}
} on IOException {
return null; // if we get a permission error, etc, we return null
}
final next = packageDir.resolve('..');
if (next == packageDir) return null;
packageDir = next;
}
}
/// Load list of packages and associated URIs from the
/// `.dart_tool/package_config.json` file in [packageConfigFile].
Future<PackageConfig> loadPackageConfig(
File packageConfigFile,
) async {
try {
final packageConfig = decodeJsonMap(await packageConfigFile.readAsString());
if (packageConfig.expectNumber('configVersion') != 2) {
throw const FormatException('"configVersion" must be 2');
}
return packageConfig.expectListObjects('packages').map((p) {
final rootUri = p.expectUri('rootUri').asDirectory();
return (
name: p.expectString('name'),
rootUri: rootUri,
packageUri: p.optionalUri('packageUri')?.asDirectory() ?? rootUri,
);
}).toList();
} on IOException catch (e) {
if (!packageConfigFile.existsSync()) {
throw packageConfigNotFound(packageConfigFile.uri);
}
throw packageConfigIOException(e);
} on FormatException catch (e) {
throw packageConfigInvalid(packageConfigFile.uri, e);
}
}
/// Thrown, if the `.dart_tool/package_config.json` cannot be found or parsing
/// it fails.
///
/// This could be because the `package_config.json` file is missing or simply
/// invalid.
///
/// Mostly, this will happen if dependencies are not resolved, the solution is
/// call `dart pub get`.
final class PackageConfigException implements Exception {
final String message;
PackageConfigException._(this.message);
@override
String toString() => message;
}
PackageConfigException packageConfigNotFound(Uri packageConfigUri) =>
PackageConfigException._(
'package_config.json not found at: "$packageConfigUri"',
);
PackageConfigException packageConfigInvalid(
Uri packageConfigUri, FormatException e) =>
PackageConfigException._(
'Invalid package_config.json found at "$packageConfigUri": $e',
);
PackageConfigException packageConfigIOException(IOException e) =>
PackageConfigException._(
'Failed to read package_config.json: $e',
);
extension on Uri {
Uri asDirectory() => replace(
pathSegments: pathSegments.lastOrNull != ''
? pathSegments.followedBy([''])
: pathSegments,
);
}