blob: 9aed621e09a89e1ed1d79468e6a6ceeb81170f1e [file] [log] [blame]
// 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.
// dart:io dependent functionality for reading and writing configuration files.
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'errors.dart';
import 'package_config_impl.dart';
import 'package_config_json.dart';
import 'packages_file.dart' as packages_file;
import 'util.dart';
import 'util_io.dart';
/// Name of directory where Dart tools store their configuration.
///
/// Directory is created in the package root directory.
const dartToolDirName = '.dart_tool';
/// Name of file containing new package configuration data.
///
/// File is stored in the dart tool directory.
const packageConfigFileName = 'package_config.json';
/// Name of file containing legacy package configuration data.
///
/// File is stored in the package root directory.
const packagesFileName = '.packages';
/// Reads a package configuration file.
///
/// Detects whether the [file] is a version one `.packages` file or
/// a version two `package_config.json` file.
///
/// If the [file] is a `.packages` file and [preferNewest] is true,
/// first checks whether there is an adjacent `.dart_tool/package_config.json`
/// file, and if so, reads that instead.
/// If [preferNewset] is false, the specified file is loaded even if it is
/// a `.packages` file and there is an available `package_config.json` file.
///
/// The file must exist and be a normal file.
Future<PackageConfig> readAnyConfigFile(
File file, bool preferNewest, void Function(Object error) onError) async {
if (preferNewest && fileName(file.path) == packagesFileName) {
var alternateFile = File(
pathJoin(dirName(file.path), dartToolDirName, packageConfigFileName));
if (alternateFile.existsSync()) {
return await readPackageConfigJsonFile(alternateFile, onError);
}
}
Uint8List bytes;
try {
bytes = await file.readAsBytes();
} catch (e) {
onError(e);
return const SimplePackageConfig.empty();
}
return parseAnyConfigFile(bytes, file.uri, onError);
}
/// Like [readAnyConfigFile] but uses a URI and an optional loader.
Future<PackageConfig> readAnyConfigFileUri(
Uri file,
Future<Uint8List?> Function(Uri uri)? loader,
void Function(Object error) onError,
bool preferNewest) async {
if (file.isScheme('package')) {
throw PackageConfigArgumentError(
file, 'file', 'Must not be a package: URI');
}
if (loader == null) {
if (file.isScheme('file')) {
return await readAnyConfigFile(File.fromUri(file), preferNewest, onError);
}
loader = defaultLoader;
}
if (preferNewest && file.pathSegments.last == packagesFileName) {
var alternateFile = file.resolve('$dartToolDirName/$packageConfigFileName');
Uint8List? bytes;
try {
bytes = await loader(alternateFile);
} catch (e) {
onError(e);
return const SimplePackageConfig.empty();
}
if (bytes != null) {
return parsePackageConfigBytes(bytes, alternateFile, onError);
}
}
Uint8List? bytes;
try {
bytes = await loader(file);
} catch (e) {
onError(e);
return const SimplePackageConfig.empty();
}
if (bytes == null) {
onError(PackageConfigArgumentError(
file.toString(), 'file', 'File cannot be read'));
return const SimplePackageConfig.empty();
}
return parseAnyConfigFile(bytes, file, onError);
}
/// Parses a `.packages` or `package_config.json` file's contents.
///
/// Assumes it's a JSON file if the first non-whitespace character
/// is `{`, otherwise assumes it's a `.packages` file.
PackageConfig parseAnyConfigFile(
Uint8List bytes, Uri file, void Function(Object error) onError) {
var firstChar = firstNonWhitespaceChar(bytes);
if (firstChar != $lbrace) {
// Definitely not a JSON object, probably a .packages.
return packages_file.parse(bytes, file, onError);
}
return parsePackageConfigBytes(bytes, file, onError);
}
Future<PackageConfig> readPackageConfigJsonFile(
File file, void Function(Object error) onError) async {
Uint8List bytes;
try {
bytes = await file.readAsBytes();
} catch (error) {
onError(error);
return const SimplePackageConfig.empty();
}
return parsePackageConfigBytes(bytes, file.uri, onError);
}
Future<PackageConfig> readDotPackagesFile(
File file, void Function(Object error) onError) async {
Uint8List bytes;
try {
bytes = await file.readAsBytes();
} catch (error) {
onError(error);
return const SimplePackageConfig.empty();
}
return packages_file.parse(bytes, file.uri, onError);
}
Future<void> writePackageConfigJsonFile(
PackageConfig config, Directory targetDirectory) async {
// Write .dart_tool/package_config.json first.
var dartToolDir = Directory(pathJoin(targetDirectory.path, dartToolDirName));
await dartToolDir.create(recursive: true);
var file = File(pathJoin(dartToolDir.path, packageConfigFileName));
var baseUri = file.uri;
var sink = file.openWrite(encoding: utf8);
writePackageConfigJsonUtf8(config, baseUri, sink);
var doneJson = sink.close();
// Write .packages too.
file = File(pathJoin(targetDirectory.path, packagesFileName));
baseUri = file.uri;
sink = file.openWrite(encoding: utf8);
writeDotPackages(config, baseUri, sink);
var donePackages = sink.close();
await Future.wait([doneJson, donePackages]);
}