Change default organization name to example.com in project templates (#14116)
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 142ec29..b4e3283 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -22,6 +22,7 @@ import '../globals.dart'; import '../ios/xcodeproj.dart'; import '../plugins.dart'; +import '../project.dart'; import '../runner/flutter_command.dart'; import '../template.dart'; import '../version.dart'; @@ -65,7 +66,7 @@ ); argParser.addOption( 'org', - defaultsTo: 'com.yourcompany', + defaultsTo: 'com.example', help: 'The organization responsible for your new Flutter project, in reverse domain name notation.\n' 'This string is used in Java package names and as prefix in the iOS bundle identifier.' ); @@ -135,7 +136,18 @@ // TODO(goderbauer): Work-around for: https://github.com/dart-lang/path/issues/24 if (fs.path.basename(dirPath) == '.') dirPath = fs.path.dirname(dirPath); - final String organization = argResults['org']; + String organization = argResults['org']; + if (!argResults.wasParsed('org')) { + final Set<String> existingOrganizations = await new FlutterProject(projectDir).organizationNames(); + if (existingOrganizations.length == 1) { + organization = existingOrganizations.first; + } else if (1 < existingOrganizations.length) { + throwToolExit( + 'Ambiguous organization in existing files: $existingOrganizations.\n' + 'The --org command line argument must be specified to recreate project.' + ); + } + } final String projectName = fs.path.basename(dirPath); String error =_validateProjectDir(dirPath, flutterRoot: flutterRoot);
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 14fa5a3..a5639f7 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -393,10 +393,10 @@ } if (result.xcodeBuildExecution != null && result.xcodeBuildExecution.buildForPhysicalDevice && - app.id?.contains('com.yourcompany') ?? false) { + app.id?.contains('com.example') ?? false) { printError(''); printError('It appears that your application still contains the default signing identifier.'); - printError("Try replacing 'com.yourcompany' with your signing id in Xcode:"); + printError("Try replacing 'com.example' with your signing id in Xcode:"); printError(' open ios/Runner.xcworkspace'); return; }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart new file mode 100644 index 0000000..1fd1b4f --- /dev/null +++ b/packages/flutter_tools/lib/src/project.dart
@@ -0,0 +1,96 @@ +// Copyright 2018 The Chromium 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 'dart:async'; +import 'dart:convert'; +import 'base/file_system.dart'; + +/// Represents the contents of a Flutter project at the specified [directory]. +class FlutterProject { + FlutterProject(this.directory); + + /// The location of this project. + final Directory directory; + + /// Asynchronously returns the organization names found in this project as + /// part of iOS product bundle identifier, Android application ID, or + /// Gradle group ID. + Future<Set<String>> organizationNames() async { + final List<String> candidates = await Future.wait(<Future<String>>[ + ios.productBundleIdentifier(), + android.applicationId(), + android.group(), + example.android.applicationId(), + example.ios.productBundleIdentifier(), + ]); + return new Set<String>.from( + candidates.map(_organizationNameFromPackageName) + .where((String name) => name != null) + ); + } + + String _organizationNameFromPackageName(String packageName) { + if (packageName != null && 0 <= packageName.lastIndexOf('.')) + return packageName.substring(0, packageName.lastIndexOf('.')); + else + return null; + } + + /// The iOS sub project of this project. + IosProject get ios => new IosProject(directory.childDirectory('ios')); + + /// The Android sub project of this project. + AndroidProject get android => new AndroidProject(directory.childDirectory('android')); + + /// The example sub project of this (plugin) project. + FlutterProject get example => new FlutterProject(directory.childDirectory('example')); +} + +/// Represents the contents of the ios/ folder of a Flutter project. +class IosProject { + static final RegExp _productBundleIdPattern = new RegExp(r'^\s*PRODUCT_BUNDLE_IDENTIFIER\s*=\s*(.*);\s*$'); + IosProject(this.directory); + + final Directory directory; + + Future<String> productBundleIdentifier() { + final File projectFile = directory.childDirectory('Runner.xcodeproj').childFile('project.pbxproj'); + return _firstMatchInFile(projectFile, _productBundleIdPattern).then((Match match) => match?.group(1)); + } +} + +/// Represents the contents of the android/ folder of a Flutter project. +class AndroidProject { + static final RegExp _applicationIdPattern = new RegExp('^\\s*applicationId\\s+[\'\"](.*)[\'\"]\\s*\$'); + static final RegExp _groupPattern = new RegExp('^\\s*group\\s+[\'\"](.*)[\'\"]\\s*\$'); + + AndroidProject(this.directory); + + final Directory directory; + + Future<String> applicationId() { + final File gradleFile = directory.childDirectory('app').childFile('build.gradle'); + return _firstMatchInFile(gradleFile, _applicationIdPattern).then((Match match) => match?.group(1)); + } + + Future<String> group() { + final File gradleFile = directory.childFile('build.gradle'); + return _firstMatchInFile(gradleFile, _groupPattern).then((Match match) => match?.group(1)); + } +} + +/// Asynchronously returns the first line-based match for [regExp] in [file]. +/// +/// Assumes UTF8 encoding. +Future<Match> _firstMatchInFile(File file, RegExp regExp) async { + if (!await file.exists()) { + return null; + } + return file + .openRead() + .transform(UTF8.decoder) + .transform(const LineSplitter()) + .map(regExp.firstMatch) + .firstWhere((Match match) => match != null, defaultValue: () => null); +}