blob: 0c097ff314fbd7b6a9cba16c7bafd2e79b5135b7 [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. 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:convert' show jsonDecode;
import 'dart:io';
import 'package:path/path.dart' as path;
import 'project.dart';
import 'sdk.dart';
import 'utils.dart';
typedef LogFunction = void Function(String);
class ProjectCreator {
final String _dartSdkPath;
final String _flutterToolPath;
final String _templatesPath;
/// The Dart Language Version to use for code using null safety.
final String _dartLanguageVersion;
final File _dependenciesFile;
final LogFunction _log;
ProjectCreator(
Sdk sdk,
this._templatesPath, {
required String dartLanguageVersion,
required File dependenciesFile,
required LogFunction log,
}) : _dartSdkPath = sdk.dartSdkPath,
_flutterToolPath = sdk.flutterToolPath,
_dartLanguageVersion = dartLanguageVersion,
_dependenciesFile = dependenciesFile,
_log = log;
/// Builds a basic Dart project template directory, complete with `pubspec.yaml`
/// and `analysis_options.yaml`.
Future<void> buildDartProjectTemplate() async {
final projectPath = path.join(_templatesPath, 'dart_project');
final projectDirectory = Directory(projectPath);
await projectDirectory.create(recursive: true);
final dependencies = _dependencyVersions(supportedBasicDartPackages);
File(path.join(projectPath, 'pubspec.yaml'))
.writeAsStringSync(createPubspec(
includeFlutterWeb: false,
dartLanguageVersion: _dartLanguageVersion,
dependencies: dependencies,
));
await _runDartPubGet(projectDirectory);
File(path.join(projectPath, 'analysis_options.yaml')).writeAsStringSync('''
include: package:lints/recommended.yaml
linter:
rules:
avoid_print: false
''');
}
/// Builds a Flutter project template directory, complete with `pubspec.yaml`,
/// `analysis_options.yaml`, and `web/index.html`.
///
/// Depending on [firebaseStyle], Firebase packages are included in
/// `pubspec.yaml` which affects how `flutter packages get` will register
/// plugins.
Future<void> buildFlutterProjectTemplate({
required FirebaseStyle firebaseStyle,
}) async {
final projectDirName = firebaseStyle == FirebaseStyle.none
? 'flutter_project'
: 'firebase_project';
final projectPath = path.join(
_templatesPath,
projectDirName,
);
final projectDir = await Directory(projectPath).create(recursive: true);
await Directory(path.join(projectPath, 'lib')).create();
await Directory(path.join(projectPath, 'web')).create();
await File(path.join(projectPath, 'web', 'index.html')).create();
var packages = {
...supportedBasicDartPackages,
...supportedFlutterPackages,
if (firebaseStyle != FirebaseStyle.none) ...coreFirebasePackages,
if (firebaseStyle == FirebaseStyle.flutterFire)
...registerableFirebasePackages,
};
final dependencies = _dependencyVersions(packages);
File(path.join(projectPath, 'pubspec.yaml'))
.writeAsStringSync(createPubspec(
includeFlutterWeb: true,
dartLanguageVersion: _dartLanguageVersion,
dependencies: dependencies,
));
await runFlutterPackagesGet(_flutterToolPath, projectPath, log: _log);
if (firebaseStyle != FirebaseStyle.none) {
// `flutter packages get` has been run with a _subset_ of all supported
// Firebase packages, the ones that don't require a Firebase app to be
// configured in JavaScript, before executing Dart. Now add the full set of
// supported Firebase pacakges. This workaround is a very fragile hack.
packages = {
...supportedBasicDartPackages,
...supportedFlutterPackages,
...firebasePackages,
};
final dependencies = _dependencyVersions(packages);
File(path.join(projectPath, 'pubspec.yaml'))
.writeAsStringSync(createPubspec(
includeFlutterWeb: true,
dartLanguageVersion: _dartLanguageVersion,
dependencies: dependencies,
));
await _runDartPubGet(projectDir);
}
File(path.join(projectPath, 'analysis_options.yaml')).writeAsStringSync('''
include: package:flutter_lints/flutter.yaml
linter:
rules:
avoid_print: false
use_key_in_widget_constructors: false
''');
}
Future<void> _runDartPubGet(Directory dir) async {
final process = await runWithLogging(
path.join(_dartSdkPath, 'bin', 'dart'),
arguments: ['pub', 'get'],
workingDirectory: dir.path,
environment: {'PUB_CACHE': _pubCachePath},
log: _log,
);
await process.exitCode;
}
Map<String, String> _dependencyVersions(
Iterable<String> packages,
) {
final allVersions =
parsePubDependenciesFile(dependenciesFile: _dependenciesFile);
return {
for (var package in packages) package: allVersions[package] ?? 'any',
// Overwrite with important constraints:
...packageVersionConstraints,
};
}
}
/// A mapping of version constraints for certain packages.
const packageVersionConstraints = {
// Ensure that pub version solving keeps these at sane minimum versions.
'cloud_firestore': '^3.1.0',
'firebase_auth': '^3.3.0',
'firebase_core': '^1.10.0',
};
/// Parses [dependenciesFile] as a JSON Map of Strings.
Map<String, String> parsePubDependenciesFile({required File dependenciesFile}) {
final packageVersions =
jsonDecode(dependenciesFile.readAsStringSync()) as Map;
return packageVersions.cast<String, String>();
}
/// Build a return a `pubspec.yaml` file.
String createPubspec({
required bool includeFlutterWeb,
required String dartLanguageVersion,
Map<String, String> dependencies = const {},
}) {
var content = '''
name: dartpad_sample
environment:
sdk: '>=$dartLanguageVersion <3.0.0'
dependencies:
''';
if (includeFlutterWeb) {
content += '''
flutter:
sdk: flutter
flutter_test:
sdk: flutter
''';
}
dependencies.forEach((name, version) {
content += ' $name: $version\n';
});
return content;
}
Future<void> runFlutterPackagesGet(
String flutterToolPath,
String projectPath, {
required LogFunction log,
}) async {
final process = await runWithLogging(flutterToolPath,
arguments: ['packages', 'get'],
workingDirectory: projectPath,
environment: {'PUB_CACHE': _pubCachePath},
log: log);
await process.exitCode;
}
/// Builds the local pub cache directory and returns the path.
String get _pubCachePath {
final pubCachePath = path.join(Directory.current.path, 'local_pub_cache');
Directory(pubCachePath).createSync();
return pubCachePath;
}
enum FirebaseStyle {
/// Indicates that no Firebase is used.
none,
/// Indicates that the "pure Dart" Flutterfire packages are used.
flutterFire,
}