Skip to content

Move SDK summary generation to a separate library #3431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build_resolvers/lib/src/build_asset_uri_resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'dart:async';
import 'dart:collection';
import 'dart:isolate';

import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
Expand Down Expand Up @@ -218,6 +219,11 @@ class BuildAssetUriResolver extends UriResolver {
String assetPath(AssetId assetId) =>
p.posix.join('/${assetId.package}', assetId.path);

Future<String> packagePath(String package) async {
var libRoot = await Isolate.resolvePackageUri(Uri.parse('package:$package/'));
return p.dirname(p.fromUri(libRoot));
}

/// Returns all the directives from a Dart library that can be resolved to an
/// [AssetId].
Set<AssetId> _parseDirectives(String content, AssetId from) => HashSet.of(
Expand Down
113 changes: 4 additions & 109 deletions build_resolvers/lib/src/resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,21 @@ import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/sdk/build_sdk_summary.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
// ignore: implementation_imports
import 'package:analyzer/src/clients/build_resolvers/build_resolvers.dart';
import 'package:async/async.dart';
import 'package:build/build.dart';
import 'package:build/experiments.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:logging/logging.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import 'package:pool/pool.dart';
import 'package:yaml/yaml.dart';

import 'analysis_driver.dart';
import 'build_asset_uri_resolver.dart';
import 'human_readable_duration.dart';

final _logger = Logger('build_resolvers');

Future<String> _packagePath(String package) async {
var libRoot = await Isolate.resolvePackageUri(Uri.parse('package:$package/'));
return p.dirname(p.fromUri(libRoot));
}
import 'sdk_summary.dart';

/// Implements [Resolver.libraries] and [Resolver.findLibraryByName] by crawling
/// down from entrypoints.
Expand Down Expand Up @@ -365,7 +355,7 @@ class AnalyzerResolvers implements Resolvers {

/// A function that returns the path to the SDK summary when invoked.
///
/// Defaults to [_defaultSdkSummaryGenerator].
/// Defaults to [defaultSdkSummaryGenerator].
final Future<String> Function() _sdkSummaryGenerator;

// Lazy, all access must be preceded by a call to `_ensureInitialized`.
Expand Down Expand Up @@ -400,7 +390,7 @@ class AnalyzerResolvers implements Resolvers {
..contextFeatures =
_featureSet(enableExperiments: enabledExperiments)),
_sdkSummaryGenerator =
sdkSummaryGenerator ?? _defaultSdkSummaryGenerator;
sdkSummaryGenerator ?? defaultSdkSummaryGenerator;

/// Create a Resolvers backed by an `AnalysisContext` using options
/// [_analysisOptions].
Expand Down Expand Up @@ -430,89 +420,6 @@ class AnalyzerResolvers implements Resolvers {
}
}

/// Lazily creates a summary of the users SDK and caches it under
/// `.dart_tool/build_resolvers`.
///
/// This is only intended for use in typical dart packages, which must
/// have an already existing `.dart_tool` directory (this is how we
/// validate we are running under a typical dart package and not a custom
/// environment).
Future<String> _defaultSdkSummaryGenerator() async {
var dartToolPath = '.dart_tool';
if (!await Directory(dartToolPath).exists()) {
throw StateError(
'The default analyzer resolver can only be used when the current '
'working directory is a standard pub package.');
}

var cacheDir = p.join(dartToolPath, 'build_resolvers');
var summaryPath = p.join(cacheDir, 'sdk.sum');
var depsFile = File('$summaryPath.deps');
var summaryFile = File(summaryPath);

var currentDeps = {
'sdk': Platform.version,
for (var package in _packageDepsToCheck)
package: await _packagePath(package),
};

// Invalidate existing summary/version/analyzer files if present.
if (await depsFile.exists()) {
if (!await _checkDeps(depsFile, currentDeps)) {
await depsFile.delete();
if (await summaryFile.exists()) await summaryFile.delete();
}
} else if (await summaryFile.exists()) {
// Fallback for cases where we could not do a proper version check.
await summaryFile.delete();
}

// Generate the summary and version files if necessary.
if (!await summaryFile.exists()) {
var watch = Stopwatch()..start();
_logger.info('Generating SDK summary...');
await summaryFile.create(recursive: true);
final embedderYamlPath =
isFlutter ? p.join(_dartUiPath, '_embedder.yaml') : null;
await summaryFile.writeAsBytes(
await buildSdkSummary(
sdkPath: _runningDartSdkPath,
resourceProvider: PhysicalResourceProvider.INSTANCE,
embedderYamlPath: embedderYamlPath,
),
);

await _createDepsFile(depsFile, currentDeps);
watch.stop();
_logger.info('Generating SDK summary completed, took '
'${humanReadable(watch.elapsed)}\n');
}

return p.absolute(summaryPath);
}

final _packageDepsToCheck = ['analyzer', 'build_resolvers'];

Future<bool> _checkDeps(
File versionsFile, Map<String, Object?> currentDeps) async {
var previous =
jsonDecode(await versionsFile.readAsString()) as Map<String, Object?>;

if (previous.keys.length != currentDeps.keys.length) return false;

for (var entry in previous.entries) {
if (entry.value != currentDeps[entry.key]) return false;
}

return true;
}

Future<void> _createDepsFile(
File depsFile, Map<String, Object?> currentDeps) async {
await depsFile.create(recursive: true);
await depsFile.writeAsString(jsonEncode(currentDeps));
}

/// Checks that the current analyzer version supports the current language
/// version.
void _warnOnLanguageVersionMismatch() async {
Expand All @@ -528,7 +435,7 @@ void _warnOnLanguageVersionMismatch() async {
var json = jsonDecode(content.toString());
var latestAnalyzer = json['latest']['version'];
var analyzerPubspecPath =
p.join(await _packagePath('analyzer'), 'pubspec.yaml');
p.join(await packagePath('analyzer'), 'pubspec.yaml');
var currentAnalyzer =
loadYaml(await File(analyzerPubspecPath).readAsString())['version'];

Expand Down Expand Up @@ -580,11 +487,6 @@ https://pub.dev/packages/analyzer.
}
}

/// Path where the dart:ui package will be found, if executing via the dart
/// binary provided by the Flutter SDK.
final _dartUiPath =
p.normalize(p.join(_runningDartSdkPath, '..', 'pkg', 'sky_engine', 'lib'));

/// The current feature set based on the current sdk version and enabled
/// experiments.
FeatureSet _featureSet({List<String> enableExperiments = const []}) {
Expand All @@ -609,10 +511,3 @@ current version by running `pub deps`.
return FeatureSet.fromEnableFlags2(
sdkLanguageVersion: sdkLanguageVersion, flags: enableExperiments);
}

/// Path to the running dart's SDK root.
final _runningDartSdkPath = p.dirname(p.dirname(Platform.resolvedExecutable));

/// `true` if the currently running dart was provided by the Flutter SDK.
final isFlutter =
Platform.version.contains('flutter') || Directory(_dartUiPath).existsSync();
111 changes: 111 additions & 0 deletions build_resolvers/lib/src/sdk_summary.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) 2022, 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 'dart:io';

import 'package:analyzer/dart/sdk/build_sdk_summary.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;

import 'build_asset_uri_resolver.dart' show packagePath;
import 'human_readable_duration.dart';

final _logger = Logger('build_resolvers');

/// `true` if the currently running dart was provided by the Flutter SDK.
final isFlutter =
Platform.version.contains('flutter') || Directory(_dartUiPath).existsSync();

/// Path to the running dart's SDK root.
final _runningDartSdkPath = p.dirname(p.dirname(Platform.resolvedExecutable));

/// Path where the dart:ui package will be found, if executing via the dart
/// binary provided by the Flutter SDK.
final _dartUiPath =
p.normalize(p.join(_runningDartSdkPath, '..', 'pkg', 'sky_engine', 'lib'));

/// Lazily creates a summary of the users SDK and caches it under
/// `.dart_tool/build_resolvers`.
///
/// This is only intended for use in typical dart packages, which must
/// have an already existing `.dart_tool` directory (this is how we
/// validate we are running under a typical dart package and not a custom
/// environment).
Future<String> defaultSdkSummaryGenerator() async {
var dartToolPath = '.dart_tool';
if (!await Directory(dartToolPath).exists()) {
throw StateError(
'The default analyzer resolver can only be used when the current '
'working directory is a standard pub package.');
}

var cacheDir = p.join(dartToolPath, 'build_resolvers');
var summaryPath = p.join(cacheDir, 'sdk.sum');
var depsFile = File('$summaryPath.deps');
var summaryFile = File(summaryPath);

var currentDeps = {
'sdk': Platform.version,
for (var package in _packageDepsToCheck)
package: await packagePath(package),
};

// Invalidate existing summary/version/analyzer files if present.
if (await depsFile.exists()) {
if (!await _checkDeps(depsFile, currentDeps)) {
await depsFile.delete();
if (await summaryFile.exists()) await summaryFile.delete();
}
} else if (await summaryFile.exists()) {
// Fallback for cases where we could not do a proper version check.
await summaryFile.delete();
}

// Generate the summary and version files if necessary.
if (!await summaryFile.exists()) {
var watch = Stopwatch()..start();
_logger.info('Generating SDK summary...');
await summaryFile.create(recursive: true);
final embedderYamlPath =
isFlutter ? p.join(_dartUiPath, '_embedder.yaml') : null;
await summaryFile.writeAsBytes(
await buildSdkSummary(
sdkPath: _runningDartSdkPath,
resourceProvider: PhysicalResourceProvider.INSTANCE,
embedderYamlPath: embedderYamlPath,
),
);

await _createDepsFile(depsFile, currentDeps);
watch.stop();
_logger.info('Generating SDK summary completed, took '
'${humanReadable(watch.elapsed)}\n');
}

return p.absolute(summaryPath);
}

final _packageDepsToCheck = ['analyzer', 'build_resolvers'];

Future<bool> _checkDeps(
File versionsFile, Map<String, Object?> currentDeps) async {
var previous =
jsonDecode(await versionsFile.readAsString()) as Map<String, Object?>;

if (previous.keys.length != currentDeps.keys.length) return false;

for (var entry in previous.entries) {
if (entry.value != currentDeps[entry.key]) return false;
}

return true;
}

Future<void> _createDepsFile(
File depsFile, Map<String, Object?> currentDeps) async {
await depsFile.create(recursive: true);
await depsFile.writeAsString(jsonEncode(currentDeps));
}
1 change: 1 addition & 0 deletions build_resolvers/test/resolver_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:build/build.dart';
import 'package:build/experiments.dart';
import 'package:build_resolvers/src/analysis_driver.dart';
import 'package:build_resolvers/src/resolver.dart';
import 'package:build_resolvers/src/sdk_summary.dart';
import 'package:build_test/build_test.dart';
import 'package:logging/logging.dart';
import 'package:package_config/package_config.dart';
Expand Down