| // 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:convert'; |
| |
| import 'package:path/path.dart' as p; |
| import 'package:pub_semver/pub_semver.dart'; |
| |
| import 'language_version.dart'; |
| |
| /// Contents of a `.dart_tool/package_config.json` file. |
| class PackageConfig { |
| /// Version of the configuration in the `.dart_tool/package_config.json` file. |
| /// |
| /// The only supported value as of writing is `2`. |
| int configVersion; |
| |
| /// Packages configured. |
| List<PackageConfigEntry> packages; |
| |
| /// Date-time the `.dart_tool/package_config.json` file was generated. |
| /// |
| /// `null` if not given. |
| DateTime? generated; |
| |
| /// Tool that generated the `.dart_tool/package_config.json` file. |
| /// |
| /// For `pub` this is always `'pub'`. |
| /// |
| /// `null` if not given. |
| String? generator; |
| |
| /// Version of the tool that generated the `.dart_tool/package_config.json` |
| /// file. |
| /// |
| /// For `pub` this is the Dart SDK version from which `pub get` was called. |
| /// |
| /// `null` if not given. |
| Version? generatorVersion; |
| |
| /// Additional properties not in the specification for the |
| /// `.dart_tool/package_config.json` file. |
| Map<String, dynamic> additionalProperties; |
| |
| PackageConfig({ |
| required this.configVersion, |
| required this.packages, |
| this.generated, |
| this.generator, |
| this.generatorVersion, |
| Map<String, dynamic>? additionalProperties, |
| }) : additionalProperties = additionalProperties ?? {}; |
| |
| /// Create [PackageConfig] from JSON [data]. |
| /// |
| /// Throws [FormatException], if format is invalid, this does not validate the |
| /// contents only that the format is correct. |
| factory PackageConfig.fromJson(Object data) { |
| if (data is! Map<String, dynamic>) { |
| throw FormatException('package_config.json must be a JSON object'); |
| } |
| final root = data; |
| |
| void throwFormatException(String property, String mustBe) => |
| throw FormatException( |
| '"$property" in .dart_tool/package_config.json $mustBe'); |
| |
| /// Read the 'configVersion' property |
| final configVersion = root['configVersion']; |
| if (configVersion is! int) { |
| throwFormatException('configVersion', 'must be an integer'); |
| } |
| if (configVersion != 2) { |
| throwFormatException( |
| 'configVersion', 'must be 2 (the only supported version)'); |
| } |
| |
| final packagesRaw = root['packages']; |
| if (packagesRaw is! List) { |
| throwFormatException('packages', 'must be a list'); |
| } |
| final packages = <PackageConfigEntry>[]; |
| for (final entry in packagesRaw) { |
| packages.add(PackageConfigEntry.fromJson(entry)); |
| } |
| |
| // Read the 'generated' property |
| DateTime? generated; |
| final generatedRaw = root['generated']; |
| if (generatedRaw != null) { |
| if (generatedRaw is! String) { |
| throwFormatException('generated', 'must be a string, if given'); |
| } |
| generated = DateTime.parse(generatedRaw); |
| } |
| |
| // Read the 'generator' property |
| final generator = root['generator']; |
| if (generator != null && generator is! String) { |
| throw FormatException( |
| '"generator" in package_config.json must be a string, if given'); |
| } |
| |
| // Read the 'generatorVersion' property |
| Version? generatorVersion; |
| final generatorVersionRaw = root['generatorVersion']; |
| if (generatorVersionRaw != null) { |
| if (generatorVersionRaw is! String) { |
| throwFormatException('generatorVersion', 'must be a string, if given'); |
| } |
| try { |
| generatorVersion = Version.parse(generatorVersionRaw); |
| } on FormatException catch (e) { |
| throwFormatException('generatorVersion', |
| 'must be a semver version, if given, error: ${e.message}'); |
| } |
| } |
| |
| return PackageConfig( |
| configVersion: configVersion as int, |
| packages: packages, |
| generated: generated, |
| generator: generator, |
| generatorVersion: generatorVersion, |
| additionalProperties: Map.fromEntries(root.entries.where((e) => !{ |
| 'configVersion', |
| 'packages', |
| 'generated', |
| 'generator', |
| 'generatorVersion', |
| }.contains(e.key)))); |
| } |
| |
| /// Convert to JSON structure. |
| Map<String, Object?> toJson() => { |
| 'configVersion': configVersion, |
| 'packages': packages.map((p) => p.toJson()).toList(), |
| 'generated': generated?.toUtc().toIso8601String(), |
| 'generator': generator, |
| 'generatorVersion': generatorVersion?.toString(), |
| }..addAll(additionalProperties); |
| |
| // We allow the package called 'flutter_gen' to be injected into |
| // package_config. |
| // |
| // This is somewhat a hack. But it allows flutter to generate code in a |
| // package as it likes. |
| // |
| // See https://github.com/flutter/flutter/issues/73870 . |
| Iterable<PackageConfigEntry> get nonInjectedPackages => |
| packages.where((package) => package.name != 'flutter_gen'); |
| } |
| |
| class PackageConfigEntry { |
| /// Package name. |
| String name; |
| |
| /// Root [Uri] of the package. |
| /// |
| /// This specifies the root folder of the package, all files below this folder |
| /// is considered part of this package. |
| Uri rootUri; |
| |
| /// Relative URI path of the library folder relative to [rootUri]. |
| /// |
| /// Import statements in Dart programs are resolved relative to this folder. |
| /// This must be in the sub-tree under [rootUri]. |
| /// |
| /// `null` if not given. |
| Uri? packageUri; |
| |
| /// Language version used by package. |
| /// |
| /// Given as `<major>.<minor>` version, similar to the `// @dart = X.Y` |
| /// comment. This is derived from the lower-bound on the Dart SDK requirement |
| /// in the `pubspec.yaml` for the given package. |
| LanguageVersion? languageVersion; |
| |
| /// Additional properties not in the specification for the |
| /// `.dart_tool/package_config.json` file. |
| Map<String, dynamic>? additionalProperties; |
| |
| PackageConfigEntry({ |
| required this.name, |
| required this.rootUri, |
| this.packageUri, |
| this.languageVersion, |
| this.additionalProperties = const {}, |
| }); |
| |
| /// Create [PackageConfigEntry] from JSON [data]. |
| /// |
| /// Throws [FormatException], if format is invalid, this does not validate the |
| /// contents only that the format is correct. |
| factory PackageConfigEntry.fromJson(Object data) { |
| if (data is! Map<String, dynamic>) { |
| throw FormatException( |
| 'packages[] entries in package_config.json must be JSON objects'); |
| } |
| final root = data; |
| |
| Never throwFormatException(String property, String mustBe) => |
| throw FormatException( |
| '"packages[].$property" in .dart_tool/package_config.json $mustBe'); |
| |
| final name = root['name']; |
| if (name is! String) { |
| throwFormatException('name', 'must be a string'); |
| } |
| |
| final Uri rootUri; |
| final rootUriRaw = root['rootUri']; |
| if (rootUriRaw is! String) { |
| throwFormatException('rootUri', 'must be a string'); |
| } |
| try { |
| rootUri = Uri.parse(rootUriRaw); |
| } on FormatException { |
| throwFormatException('rootUri', 'must be a URI'); |
| } |
| |
| Uri? packageUri; |
| var packageUriRaw = root['packageUri']; |
| if (packageUriRaw != null) { |
| if (packageUriRaw is! String) { |
| throwFormatException('packageUri', 'must be a string'); |
| } |
| if (!packageUriRaw.endsWith('/')) { |
| packageUriRaw = '$packageUriRaw/'; |
| } |
| try { |
| packageUri = Uri.parse(packageUriRaw); |
| } on FormatException { |
| throwFormatException('packageUri', 'must be a URI'); |
| } |
| } |
| |
| LanguageVersion? languageVersion; |
| final languageVersionRaw = root['languageVersion']; |
| if (languageVersionRaw != null) { |
| if (languageVersionRaw is! String) { |
| throwFormatException('languageVersion', 'must be a string'); |
| } |
| try { |
| languageVersion = LanguageVersion.parse(languageVersionRaw); |
| } on FormatException { |
| throwFormatException( |
| 'languageVersion', 'must be on the form <major>.<minor>'); |
| } |
| } |
| |
| return PackageConfigEntry( |
| name: name, |
| rootUri: rootUri, |
| packageUri: packageUri, |
| languageVersion: languageVersion, |
| ); |
| } |
| |
| /// Convert to JSON structure. |
| Map<String, Object?> toJson() => { |
| 'name': name, |
| 'rootUri': rootUri.toString(), |
| if (packageUri != null) 'packageUri': packageUri.toString(), |
| if (languageVersion != null) 'languageVersion': '$languageVersion', |
| }..addAll(additionalProperties ?? {}); |
| |
| @override |
| String toString() { |
| // TODO: implement toString |
| return JsonEncoder.withIndent(' ').convert(toJson()); |
| } |
| |
| String resolvedRootDir(String packageConfigPath) { |
| return p.join(p.dirname(packageConfigPath), p.fromUri(rootUri)); |
| } |
| } |