Files
sdk/pkg/analysis_server/tool/log_player/normalize.dart
T
Danny Tuppeny 11b1da577f [analysis_server] Handle URIs/filePaths separately in log normalization/replay
For posix paths, replacing the file path during normalization and then swapping it back later works for both paths and URIs, because a file URI just contains the file path verbatim:

file:///foo/bar/baz

However that's not the case for Windows:

C:\foo\bar\baz
file:///c:/foo/bar/baz

So when normalizing, we need to know if we normalized a URI or a file path, so that we can reverse it later.

With this change, we'll use `{{workspaceFolder-0}}` for the URI, and `{{workspaceFolder-0:filePath}}` for the file path. Then when reversing, we can easily put the correct one back.

This also updates the log replace/scenarios to use the LogNormalizer to perform the denormalization so they don't have to have duplicated logic about what to restore.

I've also updated the existing committed scenarios (EDIT: moved this to a separate CL because Gerrit is falling over) - although even with those changes, they all fail for different reasons (invalid git hashes, mismatches in expected vs actual requests) so I think there is still more work to do here.

Fixes https://github.com/dart-lang/sdk/issues/63330

Change-Id: Ib4c4aabe2c7c0d089bd620bdf00de37acde25f52
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501600
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2026-05-07 10:03:24 -07:00

150 lines
4.7 KiB
Dart

// Copyright (c) 2025, 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:io' show Directory, exit;
import 'package:analysis_server/src/session_logger/log_normalizer.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:args/args.dart';
import 'package:cli_util/cli_util.dart';
import 'package:package_config/package_config.dart';
import '../performance/project_generator/project_generator.dart'
show ContextRoot, getContextRoots;
import 'log.dart';
Future<void> main(List<String> args) async {
var parsed = argParser.parse(args);
if (parsed.flag('help')) {
print(argParser.usage);
return;
}
var resourceProvider = PhysicalResourceProvider.INSTANCE;
var inputFile = resourceProvider.getFile(
Uri.base.resolve(parsed.option('input')!).toFilePath(),
);
if (!inputFile.exists) {
print('Input file ${inputFile.path} does not exist');
exit(1);
}
var outputFile = resourceProvider.getFile(
Uri.base.resolve(parsed.option('output')!).toFilePath(),
);
var rootDirPath = parsed.option('root-dir');
if (rootDirPath == null) {
print('Root directory not specified');
exit(1);
}
var rootDir = resourceProvider.getFolder(rootDirPath);
if (!rootDir.exists) {
print('Root directory $rootDirPath does not exist');
exit(1);
}
List<ContextRoot> contextRoots;
var packageConfigPath = parsed.option('package-config');
if (packageConfigPath != null) {
var packageConfigFile = resourceProvider.getFile(packageConfigPath);
if (!packageConfigFile.exists) {
print('Package config file $packageConfigPath does not exist');
exit(1);
}
var packageConfig = PackageConfig.parseBytes(
packageConfigFile.readAsBytesSync(),
packageConfigFile.toUri(),
);
contextRoots = [ContextRoot(Directory(rootDirPath), packageConfig)];
} else {
contextRoots = await getContextRoots(rootDirPath);
}
print('normalizing log at ${inputFile.path}');
var normalized = normalizeLog(inputFile, contextRoots);
outputFile.writeAsStringSync(normalized);
print('wrote normalized log to ${outputFile.path}');
var absFileMatches = normalized.allMatches('"file:///');
if (absFileMatches.isNotEmpty) {
print('found ${absFileMatches.length} absolute file paths remaining:');
}
for (var match in absFileMatches.take(5)) {
print('- ${match[0]}');
}
}
final argParser = ArgParser()
..addOption(
'input',
abbr: 'i',
help: 'The path to the input log to be normalized',
mandatory: true,
)
..addOption(
'output',
abbr: 'o',
help: 'The path output the normalized log to',
mandatory: true,
)
..addOption(
'root-dir',
abbr: 'r',
help: 'The path to the root directory for normalizing package paths',
mandatory: true,
)
..addOption(
'package-config',
abbr: 'p',
help:
'The path to the package config file, if specified, will be used '
'instead of inferring it from the workspace directories.',
)
..addFlag('help', abbr: 'h', help: 'Prints the usage text');
/// Reads an [input] log file, and attempts to normalize it so that it can work
/// across multiple environments.
///
/// Specifically, this:
/// - Replaces all workspace folder paths with `{{workspaceFolder-[i]}}`
/// placeholders.
/// - Replaces the Dart SDK root with `{{dartSdkRoot}}`.
/// - Replaces all package roots with `{{package-root:[package-name]}}`.
///
/// Returns the new file contents after normalization.
//
// TODO(somebody): Support legacy protocol.
//
// TODO(somebody): Don't take a package config, instead infer them from the
// workspace directories.
String normalizeLog(File input, List<ContextRoot> contextRoots) {
var normalizer = LogNormalizer();
var content = input.readAsStringSync();
var original = Log.fromString(content);
var initializeMessage = original.entries.firstWhere(
(log) => log.isMessage && log.message.isInitializeRequest,
);
// Add all the workspace + root paths from the initialize message.
normalizer.addLspWorkspaceReplacements(initializeMessage.message);
// Additionally add SDKs and packages.
normalizer.addReplacementsForPath(sdkPath, 'dartSdkRoot');
// TODO(somebody): replace {{flutterSdkRoot}} with the flutter SDK path
for (var i = 0; i < contextRoots.length; i++) {
for (var package in contextRoots[i].packageConfig.packages) {
normalizer.addReplacementsForUri(
package.root,
'context-$i:package-root:${package.name}',
);
}
}
// Now normalize the full content.
return normalizer.normalize(content);
}