Unverified Commit 2584afd7 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] scaffolding for Windows UWP template (#78067)

parent c30fdfbe
......@@ -22,15 +22,18 @@ import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
import 'create_base.dart';
const String kPlatformHelp =
'The platforms supported by this project. '
'Platform folders (e.g. android/) will be generated in the target project. '
'This argument only works when "--template" is set to app or plugin. '
'When adding platforms to a plugin project, the pubspec.yaml will be updated with the requested platform. '
'Adding desktop platforms requires the corresponding desktop config setting to be enabled.';
class CreateCommand extends CreateBase {
CreateCommand({
bool verboseHelp = false,
}) : super(verboseHelp: verboseHelp) {
addPlatformsOptions(customHelp: 'The platforms supported by this project. '
'Platform folders (e.g. android/) will be generated in the target project. '
'This argument only works when "--template" is set to app or plugin. '
'When adding platforms to a plugin project, the pubspec.yaml will be updated with the requested platform. '
'Adding desktop platforms requires the corresponding desktop config setting to be enabled.');
addPlatformsOptions(customHelp: kPlatformHelp);
argParser.addOption(
'template',
abbr: 't',
......@@ -39,8 +42,7 @@ class CreateCommand extends CreateBase {
valueHelp: 'type',
allowedHelp: <String, String>{
flutterProjectTypeToString(FlutterProjectType.app): '(default) Generate a Flutter application.',
flutterProjectTypeToString(FlutterProjectType.package): 'Generate a shareable Flutter project containing modular '
'Dart code.',
flutterProjectTypeToString(FlutterProjectType.package): 'Generate a shareable Flutter project containing only Dart code.',
flutterProjectTypeToString(FlutterProjectType.plugin): 'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation for Android, for iOS code, or '
'for both.',
......@@ -240,6 +242,7 @@ class CreateCommand extends CreateBase {
linux: featureFlags.isLinuxEnabled && platforms.contains('linux'),
macos: featureFlags.isMacOSEnabled && platforms.contains('macos'),
windows: featureFlags.isWindowsEnabled && platforms.contains('windows'),
windowsUwp: featureFlags.isWindowsUwpEnabled && platforms.contains('winuwp'),
// Enable null-safety for sample code, which is - unlike our regular templates - already migrated.
dartSdkVersionBounds: sampleCode != null ? '">=2.12.0-0 <3.0.0"' : '">=2.7.0 <3.0.0"'
);
......@@ -382,15 +385,11 @@ Your $application code is in $relativeAppMain.
}
Future<int> _generatePlugin(Directory directory, Map<String, dynamic> templateContext, { bool overwrite = false }) async {
// Plugin doesn't create any platform by default
// Plugins only add a platform if it was requested explicitly by the user.
if (!argResults.wasParsed('platforms')) {
// If the user didn't explicitly declare the platforms, we don't generate any platforms.
templateContext['ios'] = false;
templateContext['android'] = false;
templateContext['web'] = false;
templateContext['linux'] = false;
templateContext['macos'] = false;
templateContext['windows'] = false;
for (final String platform in kAllCreatePlatforms) {
templateContext[platform] = false;
}
}
final List<String> platformsToAdd = _getSupportedPlatformsFromTemplateContext(templateContext);
......@@ -457,18 +456,8 @@ Your $application code is in $relativeAppMain.
List<String> _getSupportedPlatformsFromTemplateContext(Map<String, dynamic> templateContext) {
return <String>[
if (templateContext['ios'] == true)
'ios',
if (templateContext['android'] == true)
'android',
if (templateContext['web'] == true)
'web',
if (templateContext['linux'] == true)
'linux',
if (templateContext['windows'] == true)
'windows',
if (templateContext['macos'] == true)
'macos',
for (String platform in kAllCreatePlatforms)
if (templateContext[platform] == true) platform
];
}
......
......@@ -16,6 +16,7 @@ import '../base/utils.dart';
import '../cache.dart';
import '../convert.dart';
import '../dart/pub.dart';
import '../features.dart';
import '../flutter_project_metadata.dart';
import '../globals.dart' as globals;
import '../project.dart';
......@@ -31,6 +32,18 @@ const List<String> _kAvailablePlatforms = <String>[
'web',
];
/// A list of all possible create platforms, even those that may not be enabled
/// with the current config.
const List<String> kAllCreatePlatforms = <String>[
'ios',
'android',
'windows',
'linux',
'macos',
'web',
'winuwp',
];
const String _kDefaultPlatformArgumentHelp =
'(required) The platforms supported by this project. '
'Platform folders (e.g. android/) will be generated in the target project. '
......@@ -131,16 +144,27 @@ abstract class CreateBase extends FlutterCommand {
void addPlatformsOptions({String customHelp}) {
argParser.addMultiOption('platforms',
help: customHelp ?? _kDefaultPlatformArgumentHelp,
defaultsTo: _kAvailablePlatforms,
allowed: _kAvailablePlatforms);
defaultsTo: <String>[
..._kAvailablePlatforms,
if (featureFlags.isWindowsUwpEnabled)
'winuwp',
],
allowed: <String>[
..._kAvailablePlatforms,
if (featureFlags.isWindowsUwpEnabled)
'winuwp',
],
);
}
/// Throw with exit code 2 if the output directory is invalid.
@protected
void validateOutputDirectoryArg() {
if (argResults.rest.isEmpty) {
throwToolExit('No option specified for the output directory.\n$usage',
exitCode: 2);
throwToolExit(
'No option specified for the output directory.\n$usage',
exitCode: 2,
);
}
if (argResults.rest.length > 1) {
......@@ -156,37 +180,8 @@ abstract class CreateBase extends FlutterCommand {
}
/// Gets the flutter root directory.
///
/// Throw with exit code 2 if the flutter sdk installed is invalid.
@protected
String get flutterRoot {
if (Cache.flutterRoot == null) {
throwToolExit(
'The FLUTTER_ROOT environment variable was not specified. Unable to find package:flutter.',
exitCode: 2);
}
final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot);
final String flutterPackagesDirectory =
globals.fs.path.join(flutterRoot, 'packages');
final String flutterPackagePath =
globals.fs.path.join(flutterPackagesDirectory, 'flutter');
if (!globals.fs
.isFileSync(globals.fs.path.join(flutterPackagePath, 'pubspec.yaml'))) {
throwToolExit('Unable to find package:flutter in $flutterPackagePath',
exitCode: 2);
}
final String flutterDriverPackagePath =
globals.fs.path.join(flutterRoot, 'packages', 'flutter_driver');
if (!globals.fs.isFileSync(
globals.fs.path.join(flutterDriverPackagePath, 'pubspec.yaml'))) {
throwToolExit(
'Unable to find package:flutter_driver in $flutterDriverPackagePath',
exitCode: 2);
}
return flutterRoot;
}
String get flutterRoot => Cache.flutterRoot;
/// Determines the project type in an existing flutter project.
///
......@@ -332,9 +327,8 @@ abstract class CreateBase extends FlutterCommand {
bool linux = false,
bool macos = false,
bool windows = false,
bool windowsUwp = false,
}) {
flutterRoot = globals.fs.path.normalize(flutterRoot);
final String pluginDartClass = _createPluginClassName(projectName);
final String pluginClass = pluginDartClass.endsWith('Plugin')
? pluginDartClass
......@@ -379,6 +373,7 @@ abstract class CreateBase extends FlutterCommand {
'linux': linux,
'macos': macos,
'windows': windows,
'winuwp': windowsUwp,
'year': DateTime.now().year,
'dartSdkVersionBounds': dartSdkVersionBounds,
};
......@@ -410,8 +405,12 @@ abstract class CreateBase extends FlutterCommand {
Directory directory, Map<String, dynamic> templateContext,
{bool overwrite = false, bool pluginExampleApp = false}) async {
int generatedCount = 0;
generatedCount += await renderTemplate('app', directory, templateContext,
overwrite: overwrite);
generatedCount += await renderTemplate(
'app',
directory,
templateContext,
overwrite: overwrite,
);
final FlutterProject project = FlutterProject.fromDirectory(directory);
if (templateContext['android'] == true) {
generatedCount += _injectGradleWrapper(project);
......@@ -432,6 +431,7 @@ abstract class CreateBase extends FlutterCommand {
macOSPlatform: templateContext['macos'] as bool ?? false,
windowsPlatform: templateContext['windows'] as bool ?? false,
webPlatform: templateContext['web'] as bool ?? false,
windowsUwpPlatform: templateContext['winuwp'] as bool ?? false,
);
}
if (templateContext['android'] == true) {
......@@ -616,32 +616,10 @@ const Set<String> _keywords = <String>{
};
const Set<String> _packageDependencies = <String>{
'analyzer',
'args',
'async',
'collection',
'convert',
'crypto',
'flutter',
'flutter_test',
'front_end',
'html',
'http',
'intl',
'io',
'isolate',
'kernel',
'logging',
'matcher',
'meta',
'mime',
'path',
'plugin',
'pool',
'test',
'utf',
'watcher',
'yaml',
};
/// Whether [name] is a valid Pub package.
......
......@@ -135,6 +135,7 @@ const List<Feature> allFeatures = <Feature>[
flutterLinuxDesktopFeature,
flutterMacOSDesktopFeature,
flutterWindowsDesktopFeature,
windowsUwpEmbedding,
singleWidgetReload,
flutterAndroidFeature,
flutterIOSFeature,
......
......@@ -170,6 +170,10 @@ class FlutterProject {
WindowsProject _windows;
WindowsProject get windows => _windows ??= WindowsProject._(this);
/// The Windows UWP sub project of this project.
WindowsUwpProject _windowUwp;
WindowsUwpProject get windowsUwp => _windowUwp ??= WindowsUwpProject._(this);
/// The Fuchsia sub project of this project.
FuchsiaProject _fuchsia;
FuchsiaProject get fuchsia => _fuchsia ??= FuchsiaProject._(this);
......@@ -287,6 +291,7 @@ class FlutterProject {
bool macOSPlatform = false,
bool windowsPlatform = false,
bool webPlatform = false,
bool windowsUwpPlatform = false,
}) async {
if (!directory.existsSync() || hasExampleApp || isPlugin) {
return;
......@@ -310,6 +315,9 @@ class FlutterProject {
if (webPlatform) {
await web.ensureReadyForPlatformSpecificTooling();
}
if (windowsUwpPlatform) {
await windowsUwp.ensureReadyForPlatformSpecificTooling();
}
await injectPlugins(
this,
androidPlatform: androidPlatform,
......@@ -1163,6 +1171,8 @@ class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject
@override
String get pluginConfigKey => WindowsPlugin.kConfigKey;
String get _childDirectory => 'windows';
@override
bool existsSync() => _editableDirectory.existsSync() && cmakeFile.existsSync();
......@@ -1181,7 +1191,7 @@ class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject
@override
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
Directory get _editableDirectory => parent.directory.childDirectory('windows');
Directory get _editableDirectory => parent.directory.childDirectory(_childDirectory);
/// The directory in the project that is managed by Flutter. As much as
/// possible, files that are edited by Flutter tooling after initial project
......@@ -1196,6 +1206,17 @@ class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject
Future<void> ensureReadyForPlatformSpecificTooling() async {}
}
/// The Windows UWP version of the Windows project.
class WindowsUwpProject extends WindowsProject {
WindowsUwpProject._(FlutterProject parent) : super._(parent);
@override
String get _childDirectory => 'windows-uwp';
/// Eventually this will be used to check if the user's unstable project needs to be regenerated.
int get projectVersion => int.tryParse(_editableDirectory.childFile('project_version').readAsStringSync());
}
/// The Linux sub project.
class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
LinuxProject._(this.parent);
......
......@@ -162,6 +162,11 @@ class Template {
if (relativeDestinationPath.startsWith('windows.tmpl') && !windows) {
return null;
}
// Only build a Windows UWP project if explicitly asked.
final bool windowsUwp = context['winuwp'] as bool;
if (relativeDestinationPath.startsWith('winuwp.tmpl') && !windowsUwp) {
return null;
}
final String projectName = context['projectName'] as String;
final String androidIdentifier = context['androidIdentifier'] as String;
......
......@@ -137,6 +137,7 @@
"templates/app/windows.tmpl/runner/utils.h",
"templates/app/windows.tmpl/runner/win32_window.cpp",
"templates/app/windows.tmpl/runner/win32_window.h",
"templates/app/winuwp.tmpl/project_version",
"templates/cocoapods/Podfile-ios-objc",
"templates/cocoapods/Podfile-ios-swift",
"templates/cocoapods/Podfile-macos",
......
......@@ -38,11 +38,14 @@ const String _kNoPlatformsMessage = 'You\'ve created a plugin project that doesn
const String frameworkRevision = '12345678';
const String frameworkChannel = 'omega';
const String _kDisabledPlatformRequestedMessage = 'currently not supported on your local environment.';
// TODO(fujino): replace FakePlatform.fromPlatform() with FakePlatform()
// This needs to be created from the local platform due to re-entrant flutter calls made in this test.
FakePlatform _kNoColorTerminalPlatform() => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
Platform: _kNoColorTerminalPlatform,
};
const String samplesIndexJson = '''
[
{ "id": "sample1" },
......@@ -702,6 +705,7 @@ void main() {
expect(projectDir.childDirectory('macos'), isNot(exists));
expect(projectDir.childDirectory('windows'), isNot(exists));
expect(projectDir.childDirectory('web'), isNot(exists));
expect(projectDir.childDirectory('winuwp'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(),
});
......@@ -720,18 +724,42 @@ void main() {
expect(projectDir.childDirectory('macos'), isNot(exists));
expect(projectDir.childDirectory('windows'), isNot(exists));
expect(projectDir.childDirectory('web'), isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('linux'),
isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('macos'),
isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('windows'),
isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('web'),
isNot(exists));
expect(projectDir.childDirectory('winuwp'), isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('linux'), isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('macos'), isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('windows'), isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('web'), isNot(exists));
expect(projectDir.childDirectory('example').childDirectory('winuwp'), isNot(exists));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(),
});
testUsingContext('app supports Windows UWP if requested', () async {
Cache.flutterRoot = '../..';
final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'create',
'--no-pub',
'--platforms=winuwp',
projectDir.path,
]);
expect(projectDir.childDirectory('linux'), isNot(exists));
expect(projectDir.childDirectory('android'), isNot(exists));
expect(projectDir.childDirectory('ios'), isNot(exists));
expect(projectDir.childDirectory('windows'), isNot(exists));
expect(projectDir.childDirectory('macos'), isNot(exists));
expect(projectDir.childDirectory('web'), isNot(exists));
expect(projectDir.childDirectory('winuwp'), exists);
expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsUwpEnabled: true),
Logger: () => logger,
});
testUsingContext('app supports Linux if requested', () async {
Cache.flutterRoot = '../..';
......@@ -745,13 +773,13 @@ void main() {
projectDir.path,
]);
expect(
projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
expect(projectDir.childDirectory('linux').childFile('CMakeLists.txt'), exists);
expect(projectDir.childDirectory('android'), isNot(exists));
expect(projectDir.childDirectory('ios'), isNot(exists));
expect(projectDir.childDirectory('windows'), isNot(exists));
expect(projectDir.childDirectory('macos'), isNot(exists));
expect(projectDir.childDirectory('web'), isNot(exists));
expect(projectDir.childDirectory('winuwp'), isNot(exists));
expect(logger.errorText, isNot(contains(_kNoPlatformsMessage)));
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment