| // Copyright (c) 2018, 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:async'; | |
| import 'package:analyzer/dart/analysis/declared_variables.dart'; | |
| import 'package:analyzer/dart/analysis/features.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/file_system/physical_file_system.dart'; | |
| import 'package:analyzer/src/analysis_options/analysis_options.dart'; | |
| import 'package:analyzer/src/analysis_options/analysis_options_parser.dart'; | |
| import 'package:analyzer/src/context/builder.dart' show locateEmbedderYamlFor; | |
| import 'package:analyzer/src/context/packages.dart'; | |
| import 'package:analyzer/src/dart/analysis/analysis_options_map.dart'; | |
| import 'package:analyzer/src/dart/analysis/byte_store.dart' | |
| show ByteStore, MemoryByteStore; | |
| import 'package:analyzer/src/dart/analysis/context_root.dart'; | |
| import 'package:analyzer/src/dart/analysis/driver.dart' | |
| show | |
| AnalysisDriver, | |
| AnalysisDriverScheduler, | |
| AnalysisDriverTestView, | |
| OwnedFiles; | |
| import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; | |
| import 'package:analyzer/src/dart/analysis/file_content_cache.dart'; | |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart' | |
| show PerformanceLog; | |
| import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart'; | |
| import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
| import 'package:analyzer/src/generated/sdk.dart' show DartSdk; | |
| import 'package:analyzer/src/generated/source.dart' show SourceFactory; | |
| import 'package:analyzer/src/summary/package_bundle_reader.dart'; | |
| import 'package:analyzer/src/summary/summary_sdk.dart'; | |
| import 'package:analyzer/src/summary2/package_bundle_format.dart'; | |
| import 'package:analyzer/src/workspace/workspace.dart'; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| /// A utility class used to build an analysis context based on a context root. | |
| class ContextBuilderImpl { | |
| /// The resource provider used to access the file system. | |
| final ResourceProvider resourceProvider; | |
| /// Analysis options mappings shared by all contexts built by this builder. | |
| final AnalysisOptionsMap _optionsMap = AnalysisOptionsMap(); | |
| /// Folder-based SDKs shared by contexts using the same SDK folder. | |
| final Map<Folder, FolderBasedDartSdk> _folderSdks = {}; | |
| /// Initialize a newly created context builder. If a [resourceProvider] is | |
| /// given, then it will be used to access the file system, otherwise the | |
| /// default resource provider will be used. | |
| ContextBuilderImpl({ResourceProvider? resourceProvider}) | |
| : resourceProvider = resourceProvider ?? PhysicalResourceProvider.INSTANCE; | |
| /// Return an analysis context corresponding to the given [contextRoot]. | |
| /// | |
| /// If a set of [declaredVariables] is provided, the values will be used to | |
| /// map the variable names found in `fromEnvironment` invocations to the | |
| /// constant value that will be returned. If none is given, then no variables | |
| /// will be defined. | |
| /// | |
| /// If a list of [librarySummaryPaths] is provided, then the summary files at | |
| /// those paths will be used, when possible, when analyzing the libraries | |
| /// contained in the summary files. | |
| /// | |
| /// If an [sdkPath] is provided, and if it is a valid path to a directory | |
| /// containing a valid SDK, then the SDK in the referenced directory will be | |
| /// used when analyzing the code in the context. | |
| /// | |
| /// If an [sdkSummaryPath] is provided, then that file will be used as the | |
| /// summary file for the SDK. | |
| DriverBasedAnalysisContext createContext({ | |
| ByteStore? byteStore, | |
| required ContextRootImpl contextRoot, | |
| bool definedOptionsFile = false, | |
| DeclaredVariables? declaredVariables, | |
| bool drainStreams = true, | |
| bool enableIndex = false, | |
| Version? languageVersionOverride, | |
| List<String>? librarySummaryPaths, | |
| PerformanceLog? performanceLog, | |
| bool retainDataForTesting = false, | |
| AnalysisDriverScheduler? scheduler, | |
| required String sdkPath, | |
| String? sdkSummaryPath, | |
| void Function({required AnalysisOptionsBuilder analysisOptionsBuilder})? | |
| configureAnalysisOptionsBuilder, | |
| FileContentCache? fileContentCache, | |
| UnlinkedUnitStore? unlinkedUnitStore, | |
| OwnedFiles? ownedFiles, | |
| bool enableLintRuleTiming = false, | |
| required bool withFineDependencies, | |
| List<String> enabledExperiments = const [], | |
| }) { | |
| byteStore ??= MemoryByteStore(); | |
| performanceLog ??= PerformanceLog(null); | |
| if (scheduler == null) { | |
| scheduler = AnalysisDriverScheduler(performanceLog); | |
| scheduler.start(); | |
| } | |
| SummaryDataStore? summaryData; | |
| if (librarySummaryPaths != null) { | |
| summaryData = SummaryDataStore(); | |
| for (var summaryPath in librarySummaryPaths) { | |
| var bytes = resourceProvider.getFile(summaryPath).readAsBytesSync(); | |
| var bundle = PackageBundleReader(bytes); | |
| summaryData.addBundle(summaryPath, bundle); | |
| } | |
| } | |
| var workspace = contextRoot.workspace; | |
| var sdk = _createSdk( | |
| workspace: workspace, | |
| sdkPath: sdkPath, | |
| sdkSummaryPath: sdkSummaryPath, | |
| ); | |
| // TODO(scheglov): Ensure that "librarySummaryPaths" not null only | |
| // when "sdkSummaryPath" is not null. | |
| if (sdk is SummaryBasedDartSdk) { | |
| summaryData?.addBundle(null, sdk.bundle); | |
| } | |
| var optionsFile = contextRoot.optionsFile; | |
| var sourceFactory = workspace.createSourceFactory(sdk, summaryData); | |
| var analysisOptionsParseSession = AnalysisOptionsParseSession(); | |
| // Applies the client's settings to analysis options. Every | |
| // `AnalysisOptionsImpl` of the context goes through this function once: | |
| // from the explicit options file, from each options file in the context | |
| // root, or the default options when there are no options files. | |
| // | |
| // Non-empty `enabledExperiments` replace the experiments from analysis | |
| // options files, so the client can also disable them. Empty means that | |
| // the client has no preference, and the options files are used. | |
| AnalysisOptionsImpl updateOptions(AnalysisOptionsImpl options) { | |
| if (enabledExperiments.isEmpty && | |
| configureAnalysisOptionsBuilder == null) { | |
| return options; | |
| } | |
| var analysisOptionsBuilder = AnalysisOptionsBuilder.from(options); | |
| if (enabledExperiments.isNotEmpty) { | |
| analysisOptionsBuilder.contextFeatures = FeatureSet.fromEnableFlags2( | |
| sdkLanguageVersion: sdk.languageVersion, | |
| flags: enabledExperiments, | |
| ); | |
| } | |
| configureAnalysisOptionsBuilder?.call( | |
| analysisOptionsBuilder: analysisOptionsBuilder, | |
| ); | |
| return analysisOptionsBuilder.build(); | |
| } | |
| AnalysisOptionsMap analysisOptionsMap; | |
| // If there's an options file defined (as, e.g. passed into the | |
| // AnalysisContextCollection), use a shared options map based on it. | |
| if (definedOptionsFile && optionsFile != null) { | |
| analysisOptionsMap = AnalysisOptionsMap.forSharedOptions( | |
| _getAnalysisOptions( | |
| optionsFile, | |
| analysisOptionsParseSession, | |
| sourceFactory, | |
| contextRoot.root, | |
| updateOptions, | |
| ), | |
| ); | |
| } else { | |
| // Otherwise, create one from the options file mappings stored in the | |
| // context root. | |
| analysisOptionsMap = _createOptionsMap( | |
| contextRoot, | |
| analysisOptionsParseSession, | |
| sourceFactory, | |
| updateOptions, | |
| ); | |
| } | |
| var analysisContext = DriverBasedAnalysisContext( | |
| resourceProvider, | |
| contextRoot, | |
| ); | |
| var driver = AnalysisDriver( | |
| scheduler: scheduler, | |
| logger: performanceLog, | |
| resourceProvider: resourceProvider, | |
| byteStore: byteStore, | |
| sourceFactory: sourceFactory, | |
| analysisOptionsMap: analysisOptionsMap, | |
| packages: _createPackageMap(contextRoot: contextRoot), | |
| analysisContext: analysisContext, | |
| enableIndex: enableIndex, | |
| languageVersionOverride: languageVersionOverride, | |
| externalSummaries: summaryData, | |
| retainDataForTesting: retainDataForTesting, | |
| fileContentCache: fileContentCache, | |
| unlinkedUnitStore: unlinkedUnitStore, | |
| declaredVariables: declaredVariables, | |
| testView: retainDataForTesting ? AnalysisDriverTestView() : null, | |
| ownedFiles: ownedFiles, | |
| enableLintRuleTiming: enableLintRuleTiming, | |
| withFineDependencies: withFineDependencies, | |
| ); | |
| // AnalysisDriver reports results into streams. | |
| // We need to drain these streams to avoid memory leak. | |
| if (drainStreams) { | |
| unawaited(driver.exceptions.drain<void>()); | |
| } | |
| return analysisContext; | |
| } | |
| /// Create an [AnalysisOptionsMap] for the given [contextRoot]. | |
| AnalysisOptionsMap _createOptionsMap( | |
| ContextRootImpl contextRoot, | |
| AnalysisOptionsParseSession analysisOptionsParseSession, | |
| SourceFactory sourceFactory, | |
| AnalysisOptionsImpl Function(AnalysisOptionsImpl) updateOptions, | |
| ) { | |
| var optionsMappings = contextRoot.optionsFileMap.entries; | |
| for (var MapEntry(key: file, value: folders) in optionsMappings) { | |
| AnalysisOptionsImpl options; | |
| try { | |
| options = analysisOptionsParseSession | |
| .parse( | |
| sourceFactory: sourceFactory, | |
| contextRoot: contextRoot.root, | |
| file: file, | |
| ) | |
| .analysisOptions; | |
| } catch (e) { | |
| // Ignore exception. | |
| options = AnalysisOptionsImpl(file: file); | |
| } | |
| options = updateOptions(options); | |
| for (var folder in folders) { | |
| _optionsMap[folder] = options; | |
| } | |
| } | |
| if (_optionsMap.folders.isEmpty) { | |
| return AnalysisOptionsMap.forSharedOptions( | |
| updateOptions(AnalysisOptionsImpl()), | |
| ); | |
| } | |
| return _optionsMap; | |
| } | |
| /// Return [Packages] to analyze the [contextRoot]. | |
| /// | |
| // TODO(scheglov): Get [Packages] from [Workspace]? | |
| Packages _createPackageMap({required ContextRootImpl contextRoot}) { | |
| var packagesFile = contextRoot.packagesFile; | |
| if (packagesFile != null) { | |
| return parsePackageConfigJsonFile(resourceProvider, packagesFile); | |
| } else { | |
| return Packages.empty; | |
| } | |
| } | |
| /// Return the SDK that should be used to analyze code. | |
| DartSdk _createSdk({ | |
| required Workspace workspace, | |
| required String sdkPath, | |
| String? sdkSummaryPath, | |
| }) { | |
| if (sdkSummaryPath != null) { | |
| var file = resourceProvider.getFile(sdkSummaryPath); | |
| var bytes = file.readAsBytesSync(); | |
| return SummaryBasedDartSdk.forBundle(PackageBundleReader(bytes)); | |
| } | |
| var sdkFolder = resourceProvider.getFolder(sdkPath); | |
| var folderSdk = _folderSdks[sdkFolder] ??= FolderBasedDartSdk( | |
| resourceProvider, | |
| sdkFolder, | |
| ); | |
| { | |
| var partialSourceFactory = workspace.partialSourceFactory; | |
| var embedderYamlSource = partialSourceFactory.forUri( | |
| 'package:sky_engine/_embedder.yaml', | |
| ); | |
| if (embedderYamlSource != null) { | |
| var embedderYamlPath = embedderYamlSource.fullName; | |
| var libFolder = resourceProvider.getFile(embedderYamlPath).parent; | |
| var embedderYaml = locateEmbedderYamlFor(libFolder); | |
| if (embedderYaml != null) { | |
| return EmbedderSdk.new2( | |
| resourceProvider, | |
| libFolder, | |
| embedderYaml, | |
| languageVersion: folderSdk.languageVersion, | |
| ); | |
| } | |
| } | |
| } | |
| return folderSdk; | |
| } | |
| /// Return the analysis options from [optionsFile]. | |
| /// | |
| // TODO(scheglov): We have already loaded it once in [ContextLocatorImpl]. | |
| AnalysisOptionsImpl _getAnalysisOptions( | |
| File optionsFile, | |
| AnalysisOptionsParseSession analysisOptionsParseSession, | |
| SourceFactory sourceFactory, | |
| Folder contextRoot, | |
| AnalysisOptionsImpl Function(AnalysisOptionsImpl) updateOptions, | |
| ) { | |
| AnalysisOptionsImpl options; | |
| try { | |
| options = analysisOptionsParseSession | |
| .parse( | |
| sourceFactory: sourceFactory, | |
| contextRoot: contextRoot, | |
| file: optionsFile, | |
| ) | |
| .analysisOptions; | |
| } catch (e) { | |
| // Ignore exception. | |
| options = AnalysisOptionsImpl(file: optionsFile); | |
| } | |
| return updateOptions(options); | |
| } | |
| } |