[ Tool ] Remove use of globals from widget-preview commands (#162522)

This commit is contained in:
Ben Konyi 2025-01-31 14:17:45 -05:00 committed by GitHub
parent 53f2b136db
commit de872dd864
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 148 additions and 69 deletions

View File

@ -246,7 +246,12 @@ List<FlutterCommand> generateCommands({required bool verboseHelp, required bool
verbose: verbose, verbose: verbose,
nativeAssetsBuilder: globals.nativeAssetsBuilder, nativeAssetsBuilder: globals.nativeAssetsBuilder,
), ),
WidgetPreviewCommand(), WidgetPreviewCommand(
logger: globals.logger,
fs: globals.fs,
projectFactory: globals.projectFactory,
cache: globals.cache,
),
UpgradeCommand(verboseHelp: verboseHelp), UpgradeCommand(verboseHelp: verboseHelp),
SymbolizeCommand(stdio: globals.stdio, fileSystem: globals.fs), SymbolizeCommand(stdio: globals.stdio, fileSystem: globals.fs),
// Development-only commands. These are always hidden, // Development-only commands. These are always hidden,

View File

@ -8,24 +8,37 @@ import 'package:meta/meta.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/deferred_component.dart'; import '../base/deferred_component.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/platform.dart'; import '../base/platform.dart';
import '../cache.dart'; import '../cache.dart';
import '../convert.dart'; import '../convert.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
import '../flutter_manifest.dart'; import '../flutter_manifest.dart';
import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../widget_preview/preview_code_generator.dart'; import '../widget_preview/preview_code_generator.dart';
import '../widget_preview/preview_detector.dart'; import '../widget_preview/preview_detector.dart';
import 'create_base.dart'; import 'create_base.dart';
// TODO(bkonyi): use dependency injection instead of global accessors throughout this file.
class WidgetPreviewCommand extends FlutterCommand { class WidgetPreviewCommand extends FlutterCommand {
WidgetPreviewCommand() { WidgetPreviewCommand({
addSubcommand(WidgetPreviewStartCommand()); required Logger logger,
addSubcommand(WidgetPreviewCleanCommand()); required FileSystem fs,
required FlutterProjectFactory projectFactory,
required Cache cache,
}) {
addSubcommand(
WidgetPreviewStartCommand(
logger: logger,
fs: fs,
projectFactory: projectFactory,
cache: cache,
),
);
addSubcommand(
WidgetPreviewCleanCommand(logger: logger, fs: fs, projectFactory: projectFactory),
);
} }
@override @override
@ -46,27 +59,30 @@ class WidgetPreviewCommand extends FlutterCommand {
Future<FlutterCommandResult> runCommand() async => FlutterCommandResult.fail(); Future<FlutterCommandResult> runCommand() async => FlutterCommandResult.fail();
} }
/// Common utilities for the 'start' and 'clean' commands. abstract base class WidgetPreviewSubCommandBase extends FlutterCommand {
mixin WidgetPreviewSubCommandMixin on FlutterCommand { FileSystem get fs;
Logger get logger;
FlutterProjectFactory get projectFactory;
FlutterProject getRootProject() { FlutterProject getRootProject() {
final ArgResults results = argResults!; final ArgResults results = argResults!;
final Directory projectDir; final Directory projectDir;
if (results.rest case <String>[final String directory]) { if (results.rest case <String>[final String directory]) {
projectDir = globals.fs.directory(directory); projectDir = fs.directory(directory);
if (!projectDir.existsSync()) { if (!projectDir.existsSync()) {
throwToolExit('Could not find ${projectDir.path}.'); throwToolExit('Could not find ${projectDir.path}.');
} }
} else if (results.rest.length > 1) { } else if (results.rest.length > 1) {
throwToolExit('Only one directory should be provided.'); throwToolExit('Only one directory should be provided.');
} else { } else {
projectDir = globals.fs.currentDirectory; projectDir = fs.currentDirectory;
} }
return validateFlutterProjectForPreview(projectDir); return validateFlutterProjectForPreview(projectDir);
} }
FlutterProject validateFlutterProjectForPreview(Directory directory) { FlutterProject validateFlutterProjectForPreview(Directory directory) {
globals.logger.printTrace('Verifying that ${directory.path} is a Flutter project.'); logger.printTrace('Verifying that ${directory.path} is a Flutter project.');
final FlutterProject flutterProject = globals.projectFactory.fromDirectory(directory); final FlutterProject flutterProject = projectFactory.fromDirectory(directory);
if (!flutterProject.dartTool.existsSync()) { if (!flutterProject.dartTool.existsSync()) {
throwToolExit('${flutterProject.directory.path} is not a valid Flutter project.'); throwToolExit('${flutterProject.directory.path} is not a valid Flutter project.');
} }
@ -74,9 +90,13 @@ mixin WidgetPreviewSubCommandMixin on FlutterCommand {
} }
} }
class WidgetPreviewStartCommand extends FlutterCommand final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with CreateBase {
with CreateBase, WidgetPreviewSubCommandMixin { WidgetPreviewStartCommand({
WidgetPreviewStartCommand() { required this.logger,
required this.fs,
required this.projectFactory,
required this.cache,
}) {
addPubOptions(); addPubOptions();
} }
@ -86,8 +106,20 @@ class WidgetPreviewStartCommand extends FlutterCommand
@override @override
String get name => 'start'; String get name => 'start';
@override
final FileSystem fs;
@override
final Logger logger;
@override
final FlutterProjectFactory projectFactory;
final Cache cache;
late final PreviewDetector _previewDetector = PreviewDetector( late final PreviewDetector _previewDetector = PreviewDetector(
logger: globals.logger, logger: logger,
fs: fs,
onChangeDetected: onChangeDetected, onChangeDetected: onChangeDetected,
); );
@ -101,9 +133,7 @@ class WidgetPreviewStartCommand extends FlutterCommand
// Check to see if a preview scaffold has already been generated. If not, // Check to see if a preview scaffold has already been generated. If not,
// generate one. // generate one.
if (!widgetPreviewScaffold.existsSync()) { if (!widgetPreviewScaffold.existsSync()) {
globals.logger.printStatus( logger.printStatus('Creating widget preview scaffolding at: ${widgetPreviewScaffold.path}');
'Creating widget preview scaffolding at: ${widgetPreviewScaffold.path}',
);
await generateApp( await generateApp(
<String>['widget_preview_scaffold'], <String>['widget_preview_scaffold'],
widgetPreviewScaffold, widgetPreviewScaffold,
@ -112,7 +142,7 @@ class WidgetPreviewStartCommand extends FlutterCommand
projectName: 'widget_preview_scaffold', projectName: 'widget_preview_scaffold',
titleCaseProjectName: 'Widget Preview Scaffold', titleCaseProjectName: 'Widget Preview Scaffold',
flutterRoot: Cache.flutterRoot!, flutterRoot: Cache.flutterRoot!,
dartSdkVersionBounds: '^${globals.cache.dartSdkBuild}', dartSdkVersionBounds: '^${cache.dartSdkBuild}',
linux: const LocalPlatform().isLinux, linux: const LocalPlatform().isLinux,
macos: const LocalPlatform().isMacOS, macos: const LocalPlatform().isMacOS,
windows: const LocalPlatform().isWindows, windows: const LocalPlatform().isWindows,
@ -128,7 +158,7 @@ class WidgetPreviewStartCommand extends FlutterCommand
// FlutterManifest before the scaffold project's pubspec has been generated. // FlutterManifest before the scaffold project's pubspec has been generated.
_previewCodeGenerator = PreviewCodeGenerator( _previewCodeGenerator = PreviewCodeGenerator(
widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject, widgetPreviewScaffoldProject: rootProject.widgetPreviewScaffoldProject,
fs: globals.fs, fs: fs,
); );
final PreviewMapping initialPreviews = await _previewDetector.initialize(rootProject.directory); final PreviewMapping initialPreviews = await _previewDetector.initialize(rootProject.directory);
@ -207,7 +237,7 @@ class WidgetPreviewStartCommand extends FlutterCommand
rootManifest.deferredComponents?.map(transformDeferredComponent).toList(); rootManifest.deferredComponents?.map(transformDeferredComponent).toList();
return widgetPreviewManifest.copyWith( return widgetPreviewManifest.copyWith(
logger: globals.logger, logger: logger,
assets: assets, assets: assets,
fonts: fonts, fonts: fonts,
shaders: shaders, shaders: shaders,
@ -292,26 +322,37 @@ class WidgetPreviewStartCommand extends FlutterCommand
flutterGenPackageConfigEntry, flutterGenPackageConfigEntry,
); );
packageConfig.writeAsStringSync(json.encode(packageConfigJson)); packageConfig.writeAsStringSync(json.encode(packageConfigJson));
globals.logger.printStatus('Added flutter_gen dependency to $previewPackageConfigPath'); logger.printStatus('Added flutter_gen dependency to $previewPackageConfigPath');
} }
} }
class WidgetPreviewCleanCommand extends FlutterCommand with WidgetPreviewSubCommandMixin { final class WidgetPreviewCleanCommand extends WidgetPreviewSubCommandBase {
WidgetPreviewCleanCommand({required this.fs, required this.logger, required this.projectFactory});
@override @override
String get description => 'Cleans up widget preview state.'; String get description => 'Cleans up widget preview state.';
@override @override
String get name => 'clean'; String get name => 'clean';
@override
final FileSystem fs;
@override
final Logger logger;
@override
final FlutterProjectFactory projectFactory;
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
final Directory widgetPreviewScaffold = getRootProject().widgetPreviewScaffold; final Directory widgetPreviewScaffold = getRootProject().widgetPreviewScaffold;
if (widgetPreviewScaffold.existsSync()) { if (widgetPreviewScaffold.existsSync()) {
final String scaffoldPath = widgetPreviewScaffold.path; final String scaffoldPath = widgetPreviewScaffold.path;
globals.logger.printStatus('Deleting widget preview scaffold at $scaffoldPath.'); logger.printStatus('Deleting widget preview scaffold at $scaffoldPath.');
widgetPreviewScaffold.deleteSync(recursive: true); widgetPreviewScaffold.deleteSync(recursive: true);
} else { } else {
globals.logger.printStatus('Nothing to clean up.'); logger.printStatus('Nothing to clean up.');
} }
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }

View File

@ -16,14 +16,14 @@ import 'package:watcher/watcher.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../globals.dart' as globals;
import 'preview_code_generator.dart'; import 'preview_code_generator.dart';
typedef PreviewMapping = Map<String, List<String>>; typedef PreviewMapping = Map<String, List<String>>;
class PreviewDetector { class PreviewDetector {
PreviewDetector({required this.logger, required this.onChangeDetected}); PreviewDetector({required this.fs, required this.logger, required this.onChangeDetected});
final FileSystem fs;
final Logger logger; final Logger logger;
final void Function(PreviewMapping) onChangeDetected; final void Function(PreviewMapping) onChangeDetected;
StreamSubscription<WatchEvent>? _fileWatcher; StreamSubscription<WatchEvent>? _fileWatcher;
@ -47,7 +47,7 @@ class PreviewDetector {
} }
logger.printStatus('Detected change in $eventPath.'); logger.printStatus('Detected change in $eventPath.');
final PreviewMapping filePreviewsMapping = findPreviewFunctions( final PreviewMapping filePreviewsMapping = findPreviewFunctions(
globals.fs.file(Uri.file(event.path)), fs.file(Uri.file(event.path)),
); );
if (filePreviewsMapping.isEmpty && !_pathToPreviews.containsKey(eventPath)) { if (filePreviewsMapping.isEmpty && !_pathToPreviews.containsKey(eventPath)) {
// No previews found or removed, nothing to do. // No previews found or removed, nothing to do.

View File

@ -5,6 +5,7 @@
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/widget_preview.dart'; import 'package:flutter_tools/src/commands/widget_preview.dart';
import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_manifest.dart';
@ -14,6 +15,7 @@ import 'package:test_api/fake.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/fakes.dart';
void main() { void main() {
group('WidgetPreviewStartCommand', () { group('WidgetPreviewStartCommand', () {
@ -21,15 +23,22 @@ void main() {
late ProcessManager processManager; late ProcessManager processManager;
late WidgetPreviewStartCommand command; late WidgetPreviewStartCommand command;
late FlutterProject rootProject; late FlutterProject rootProject;
late Logger logger;
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.any(); processManager = FakeProcessManager.any();
command = WidgetPreviewStartCommand(); logger = BufferLogger.test();
command = WidgetPreviewStartCommand(
fs: fileSystem,
projectFactory: FakeFlutterProjectFactory(),
logger: logger,
cache: Cache.test(processManager: processManager),
);
rootProject = FakeFlutterProject( rootProject = FakeFlutterProject(
projectRoot: 'some_project', projectRoot: 'some_project',
fileSystem: fileSystem, fileSystem: fileSystem,
logger: BufferLogger.test(), logger: logger,
); );
}); });

View File

@ -6,11 +6,16 @@ import 'dart:io' as io show IOOverrides;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file_testing/file_testing.dart'; import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/bot_detector.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/widget_preview.dart'; import 'package:flutter_tools/src/commands/widget_preview.dart';
import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/widget_preview/preview_code_generator.dart'; import 'package:flutter_tools/src/widget_preview/preview_code_generator.dart';
import '../../src/common.dart'; import '../../src/common.dart';
@ -23,11 +28,19 @@ void main() {
late Directory tempDir; late Directory tempDir;
late LoggingProcessManager loggingProcessManager; late LoggingProcessManager loggingProcessManager;
late FakeStdio mockStdio; late FakeStdio mockStdio;
late Logger logger;
late FileSystem fs;
late BotDetector botDetector;
late Platform platform;
setUp(() { setUp(() {
loggingProcessManager = LoggingProcessManager(); loggingProcessManager = LoggingProcessManager();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_create_test.'); logger = BufferLogger.test();
fs = LocalFileSystem.test(signals: Signals.test());
botDetector = const FakeBotDetector(false);
tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_create_test.');
mockStdio = FakeStdio(); mockStdio = FakeStdio();
platform = const LocalPlatform();
}); });
tearDown(() { tearDown(() {
@ -35,7 +48,7 @@ void main() {
}); });
Future<Directory> createRootProject() async { Future<Directory> createRootProject() async {
return globals.fs.directory(await createProject(tempDir, arguments: <String>['--pub'])); return fs.directory(await createProject(tempDir, arguments: <String>['--pub']));
} }
Directory widgetPreviewScaffoldFromRootProject({required Directory rootProject}) { Directory widgetPreviewScaffoldFromRootProject({required Directory rootProject}) {
@ -43,7 +56,14 @@ void main() {
} }
Future<void> runWidgetPreviewCommand(List<String> arguments) async { Future<void> runWidgetPreviewCommand(List<String> arguments) async {
final CommandRunner<void> runner = createTestCommandRunner(WidgetPreviewCommand()); final CommandRunner<void> runner = createTestCommandRunner(
WidgetPreviewCommand(
logger: logger,
fs: fs,
projectFactory: FlutterProjectFactory(logger: logger, fileSystem: fs),
cache: Cache.test(processManager: loggingProcessManager, platform: platform),
),
);
await runner.run(<String>['widget-preview', ...arguments]); await runner.run(<String>['widget-preview', ...arguments]);
} }
@ -57,7 +77,7 @@ void main() {
if (rootProject != null) rootProject.path, if (rootProject != null) rootProject.path,
]); ]);
final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject( final Directory widgetPreviewScaffoldDir = widgetPreviewScaffoldFromRootProject(
rootProject: rootProject ?? globals.fs.currentDirectory, rootProject: rootProject ?? fs.currentDirectory,
); );
expect(widgetPreviewScaffoldDir, exists); expect(widgetPreviewScaffoldDir, exists);
expect( expect(
@ -69,7 +89,7 @@ void main() {
Future<void> cleanWidgetPreview({required Directory rootProject}) async { Future<void> cleanWidgetPreview({required Directory rootProject}) async {
await runWidgetPreviewCommand(<String>['clean', rootProject.path]); await runWidgetPreviewCommand(<String>['clean', rootProject.path]);
expect( expect(
globals.fs fs
.directory(rootProject) .directory(rootProject)
.childDirectory('.dart_tool') .childDirectory('.dart_tool')
.childDirectory('widget_preview_scaffold'), .childDirectory('widget_preview_scaffold'),
@ -116,11 +136,11 @@ void main() {
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Pub: Pub:
() => Pub.test( () => Pub.test(
fileSystem: globals.fs, fileSystem: fs,
logger: globals.logger, logger: logger,
processManager: globals.processManager, processManager: loggingProcessManager,
botDetector: globals.botDetector, botDetector: botDetector,
platform: globals.platform, platform: platform,
stdio: mockStdio, stdio: mockStdio,
), ),
}, },
@ -138,11 +158,11 @@ void main() {
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Pub: Pub:
() => Pub.test( () => Pub.test(
fileSystem: globals.fs, fileSystem: fs,
logger: globals.logger, logger: logger,
processManager: globals.processManager, processManager: loggingProcessManager,
botDetector: globals.botDetector, botDetector: botDetector,
platform: globals.platform, platform: platform,
stdio: mockStdio, stdio: mockStdio,
), ),
}, },
@ -179,11 +199,11 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Pub: Pub:
() => Pub.test( () => Pub.test(
fileSystem: globals.fs, fileSystem: fs,
logger: globals.logger, logger: logger,
processManager: globals.processManager, processManager: loggingProcessManager,
botDetector: globals.botDetector, botDetector: botDetector,
platform: globals.platform, platform: platform,
stdio: mockStdio, stdio: mockStdio,
), ),
}, },
@ -209,16 +229,16 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/
// Try to execute using the CWD. // Try to execute using the CWD.
await startWidgetPreview(rootProject: null); await startWidgetPreview(rootProject: null);
expect(generatedFile.readAsStringSync(), expectedGeneratedFileContents); expect(generatedFile.readAsStringSync(), expectedGeneratedFileContents);
}, getCurrentDirectory: () => globals.fs.directory(rootProject)); }, getCurrentDirectory: () => fs.directory(rootProject));
}, },
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Pub: Pub:
() => Pub.test( () => Pub.test(
fileSystem: globals.fs, fileSystem: fs,
logger: globals.logger, logger: logger,
processManager: globals.processManager, processManager: loggingProcessManager,
botDetector: globals.botDetector, botDetector: botDetector,
platform: globals.platform, platform: platform,
stdio: mockStdio, stdio: mockStdio,
), ),
}, },
@ -234,11 +254,11 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/
overrides: <Type, Generator>{ overrides: <Type, Generator>{
Pub: Pub:
() => Pub.test( () => Pub.test(
fileSystem: globals.fs, fileSystem: fs,
logger: globals.logger, logger: logger,
processManager: globals.processManager, processManager: loggingProcessManager,
botDetector: globals.botDetector, botDetector: botDetector,
platform: globals.platform, platform: platform,
stdio: mockStdio, stdio: mockStdio,
), ),
}, },
@ -287,11 +307,11 @@ import 'package:flutter_project/foo.dart' as _i1;import 'package:widget_preview/
ProcessManager: () => loggingProcessManager, ProcessManager: () => loggingProcessManager,
Pub: Pub:
() => Pub.test( () => Pub.test(
fileSystem: globals.fs, fileSystem: fs,
logger: globals.logger, logger: logger,
processManager: globals.processManager, processManager: loggingProcessManager,
botDetector: globals.botDetector, botDetector: botDetector,
platform: globals.platform, platform: platform,
stdio: mockStdio, stdio: mockStdio,
), ),
}, },

View File

@ -48,7 +48,11 @@ void main() {
fs = LocalFileSystem.test(signals: Signals.test()); fs = LocalFileSystem.test(signals: Signals.test());
projectRoot = createBasicProjectStructure(fs); projectRoot = createBasicProjectStructure(fs);
logger = BufferLogger.test(); logger = BufferLogger.test();
previewDetector = PreviewDetector(logger: logger, onChangeDetected: onChangeDetectedRoot); previewDetector = PreviewDetector(
logger: logger,
fs: fs,
onChangeDetected: onChangeDetectedRoot,
);
}); });
tearDown(() async { tearDown(() async {