mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

Adds initial support for flutter create of apps and plugins. This is derived from the current FDE example app and sample plugin, adding template values where relevant. Since the APIs/tooling/template aren't stable yet, the app template includes a version marker, which will be updated each time there's a breaking change. The build now checks that the template version matches the version known by that version of the tool, and gives a specific error message when there's a mismatch, which improves over the current breaking change experience of hitting whatever build failure the breaking change causes and having to figure out that the problem is that the runner is out of date. It also adds a warning to the create output about the fact that it won't be stable. Plugins don't currently have a version marker since in practice this is not a significant problem for plugins yet the way it is for runners; we can add it later if that changes. Fixes #30704
294 lines
11 KiB
Dart
294 lines
11 KiB
Dart
// Copyright 2014 The Flutter Authors. 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:file/memory.dart';
|
|
import 'package:platform/platform.dart';
|
|
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/commands/build_windows.dart';
|
|
import 'package:flutter_tools/src/convert.dart';
|
|
import 'package:flutter_tools/src/features.dart';
|
|
import 'package:flutter_tools/src/windows/visual_studio.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:process/process.dart';
|
|
import 'package:xml/xml.dart' as xml;
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/mocks.dart';
|
|
import '../../src/testbed.dart';
|
|
|
|
const String flutterRoot = r'C:\flutter';
|
|
const String solutionPath = r'C:\windows\Runner.sln';
|
|
const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
|
|
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat';
|
|
|
|
final Platform windowsPlatform = FakePlatform(
|
|
operatingSystem: 'windows',
|
|
environment: <String, String>{
|
|
'PROGRAMFILES(X86)': r'C:\Program Files (x86)\',
|
|
'FLUTTER_ROOT': flutterRoot,
|
|
}
|
|
);
|
|
final Platform notWindowsPlatform = FakePlatform(
|
|
operatingSystem: 'linux',
|
|
environment: <String, String>{
|
|
'FLUTTER_ROOT': flutterRoot,
|
|
}
|
|
);
|
|
|
|
void main() {
|
|
FileSystem fileSystem;
|
|
|
|
MockProcessManager mockProcessManager;
|
|
MockProcess mockProcess;
|
|
MockVisualStudio mockVisualStudio;
|
|
|
|
setUpAll(() {
|
|
Cache.disableLocking();
|
|
});
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
|
|
Cache.flutterRoot = flutterRoot;
|
|
mockProcessManager = MockProcessManager();
|
|
mockProcess = MockProcess();
|
|
mockVisualStudio = MockVisualStudio();
|
|
when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
|
|
return 0;
|
|
});
|
|
when(mockProcess.stderr).thenAnswer((Invocation invocation) {
|
|
return const Stream<List<int>>.empty();
|
|
});
|
|
when(mockProcess.stdout).thenAnswer((Invocation invocation) {
|
|
return Stream<List<int>>.fromIterable(<List<int>>[utf8.encode('STDOUT STUFF')]);
|
|
});
|
|
});
|
|
|
|
// Creates the mock files necessary to look like a Flutter project.
|
|
void setUpMockCoreProjectFiles() {
|
|
fileSystem.file('pubspec.yaml').createSync();
|
|
fileSystem.file('.packages').createSync();
|
|
fileSystem.file(fileSystem.path.join('lib', 'main.dart')).createSync(recursive: true);
|
|
}
|
|
|
|
// Creates the mock files necessary to run a build.
|
|
void setUpMockProjectFilesForBuild({int templateVersion}) {
|
|
fileSystem.file(solutionPath).createSync(recursive: true);
|
|
setUpMockCoreProjectFiles();
|
|
|
|
final String versionFileSubpath = fileSystem.path.join('flutter', '.template_version');
|
|
const int expectedTemplateVersion = 10; // Arbitrary value for tests.
|
|
final File sourceTemplateVersionfile = fileSystem.file(fileSystem.path.join(
|
|
fileSystem.path.absolute(Cache.flutterRoot),
|
|
'packages',
|
|
'flutter_tools',
|
|
'templates',
|
|
'app',
|
|
'windows.tmpl',
|
|
versionFileSubpath,
|
|
));
|
|
sourceTemplateVersionfile.createSync(recursive: true);
|
|
sourceTemplateVersionfile.writeAsStringSync(expectedTemplateVersion.toString());
|
|
|
|
final File projectTemplateVersionFile = fileSystem.file(
|
|
fileSystem.path.join('windows', versionFileSubpath));
|
|
templateVersion ??= expectedTemplateVersion;
|
|
projectTemplateVersionFile.createSync(recursive: true);
|
|
projectTemplateVersionFile.writeAsStringSync(templateVersion.toString());
|
|
}
|
|
|
|
testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild();
|
|
|
|
expect(createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
), throwsToolExit());
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windowsPlatform,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Windows build fails when there is no windows project', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockCoreProjectFiles();
|
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
|
|
|
expect(createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
), throwsToolExit(message: 'No Windows desktop project configured'));
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => windowsPlatform,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Windows build fails on non windows platform', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild();
|
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
|
|
|
expect(createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
), throwsToolExit());
|
|
}, overrides: <Type, Generator>{
|
|
Platform: () => notWindowsPlatform,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Windows build fails with instructions when template is too old', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild(templateVersion: 1);
|
|
|
|
expect(createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
), throwsToolExit(message: 'flutter create .'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Platform: () => windowsPlatform,
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Windows build fails with instructions when template is too new', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild(templateVersion: 999);
|
|
|
|
expect(createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
), throwsToolExit(message: 'Upgrade Flutter'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Platform: () => windowsPlatform,
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Windows build does not spew stdout to status logger', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild();
|
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
|
|
|
when(mockProcessManager.start(<String>[
|
|
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'),
|
|
vcvarsPath,
|
|
fileSystem.path.basename(solutionPath),
|
|
'Release',
|
|
], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
|
|
return mockProcess;
|
|
});
|
|
|
|
await createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
);
|
|
expect(testLogger.statusText, isNot(contains('STDOUT STUFF')));
|
|
expect(testLogger.traceText, contains('STDOUT STUFF'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => mockProcessManager,
|
|
Platform: () => windowsPlatform,
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Windows build invokes msbuild and writes generated files', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild();
|
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
|
|
|
when(mockProcessManager.start(<String>[
|
|
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'),
|
|
vcvarsPath,
|
|
fileSystem.path.basename(solutionPath),
|
|
'Release',
|
|
], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
|
|
return mockProcess;
|
|
});
|
|
|
|
await createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
);
|
|
|
|
// Spot-check important elements from the properties file.
|
|
final File propsFile = fileSystem.file(r'C:\windows\flutter\ephemeral\Generated.props');
|
|
expect(propsFile.existsSync(), true);
|
|
final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync());
|
|
expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros');
|
|
expect(props.findAllElements('ItemGroup').length, 1);
|
|
expect(props.findAllElements('FLUTTER_ROOT').first.text, flutterRoot);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => mockProcessManager,
|
|
Platform: () => windowsPlatform,
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('Release build prints an under-construction warning', () async {
|
|
final BuildWindowsCommand command = BuildWindowsCommand()
|
|
..visualStudioOverride = mockVisualStudio;
|
|
applyMocksToCommand(command);
|
|
setUpMockProjectFilesForBuild();
|
|
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath);
|
|
|
|
when(mockProcessManager.start(<String>[
|
|
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'),
|
|
vcvarsPath,
|
|
fileSystem.path.basename(solutionPath),
|
|
'Release',
|
|
], workingDirectory: fileSystem.path.dirname(solutionPath))).thenAnswer((Invocation invocation) async {
|
|
return mockProcess;
|
|
});
|
|
|
|
await createTestCommandRunner(command).run(
|
|
const <String>['windows']
|
|
);
|
|
|
|
expect(testLogger.statusText, contains('🚧'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => mockProcessManager,
|
|
Platform: () => windowsPlatform,
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
});
|
|
|
|
testUsingContext('hidden when not enabled on Windows host', () {
|
|
expect(BuildWindowsCommand().hidden, true);
|
|
}, overrides: <Type, Generator>{
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
|
|
Platform: () => windowsPlatform,
|
|
});
|
|
|
|
testUsingContext('Not hidden when enabled and on Windows host', () {
|
|
expect(BuildWindowsCommand().hidden, false);
|
|
}, overrides: <Type, Generator>{
|
|
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
|
|
Platform: () => windowsPlatform,
|
|
});
|
|
}
|
|
|
|
class MockProcessManager extends Mock implements ProcessManager {}
|
|
class MockProcess extends Mock implements Process {}
|
|
class MockVisualStudio extends Mock implements VisualStudio {}
|