| // Copyright (c) 2017, 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 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/source/source.dart'; | |
| import 'package:analyzer/src/context/packages.dart'; | |
| import 'package:analyzer/src/file_system/file_system.dart'; | |
| import 'package:analyzer/src/generated/sdk.dart'; | |
| import 'package:analyzer/src/generated/source.dart'; | |
| import 'package:analyzer/src/source/package_map_resolver.dart'; | |
| import 'package:analyzer/src/summary/package_bundle_reader.dart'; | |
| import 'package:analyzer/src/util/file_paths.dart' as file_paths; | |
| import 'package:analyzer/src/workspace/workspace.dart'; | |
| import 'package:collection/collection.dart'; | |
| import 'package:path/path.dart' as path; | |
| /// Information about a Gn workspace. | |
| class GnWorkspace extends Workspace { | |
| /// The name of the directory that identifies the root of the workspace. | |
| static const String _jiriRootName = '.jiri_root'; | |
| /// The resource provider used to access the file system. | |
| final ResourceProvider provider; | |
| /// The absolute workspace root path (the directory containing the | |
| /// `.jiri_root` directory). | |
| @override | |
| final String root; | |
| final File buildGnFile; | |
| @override | |
| final Packages packages; | |
| GnWorkspace._(this.provider, this.root, this.buildGnFile, this.packages); | |
| // TODO(scheglov): Finish switching to [packages]. | |
| Map<String, Folder> get packageMap { | |
| var packageMap = <String, Folder>{}; | |
| for (var package in packages.packages) { | |
| packageMap[package.name] = package.libFolder; | |
| } | |
| return packageMap; | |
| } | |
| @override | |
| UriResolver get packageUriResolver => | |
| PackageMapUriResolver(provider, packageMap); | |
| @override | |
| SourceFactory createSourceFactory( | |
| DartSdk? sdk, | |
| SummaryDataStore? summaryData, | |
| ) { | |
| if (summaryData != null) { | |
| throw UnsupportedError( | |
| 'Summary files are not supported in a GN workspace.', | |
| ); | |
| } | |
| List<UriResolver> resolvers = <UriResolver>[]; | |
| if (sdk != null) { | |
| resolvers.add(DartUriResolver(sdk)); | |
| } | |
| resolvers.add(packageUriResolver); | |
| resolvers.add(ResourceUriResolver(provider)); | |
| return SourceFactory(resolvers); | |
| } | |
| /// Return the file with the given [absolutePath]. | |
| /// | |
| /// Return `null` if the given [absolutePath] is not in the workspace [root]. | |
| File? findFile(String absolutePath) { | |
| try { | |
| File writableFile = provider.getFile(absolutePath); | |
| if (writableFile.exists) { | |
| return writableFile; | |
| } | |
| } catch (_) {} | |
| return null; | |
| } | |
| @override | |
| WorkspacePackageImpl? findPackageFor(String filePath) { | |
| var startFolder = provider.getFolder(filePath); | |
| for (var folder in startFolder.withAncestors) { | |
| if (folder.path.length < root.length) { | |
| // We've walked up outside of [root], so [path] is definitely not | |
| // defined in any package in this workspace. | |
| return null; | |
| } | |
| if (folder.getFile(file_paths.buildGn).exists) { | |
| return GnWorkspacePackage(folder, this); | |
| } | |
| } | |
| return null; | |
| } | |
| /// Find the GN workspace for the given [buildGnFile]. | |
| /// | |
| /// Return `null` if a workspace could not be found. For a workspace to be | |
| /// found, the `.jiri_root` folder must be found, and at least one | |
| /// `<anything>package_config.json` file must be found in [buildGnFile]'s | |
| /// output directory. | |
| static GnWorkspace? find(File buildGnFile) { | |
| var provider = buildGnFile.provider; | |
| for (var folder in buildGnFile.parent.withAncestors) { | |
| if (folder.getFolder(_jiriRootName).exists) { | |
| // Found the .jiri_root file, must be a non-git workspace. | |
| String root = folder.path; | |
| var packagesFiles = _findPackagesFile(provider, root, buildGnFile); | |
| if (packagesFiles.isEmpty) { | |
| return null; | |
| } | |
| var packageMap = <String, Package>{}; | |
| for (var packagesFile in packagesFiles) { | |
| var packages = parsePackageConfigJsonFile(provider, packagesFile); | |
| for (var package in packages.packages) { | |
| packageMap[package.name] = package; | |
| } | |
| } | |
| return GnWorkspace._(provider, root, buildGnFile, Packages(packageMap)); | |
| } | |
| } | |
| return null; | |
| } | |
| /// For a source at `$root/foo/bar`, the packages files are generated in | |
| /// `$root/out/<debug|release>-XYZ/dartlang/gen/foo/bar`. | |
| /// | |
| /// Note that in some cases multiple package_config.json files can be found at | |
| /// that location, for example if the package contains both a library and a | |
| /// binary target. For a complete view of the package, all of these files need | |
| /// to be taken into account. | |
| /// | |
| /// Additionally, often times the package_config file name is prepended by | |
| /// extra words, which results in file names like | |
| /// `tiler_component_package_config.json`. Because of this, we cannot simply | |
| /// check for `pathContext.basename(file.path) == 'package_config.json'`. | |
| static List<File> _findPackagesFile( | |
| ResourceProvider provider, | |
| String root, | |
| File buildGnFile, | |
| ) { | |
| path.Context pathContext = provider.pathContext; | |
| String sourceDirectory = pathContext.relative( | |
| buildGnFile.parent.path, | |
| from: root, | |
| ); | |
| var outDirectory = _getOutDirectory(root, provider); | |
| if (outDirectory == null) { | |
| return const <File>[]; | |
| } | |
| Folder genDir = outDirectory.getFolder( | |
| pathContext.join('dartlang', 'gen', sourceDirectory), | |
| ); | |
| if (!genDir.exists) { | |
| return const <File>[]; | |
| } | |
| return genDir | |
| .getChildren() | |
| .whereType<File>() | |
| .where((File file) => file.path.endsWith('package_config.json')) | |
| .toList(); | |
| } | |
| /// Returns the output directory of the build, or `null` if it could not be | |
| /// found. | |
| /// | |
| /// First attempts to read a config file at the root of the source tree. If | |
| /// that file cannot be found, looks for standard output directory locations. | |
| static Folder? _getOutDirectory(String root, ResourceProvider provider) { | |
| const String fuchsiaDirConfigFile = '.fx-build-dir'; | |
| path.Context pathContext = provider.pathContext; | |
| File configFile = provider.getFile( | |
| pathContext.join(root, fuchsiaDirConfigFile), | |
| ); | |
| if (configFile.exists) { | |
| String buildDirPath = configFile.readAsStringSync().trim(); | |
| if (buildDirPath.isNotEmpty) { | |
| if (pathContext.isRelative(buildDirPath)) { | |
| buildDirPath = pathContext.join(root, buildDirPath); | |
| } | |
| return provider.getFolder(buildDirPath); | |
| } | |
| } | |
| Folder outDirectory = provider.getFolder(pathContext.join(root, 'out')); | |
| if (!outDirectory.exists) { | |
| return null; | |
| } | |
| return outDirectory.getChildren().whereType<Folder>().firstWhereOrNull(( | |
| folder, | |
| ) { | |
| String baseName = pathContext.basename(folder.path); | |
| // Taking a best guess to identify a build dir. This is clearly a fallback | |
| // to the config-based method. | |
| return baseName.startsWith('debug') || baseName.startsWith('release'); | |
| }); | |
| } | |
| } | |
| /// Information about a package defined in a GnWorkspace. | |
| /// | |
| /// Separate from [Packages] or package maps, this class is designed to simply | |
| /// understand whether arbitrary file paths represent libraries declared within | |
| /// a given package in a GnWorkspace. | |
| class GnWorkspacePackage extends WorkspacePackageImpl { | |
| @override | |
| final Folder root; | |
| @override | |
| final GnWorkspace workspace; | |
| GnWorkspacePackage(this.root, this.workspace); | |
| @override | |
| bool contains(Source source) { | |
| var filePath = filePathFromSource(source); | |
| if (filePath == null) return false; | |
| if (workspace.findFile(filePath) == null) { | |
| return false; | |
| } | |
| if (!root.contains(filePath)) { | |
| return false; | |
| } | |
| // Just because [filePath] is within [root] does not mean it is in this | |
| // package; it could be in a "subpackage." Must go through the work of | |
| // learning exactly which package [filePath] is contained in. | |
| return workspace.findPackageFor(filePath)!.root.path == root.path; | |
| } | |
| @override | |
| Packages packagesAvailableTo(String libraryPath) => workspace.packages; | |
| @override | |
| bool sourceIsInPublicApi(Source source) { | |
| var filePath = filePathFromSource(source); | |
| if (filePath == null) return false; | |
| var libFolder = root.getFolder('lib'); | |
| if (libFolder.contains(filePath)) { | |
| var libSrcFolder = root.getFolder('lib').getFolder('src'); | |
| return !libSrcFolder.contains(filePath); | |
| } | |
| return false; | |
| } | |
| } |