Unverified Commit 90d978f8 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Add windowsIdentifier template parameter (#82588)

Windows package identifiers are globally unique strings, typically a
GUID. These are required for templates that require a Windows package
name as described in
https://docs.microsoft.com/en-us/windows/win32/appxpkg/appx-portal

Fixes https://github.com/flutter/flutter/issues/82587
parent 71a7909f
...@@ -423,9 +423,10 @@ Your $application code is in $relativeAppMain. ...@@ -423,9 +423,10 @@ Your $application code is in $relativeAppMain.
final String androidPluginIdentifier = templateContext['androidIdentifier'] as String; final String androidPluginIdentifier = templateContext['androidIdentifier'] as String;
final String exampleProjectName = projectName + '_example'; final String exampleProjectName = projectName + '_example';
templateContext['projectName'] = exampleProjectName; templateContext['projectName'] = exampleProjectName;
templateContext['androidIdentifier'] = createAndroidIdentifier(organization, exampleProjectName); templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName);
templateContext['iosIdentifier'] = createUTIIdentifier(organization, exampleProjectName); templateContext['iosIdentifier'] = CreateBase.createUTIIdentifier(organization, exampleProjectName);
templateContext['macosIdentifier'] = createUTIIdentifier(organization, exampleProjectName); templateContext['macosIdentifier'] = CreateBase.createUTIIdentifier(organization, exampleProjectName);
templateContext['windowsIdentifier'] = CreateBase.createWindowsIdentifier(organization, exampleProjectName);
templateContext['description'] = 'Demonstrates how to use the $projectName plugin.'; templateContext['description'] = 'Demonstrates how to use the $projectName plugin.';
templateContext['pluginProjectName'] = projectName; templateContext['pluginProjectName'] = projectName;
templateContext['androidPluginIdentifier'] = androidPluginIdentifier; templateContext['androidPluginIdentifier'] = androidPluginIdentifier;
......
...@@ -340,6 +340,8 @@ abstract class CreateBase extends FlutterCommand { ...@@ -340,6 +340,8 @@ abstract class CreateBase extends FlutterCommand {
createUTIIdentifier(organization, projectName); createUTIIdentifier(organization, projectName);
final String androidIdentifier = final String androidIdentifier =
createAndroidIdentifier(organization, projectName); createAndroidIdentifier(organization, projectName);
final String windowsIdentifier =
createWindowsIdentifier(organization, projectName);
// Linux uses the same scheme as the Android identifier. // Linux uses the same scheme as the Android identifier.
// https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid // https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid
final String linuxIdentifier = androidIdentifier; final String linuxIdentifier = androidIdentifier;
...@@ -351,6 +353,7 @@ abstract class CreateBase extends FlutterCommand { ...@@ -351,6 +353,7 @@ abstract class CreateBase extends FlutterCommand {
'iosIdentifier': appleIdentifier, 'iosIdentifier': appleIdentifier,
'macosIdentifier': appleIdentifier, 'macosIdentifier': appleIdentifier,
'linuxIdentifier': linuxIdentifier, 'linuxIdentifier': linuxIdentifier,
'windowsIdentifier': windowsIdentifier,
'description': projectDescription, 'description': projectDescription,
'dartSdk': '$flutterRoot/bin/cache/dart-sdk', 'dartSdk': '$flutterRoot/bin/cache/dart-sdk',
'androidMinApiLevel': android_common.minApiLevel, 'androidMinApiLevel': android_common.minApiLevel,
...@@ -444,8 +447,7 @@ abstract class CreateBase extends FlutterCommand { ...@@ -444,8 +447,7 @@ abstract class CreateBase extends FlutterCommand {
/// ///
/// Android application ID is specified in: https://developer.android.com/studio/build/application-id /// Android application ID is specified in: https://developer.android.com/studio/build/application-id
/// All characters must be alphanumeric or an underscore [a-zA-Z0-9_]. /// All characters must be alphanumeric or an underscore [a-zA-Z0-9_].
@protected static String createAndroidIdentifier(String organization, String name) {
String createAndroidIdentifier(String organization, String name) {
String tmpIdentifier = '$organization.$name'; String tmpIdentifier = '$organization.$name';
final RegExp disallowed = RegExp(r'[^\w\.]'); final RegExp disallowed = RegExp(r'[^\w\.]');
tmpIdentifier = tmpIdentifier.replaceAll(disallowed, ''); tmpIdentifier = tmpIdentifier.replaceAll(disallowed, '');
...@@ -470,14 +472,20 @@ abstract class CreateBase extends FlutterCommand { ...@@ -470,14 +472,20 @@ abstract class CreateBase extends FlutterCommand {
return prefixedSegments.join('.'); return prefixedSegments.join('.');
} }
/// Creates a Windows package name.
///
/// Package names must be a globally unique, commonly a GUID.
static String createWindowsIdentifier(String organization, String name) {
return const Uuid().v4().toUpperCase();
}
String _createPluginClassName(String name) { String _createPluginClassName(String name) {
final String camelizedName = camelCase(name); final String camelizedName = camelCase(name);
return camelizedName[0].toUpperCase() + camelizedName.substring(1); return camelizedName[0].toUpperCase() + camelizedName.substring(1);
} }
/// Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name /// Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name
@protected static String createUTIIdentifier(String organization, String name) {
String createUTIIdentifier(String organization, String name) {
name = camelCase(name); name = camelCase(name);
String tmpIdentifier = '$organization.$name'; String tmpIdentifier = '$organization.$name';
final RegExp disallowed = RegExp(r'[^a-zA-Z0-9\-\.\u0080-\uffff]+'); final RegExp disallowed = RegExp(r'[^a-zA-Z0-9\-\.\u0080-\uffff]+');
......
...@@ -18,6 +18,7 @@ import 'package:flutter_tools/src/base/platform.dart'; ...@@ -18,6 +18,7 @@ import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/commands/create_base.dart';
import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
...@@ -26,6 +27,7 @@ import 'package:flutter_tools/src/version.dart'; ...@@ -26,6 +27,7 @@ import 'package:flutter_tools/src/version.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:pub_semver/pub_semver.dart'; import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:pubspec_parse/pubspec_parse.dart';
import 'package:uuid/uuid.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -83,6 +85,28 @@ void main() { ...@@ -83,6 +85,28 @@ void main() {
await _restoreFlutterToolsSnapshot(); await _restoreFlutterToolsSnapshot();
}); });
test('createAndroidIdentifier emits a valid identifier', () {
final String identifier = CreateBase.createAndroidIdentifier('42org', '8project');
expect(identifier.contains('.'), isTrue);
final RegExp startsWithLetter = RegExp(r'^[a-zA-Z][\w]*$');
final List<String> segments = identifier.split('.');
for (final String segment in segments) {
expect(startsWithLetter.hasMatch(segment), isTrue);
}
});
test('createUTIIdentifier emits a valid identifier', () {
final String identifier = CreateBase.createUTIIdentifier('org@', 'project');
expect(identifier.contains('.'), isTrue);
expect(identifier.contains('@'), isFalse);
});
test('createWindowsIdentifier emits a GUID', () {
final String identifier = CreateBase.createWindowsIdentifier('org', 'project');
expect(Uuid.isValidUUID(fromString: identifier), isTrue);
});
// Verify that we create a default project ('app') that is // Verify that we create a default project ('app') that is
// well-formed. // well-formed.
testUsingContext('can create a default project', () async { testUsingContext('can create a default project', () async {
......
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