// Copyright (c) 2019, 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 'dart:typed_data'; import 'constants.dart' as json_key; import 'errors.dart'; import 'package_config.dart'; import 'package_config_io.dart'; import 'package_config_json.dart'; import 'util_io.dart' show defaultLoader, pathJoin; /// URI used as argument to [Uri.resolveUri] to create package config path. final Uri _packageConfigJsonPath = Uri(path: '.dart_tool/package_config.json'); /// URI used as argument to [Uri.resolveUri] to convert a URI to a directory. final Uri _currentPath = Uri(path: '.'); /// URI used as argument to [Uri.resolveUri] to get URI for parent directory. final Uri _parentPath = Uri(path: '..'); /// Discover the package configuration for a Dart script. /// /// The [baseDirectory] points to the directory of the Dart script. /// A package resolution strategy is found by going through the following steps, /// and stopping when something is found. /// /// * Check if a `.dart_tool/package_config.json` file exists in the directory. /// * Repeat this check for the parent directories until reaching the /// root directory if [recursive] is true. /// /// If any such a test succeeds, a `PackageConfig` class is returned. /// Returns `null` if no configuration was found. If a configuration /// is needed, then the caller can supply [PackageConfig.empty]. /// /// If [minVersion] is greater than the version read from the /// `package_config.json` file, the file is ignored if [skipInvalid] is `true`, /// or reported to [onError] if [skipInvalid] is `false`. Future<({PackageConfig config, File file})?> findPackageConfig( Directory baseDirectory, int minVersion, bool recursive, bool skipInvalid, void Function(Object error, File file) onError, ) async { var directory = baseDirectory; if (!directory.isAbsolute) directory = directory.absolute; if (!await directory.exists()) { return null; } do { var packageConfigAndFile = await findPackageConfigInDirectory( directory, minVersion, skipInvalid, onError, ); if (packageConfigAndFile != null) return packageConfigAndFile; if (!recursive) break; // Check in parent directories. var parentDirectory = directory.parent; if (parentDirectory.path == directory.path) break; directory = parentDirectory; } while (true); return null; } /// Similar to [findPackageConfig] but based on a URI. Future<({PackageConfig config, Uri file})?> findPackageConfigUri( Uri location, int minVersion, Future Function(Uri uri)? loader, bool recursive, bool skipInvalid, void Function(Object error, Uri file) onError, ) async { if (location.isScheme('package')) { throw PackageConfigArgumentError( location, 'location', 'Must not be a package: URI', ); } if (loader == null) { if (location.isScheme('file')) { var configAndFile = await findPackageConfig( Directory.fromUri(location.resolveUri(_currentPath)), minVersion, recursive, skipInvalid, (Object error, File file) => onError(error, file.uri), ); if (configAndFile case (:var config, :var file)) { return (config: config, file: file.uri); } } loader = defaultLoader; } if (!location.path.endsWith('/')) { location = location.resolveUri(_currentPath); } while (true) { var file = location.resolveUri(_packageConfigJsonPath); var bytes = await loader(file); if (bytes != null) { var config = parsePackageConfigBytes( bytes, file, (Object error) => onError(error, file), ); if (config.version >= minVersion) return (config: config, file: file); if (!skipInvalid) { onError( PackageConfigVersionException.supportedLimit( config.version, json_key.configVersion, ), file, ); } } if (!recursive) break; var parent = location.resolveUri(_parentPath); if (parent == location) break; location = parent; } return null; } /// Finds a `.dart_tool/package_config.json` file in [directory]. /// /// Loads the file, if it is there, and returns the resulting [PackageConfig]. /// Returns `null` if the file isn't there. /// Reports a [FormatException] if a file is there but the content is not valid. /// If the file exists, but fails to be read, the file system error is reported. /// /// If [onError] is supplied, parsing errors are reported using that, and /// a best-effort attempt is made to return a package configuration. /// This may be the empty package configuration. /// /// If [minVersion] is greater than the version read from the /// `package_config.json` file, it too is ignored. Future<({PackageConfig config, File file})?> findPackageConfigInDirectory( Directory directory, int minVersion, bool skipInvalid, void Function(Object error, File file) onError, ) async { var packageConfigFile = await checkForPackageConfigJsonFile(directory); if (packageConfigFile != null) { var hasError = false; var config = await readConfigFile(packageConfigFile, (Object error) { hasError = true; onError(error, packageConfigFile); }); if (!hasError && config.version < minVersion) { if (skipInvalid) return null; onError( PackageConfigVersionException.clientLimit( config.version, minVersion, null, ), packageConfigFile, ); } return (config: config, file: packageConfigFile); } return null; } Future checkForPackageConfigJsonFile(Directory directory) async { assert(directory.isAbsolute); var file = File( pathJoin(directory.path, '.dart_tool', 'package_config.json'), ); if (await file.exists()) return file; return null; } /// Extracts the [PackageConfig] from a package config-and-file pair. PackageConfig? configOnly(({PackageConfig config, Object file})? pair) => pair?.config;