mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
285 lines
10 KiB
Dart
285 lines
10 KiB
Dart
// Copyright 2017 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 '../base/common.dart';
|
|
import '../base/file_system.dart';
|
|
import '../cache.dart';
|
|
import '../globals.dart';
|
|
import '../runner/flutter_command.dart';
|
|
import '../template.dart';
|
|
|
|
class IdeConfigCommand extends FlutterCommand {
|
|
IdeConfigCommand({this.hidden = false}) {
|
|
argParser.addFlag(
|
|
'overwrite',
|
|
negatable: true,
|
|
defaultsTo: false,
|
|
help: 'When performing operations, overwrite existing files.',
|
|
);
|
|
argParser.addFlag(
|
|
'update-templates',
|
|
negatable: false,
|
|
help: 'Update the templates in the template directory from the current '
|
|
'configuration files. This is the opposite of what $name usually does. '
|
|
'Will search the flutter tree for .iml files and copy any missing ones '
|
|
'into the template directory. If --overwrite is also specified, it will '
|
|
'update any out-of-date files, and remove any deleted files from the '
|
|
'template directory.',
|
|
);
|
|
argParser.addFlag(
|
|
'with-root-module',
|
|
negatable: true,
|
|
defaultsTo: true,
|
|
help: 'Also create module that corresponds to the root of Flutter tree. '
|
|
'This makes the entire Flutter tree browsable and searchable in IDE. '
|
|
'Without this flag, only the child modules will be visible in IDE.',
|
|
);
|
|
}
|
|
|
|
@override
|
|
final String name = 'ide-config';
|
|
|
|
@override
|
|
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{};
|
|
|
|
@override
|
|
final String description = 'Configure the IDE for use in the Flutter tree.\n\n'
|
|
'If run on a Flutter tree that is already configured for the IDE, this '
|
|
'command will add any new configurations, recreate any files that are '
|
|
'missing. If --overwrite is specified, will revert existing files to '
|
|
'the template versions, reset the module list, and return configuration '
|
|
'settings to the template versions.\n\n'
|
|
'This command is intended for Flutter developers to help them set up the'
|
|
"Flutter tree for development in an IDE. It doesn't affect other projects.\n\n"
|
|
'Currently, IntelliJ is the default (and only) IDE that may be configured.';
|
|
|
|
@override
|
|
final bool hidden;
|
|
|
|
@override
|
|
String get invocation => '${runner.executableName} $name';
|
|
|
|
static const String _ideName = 'intellij';
|
|
Directory get _templateDirectory {
|
|
return fs.directory(fs.path.join(
|
|
Cache.flutterRoot,
|
|
'packages',
|
|
'flutter_tools',
|
|
'ide_templates',
|
|
_ideName,
|
|
));
|
|
}
|
|
|
|
Directory get _createTemplatesDirectory {
|
|
return fs.directory(fs.path.join(
|
|
Cache.flutterRoot,
|
|
'packages',
|
|
'flutter_tools',
|
|
'templates',
|
|
));
|
|
}
|
|
|
|
Directory get _flutterRoot => fs.directory(fs.path.absolute(Cache.flutterRoot));
|
|
|
|
// Returns true if any entire path element is equal to dir.
|
|
bool _hasDirectoryInPath(FileSystemEntity entity, String dir) {
|
|
String path = entity.absolute.path;
|
|
while (path.isNotEmpty && fs.path.dirname(path) != path) {
|
|
if (fs.path.basename(path) == dir) {
|
|
return true;
|
|
}
|
|
path = fs.path.dirname(path);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns true if child is anywhere underneath parent.
|
|
bool _isChildDirectoryOf(FileSystemEntity parent, FileSystemEntity child) {
|
|
return child.absolute.path.startsWith(parent.absolute.path);
|
|
}
|
|
|
|
// Checks the contents of the two files to see if they have changes.
|
|
bool _fileIsIdentical(File src, File dest) {
|
|
if (src.lengthSync() != dest.lengthSync()) {
|
|
return false;
|
|
}
|
|
|
|
// Test byte by byte. We're assuming that these are small files.
|
|
final List<int> srcBytes = src.readAsBytesSync();
|
|
final List<int> destBytes = dest.readAsBytesSync();
|
|
for (int i = 0; i < srcBytes.length; ++i) {
|
|
if (srcBytes[i] != destBytes[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Discovers and syncs with existing configuration files in the Flutter tree.
|
|
void _handleTemplateUpdate() {
|
|
if (!_flutterRoot.existsSync()) {
|
|
return;
|
|
}
|
|
|
|
final Set<String> manifest = <String>{};
|
|
final List<FileSystemEntity> flutterFiles = _flutterRoot.listSync(recursive: true);
|
|
for (FileSystemEntity entity in flutterFiles) {
|
|
final String relativePath = fs.path.relative(entity.path, from: _flutterRoot.absolute.path);
|
|
if (entity is! File) {
|
|
continue;
|
|
}
|
|
|
|
final File srcFile = entity;
|
|
|
|
// Skip template files in both the ide_templates and templates
|
|
// directories to avoid copying onto themselves.
|
|
if (_isChildDirectoryOf(_templateDirectory, srcFile) ||
|
|
_isChildDirectoryOf(_createTemplatesDirectory, srcFile)) {
|
|
continue;
|
|
}
|
|
|
|
// Skip files we aren't interested in.
|
|
final RegExp _trackedIdeaFileRegExp = RegExp(
|
|
r'(\.name|modules.xml|vcs.xml)$',
|
|
);
|
|
final bool isATrackedIdeaFile = _hasDirectoryInPath(srcFile, '.idea') &&
|
|
(_trackedIdeaFileRegExp.hasMatch(relativePath) ||
|
|
_hasDirectoryInPath(srcFile, 'runConfigurations'));
|
|
final bool isAnImlOutsideIdea = !isATrackedIdeaFile && srcFile.path.endsWith('.iml');
|
|
if (!isATrackedIdeaFile && !isAnImlOutsideIdea) {
|
|
continue;
|
|
}
|
|
|
|
final File finalDestinationFile = fs.file(fs.path.absolute(
|
|
_templateDirectory.absolute.path, '$relativePath${Template.copyTemplateExtension}'));
|
|
final String relativeDestination =
|
|
fs.path.relative(finalDestinationFile.path, from: _flutterRoot.absolute.path);
|
|
if (finalDestinationFile.existsSync()) {
|
|
if (_fileIsIdentical(srcFile, finalDestinationFile)) {
|
|
printTrace(' $relativeDestination (identical)');
|
|
manifest.add('$relativePath${Template.copyTemplateExtension}');
|
|
continue;
|
|
}
|
|
if (argResults['overwrite']) {
|
|
finalDestinationFile.deleteSync();
|
|
printStatus(' $relativeDestination (overwritten)');
|
|
} else {
|
|
printTrace(' $relativeDestination (existing - skipped)');
|
|
manifest.add('$relativePath${Template.copyTemplateExtension}');
|
|
continue;
|
|
}
|
|
} else {
|
|
printStatus(' $relativeDestination (added)');
|
|
}
|
|
final Directory finalDestinationDir = fs.directory(finalDestinationFile.dirname);
|
|
if (!finalDestinationDir.existsSync()) {
|
|
printTrace(" ${finalDestinationDir.path} doesn't exist, creating.");
|
|
finalDestinationDir.createSync(recursive: true);
|
|
}
|
|
srcFile.copySync(finalDestinationFile.path);
|
|
manifest.add('$relativePath${Template.copyTemplateExtension}');
|
|
}
|
|
|
|
// If we're not overwriting, then we're not going to remove missing items either.
|
|
if (!argResults['overwrite']) {
|
|
return;
|
|
}
|
|
|
|
// Look for any files under the template dir that don't exist in the manifest and remove
|
|
// them.
|
|
final List<FileSystemEntity> templateFiles = _templateDirectory.listSync(recursive: true);
|
|
for (FileSystemEntity entity in templateFiles) {
|
|
if (entity is! File) {
|
|
continue;
|
|
}
|
|
final File templateFile = entity;
|
|
final String relativePath = fs.path.relative(
|
|
templateFile.absolute.path,
|
|
from: _templateDirectory.absolute.path,
|
|
);
|
|
if (!manifest.contains(relativePath)) {
|
|
templateFile.deleteSync();
|
|
final String relativeDestination =
|
|
fs.path.relative(templateFile.path, from: _flutterRoot.absolute.path);
|
|
printStatus(' $relativeDestination (removed)');
|
|
}
|
|
// If the directory is now empty, then remove it, and do the same for its parent,
|
|
// until we escape to the template directory.
|
|
Directory parentDir = fs.directory(templateFile.dirname);
|
|
while (parentDir.listSync().isEmpty) {
|
|
parentDir.deleteSync();
|
|
printTrace(' ${fs.path.relative(parentDir.absolute.path)} (empty directory - removed)');
|
|
parentDir = fs.directory(parentDir.dirname);
|
|
if (fs.path.isWithin(_templateDirectory.absolute.path, parentDir.absolute.path)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<FlutterCommandResult> runCommand() async {
|
|
if (argResults.rest.isNotEmpty) {
|
|
throwToolExit('Currently, the only supported IDE is IntelliJ\n$usage', exitCode: 2);
|
|
}
|
|
|
|
await Cache.instance.updateAll(<DevelopmentArtifact>{ DevelopmentArtifact.universal });
|
|
|
|
if (argResults['update-templates']) {
|
|
_handleTemplateUpdate();
|
|
return null;
|
|
}
|
|
|
|
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
|
|
final String dirPath = fs.path.normalize(
|
|
fs.directory(fs.path.absolute(Cache.flutterRoot)).absolute.path,
|
|
);
|
|
|
|
final String error = _validateFlutterDir(dirPath, flutterRoot: flutterRoot);
|
|
if (error != null) {
|
|
throwToolExit(error);
|
|
}
|
|
|
|
printStatus('Updating IDE configuration for Flutter tree at $dirPath...');
|
|
int generatedCount = 0;
|
|
generatedCount += _renderTemplate(_ideName, dirPath, <String, dynamic>{
|
|
'withRootModule': argResults['with-root-module'],
|
|
});
|
|
|
|
printStatus('Wrote $generatedCount files.');
|
|
printStatus('');
|
|
printStatus('Your IntelliJ configuration is now up to date. It is prudent to '
|
|
'restart IntelliJ, if running.');
|
|
|
|
return null;
|
|
}
|
|
|
|
int _renderTemplate(String templateName, String dirPath, Map<String, dynamic> context) {
|
|
final Template template = Template(_templateDirectory, _templateDirectory);
|
|
return template.render(
|
|
fs.directory(dirPath),
|
|
context,
|
|
overwriteExisting: argResults['overwrite'],
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Return null if the flutter root directory is a valid destination. Return a
|
|
/// validation message if we should disallow the directory.
|
|
String _validateFlutterDir(String dirPath, { String flutterRoot }) {
|
|
final FileSystemEntityType type = fs.typeSync(dirPath);
|
|
|
|
if (type != FileSystemEntityType.notFound) {
|
|
switch (type) {
|
|
case FileSystemEntityType.link:
|
|
// Do not overwrite links.
|
|
return "Invalid project root dir: '$dirPath' - refers to a link.";
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|