Unverified Commit d8b6fa15 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] generate a synthetic flutter_gen package on pub get (#61261)

Allow configuring the flutter_manifest to support a synthetic package, this is done through flutter: generate: true.

When running pub get, insert a flutter_gen entry into the packages if it does not already exist. This points to .dart_tool/flutter_gen, which can be updated to contain the generated intl sources (But doesn't currently)

Adds an integration test that verifies this code can be run and imported when enabled.

Part of #60914
parent 3631adc9
......@@ -104,6 +104,7 @@ class BuildRunner extends CodeGenerator {
directory: generatedDirectory.path,
upgrade: false,
checkLastModified: false,
generateSyntheticPackage: false,
);
if (!scriptIdFile.existsSync()) {
scriptIdFile.createSync(recursive: true);
......
......@@ -448,7 +448,8 @@ class _ResidentWebRunner extends ResidentWebRunner {
// This will result in a NoSuchMethodError thrown by injected_handler.darts
await pub.get(
context: PubContext.pubGet,
directory: globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools')
directory: globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools'),
generateSyntheticPackage: false,
);
final ExpressionCompiler expressionCompiler =
......
......@@ -211,6 +211,7 @@ class BuildDaemonCreator {
await pub.get(
context: PubContext.pubGet,
directory: globals.fs.file(buildScriptPackages).parent.path,
generateSyntheticPackage: false,
);
}
final String flutterWebSdk = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk);
......
......@@ -543,6 +543,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
context: PubContext.create,
directory: directory.path,
offline: boolArg('offline'),
generateSyntheticPackage: false,
);
final FlutterProject project = FlutterProject.fromDirectory(directory);
await project.ensureReadyForPlatformSpecificTooling(checkProjects: false);
......@@ -562,6 +563,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
context: PubContext.createPackage,
directory: directory.path,
offline: boolArg('offline'),
generateSyntheticPackage: false,
);
}
return generatedCount;
......@@ -606,6 +608,7 @@ To edit platform code in an IDE see https://flutter.dev/developing-packages/#edi
context: PubContext.createPlugin,
directory: directory.path,
offline: boolArg('offline'),
generateSyntheticPackage: false,
);
}
......@@ -674,7 +677,12 @@ https://flutter.dev/docs/development/packages-and-plugins/developing-packages#pl
}
if (boolArg('pub')) {
await pub.get(context: PubContext.create, directory: directory.path, offline: boolArg('offline'));
await pub.get(
context: PubContext.create,
directory: directory.path,
offline: boolArg('offline'),
generateSyntheticPackage: false,
);
await project.ensureReadyForPlatformSpecificTooling(checkProjects: pluginExampleApp);
}
if (templateContext['android'] == true) {
......
......@@ -88,7 +88,7 @@ class PackagesGetCommand extends FlutterCommand {
return usageValues;
}
Future<void> _runPubGet(String directory) async {
Future<void> _runPubGet(String directory, FlutterProject flutterProject) async {
final Stopwatch pubGetTimer = Stopwatch()..start();
try {
await pub.get(context: PubContext.pubGet,
......@@ -96,6 +96,7 @@ class PackagesGetCommand extends FlutterCommand {
upgrade: upgrade ,
offline: boolArg('offline'),
checkLastModified: false,
generateSyntheticPackage: flutterProject.manifest.generateSyntheticPackage,
);
pubGetTimer.stop();
globals.flutterUsage.sendTiming('pub', 'get', pubGetTimer.elapsed, label: 'success');
......@@ -121,15 +122,15 @@ class PackagesGetCommand extends FlutterCommand {
'${ workingDirectory ?? "current working directory" }.'
);
}
await _runPubGet(target);
final FlutterProject rootProject = FlutterProject.fromPath(target);
await _runPubGet(target, rootProject);
await rootProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
// Get/upgrade packages in example app as well
if (rootProject.hasExampleApp) {
final FlutterProject exampleProject = rootProject.example;
await _runPubGet(exampleProject.directory.path);
await _runPubGet(exampleProject.directory.path, exampleProject);
await exampleProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
}
......
......@@ -163,15 +163,19 @@ class TestCommand extends FlutterCommand {
"called *_test.dart and must reside in the package's 'test' "
'directory (or one of its subdirectories).');
}
final FlutterProject flutterProject = FlutterProject.current();
if (shouldRunPub) {
await pub.get(context: PubContext.getVerifyContext(name), skipPubspecYamlCheck: true);
await pub.get(
context: PubContext.getVerifyContext(name),
skipPubspecYamlCheck: true,
generateSyntheticPackage: flutterProject.manifest.generateSyntheticPackage,
);
}
final bool buildTestAssets = boolArg('test-assets');
final List<String> names = stringsArg('name');
final List<String> plainNames = stringsArg('plain-name');
final String tags = stringArg('tags');
final String excludeTags = stringArg('exclude-tags');
final FlutterProject flutterProject = FlutterProject.current();
if (buildTestAssets && flutterProject.manifest.assets.isNotEmpty) {
await _buildTestAsset();
......@@ -222,7 +226,7 @@ class TestCommand extends FlutterCommand {
final bool machine = boolArg('machine');
CoverageCollector collector;
if (boolArg('coverage') || boolArg('merge-coverage')) {
final String projectName = FlutterProject.current().manifest.appName;
final String projectName = flutterProject.manifest.appName;
collector = CoverageCollector(
verbose: !machine,
libraryPredicate: (String libraryName) => libraryName.contains(projectName),
......
......@@ -308,6 +308,7 @@ class UpdatePackagesCommand extends FlutterCommand {
flutterRootOverride: upgrade
? temporaryFlutterSdk.path
: null,
generateSyntheticPackage: false,
);
// Cleanup the temporary SDK
try {
......@@ -387,6 +388,7 @@ class UpdatePackagesCommand extends FlutterCommand {
directory: dir.path,
checkLastModified: false,
offline: offline,
generateSyntheticPackage: false,
);
count += 1;
}
......
......@@ -294,7 +294,13 @@ class UpgradeCommandRunner {
final String projectRoot = findProjectRoot();
if (projectRoot != null) {
globals.printStatus('');
await pub.get(context: PubContext.pubUpgrade, directory: projectRoot, upgrade: true, checkLastModified: false);
await pub.get(
context: PubContext.pubUpgrade,
directory: projectRoot,
upgrade: true,
checkLastModified: false,
generateSyntheticPackage: false,
);
}
}
......
......@@ -154,6 +154,7 @@ class VersionCommand extends FlutterCommand {
directory: projectRoot,
upgrade: true,
checkLastModified: false,
generateSyntheticPackage: false,
);
}
......
......@@ -207,6 +207,7 @@ Future<T> runInContext<T>(
botDetector: globals.botDetector,
platform: globals.platform,
usage: globals.flutterUsage,
toolStampFile: globals.cache.getStampFileFor('flutter_tools'),
),
ShutdownHooks: () => ShutdownHooks(logger: globals.logger),
Stdio: () => Stdio(),
......
......@@ -5,6 +5,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:process/process.dart';
import '../base/bot_detector.dart';
......@@ -16,6 +17,7 @@ import '../base/logger.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../cache.dart';
import '../dart/package_map.dart';
import '../reporting/reporting.dart';
/// The [Pub] instance.
......@@ -39,7 +41,7 @@ class PubContext {
for (final String item in _values) {
if (!_validContext.hasMatch(item)) {
throw ArgumentError.value(
_values, 'value', 'Must match RegExp ${_validContext.pattern}');
_values, 'value', 'Must match RegExp ${_validContext.pattern}');
}
}
}
......@@ -80,6 +82,7 @@ abstract class Pub {
@required Platform platform,
@required BotDetector botDetector,
@required Usage usage,
File toolStampFile,
}) = _DefaultPub;
/// Runs `pub get`.
......@@ -94,6 +97,7 @@ abstract class Pub {
bool offline = false,
bool checkLastModified = true,
bool skipPubspecYamlCheck = false,
bool generateSyntheticPackage = false,
String flutterRootOverride,
});
......@@ -139,7 +143,9 @@ class _DefaultPub implements Pub {
@required Platform platform,
@required BotDetector botDetector,
@required Usage usage,
}) : _fileSystem = fileSystem,
File toolStampFile,
}) : _toolStampFile = toolStampFile,
_fileSystem = fileSystem,
_logger = logger,
_platform = platform,
_botDetector = botDetector,
......@@ -155,6 +161,7 @@ class _DefaultPub implements Pub {
final Platform _platform;
final BotDetector _botDetector;
final Usage _usage;
final File _toolStampFile;
@override
Future<void> get({
......@@ -165,6 +172,7 @@ class _DefaultPub implements Pub {
bool offline = false,
bool checkLastModified = true,
bool skipPubspecYamlCheck = false,
bool generateSyntheticPackage = false,
String flutterRootOverride,
}) async {
directory ??= _fileSystem.currentDirectory.path;
......@@ -173,6 +181,7 @@ class _DefaultPub implements Pub {
_fileSystem.path.join(directory, 'pubspec.yaml'));
final File packageConfigFile = _fileSystem.file(
_fileSystem.path.join(directory, '.dart_tool', 'package_config.json'));
final Directory generatedDirectory = _fileSystem.directory(_fileSystem.path.join(directory, '.dart_tool', 'flutter_gen'));
if (!skipPubspecYamlCheck && !pubSpecYaml.existsSync()) {
if (!skipIfAbsent) {
......@@ -242,6 +251,10 @@ class _DefaultPub implements Pub {
'The time now is: $now'
);
}
// Insert references to synthetic flutter package.
if (generateSyntheticPackage) {
await _updatePackageConfig(packageConfigFile, generatedDirectory);
}
}
@override
......@@ -387,6 +400,12 @@ class _DefaultPub implements Pub {
if (pubSpecYaml.lastModifiedSync().isAfter(dotPackagesLastModified)) {
return true;
}
if (_toolStampFile != null &&
_toolStampFile.existsSync() &&
_toolStampFile.lastModifiedSync().isAfter(dotPackagesLastModified)) {
return true;
}
return false;
}
......@@ -439,4 +458,27 @@ class _DefaultPub implements Pub {
}
return environment;
}
/// Insert the flutter_gen synthetic package into the package configuration file if
/// there is an l10n.yaml.
Future<void> _updatePackageConfig(File packageConfigFile, Directory generatedDirectory) async {
if (!packageConfigFile.existsSync()) {
return;
}
final PackageConfig packageConfig = await loadPackageConfigWithLogging(packageConfigFile, logger: _logger);
final Package flutterGen = Package('flutter_gen', generatedDirectory.uri, languageVersion: LanguageVersion(2, 8));
if (packageConfig.packages.any((Package package) => package.name == 'flutter_gen')) {
return;
}
final PackageConfig newPackageConfig = PackageConfig(
<Package>[
...packageConfig.packages,
flutterGen,
],
);
// There is no current API for saving a package_config without hitting the real filesystem.
if (packageConfigFile.fileSystem is LocalFileSystem) {
await savePackageConfig(newPackageConfig, packageConfigFile.parent.parent);
}
}
}
......@@ -279,6 +279,26 @@ class FlutterManifest {
}
return fonts;
}
/// Whether a synthetic flutter_gen package should be generated.
///
/// This can be provided to the [Pub] interface to inject a new entry
/// into the package_config.json file which points to `.dart_tool/flutter_gen`.
///
/// This allows generated source code to be imported using a package
/// alias.
bool get generateSyntheticPackage => _generateSyntheticPackage ??= _computeGenerateSyntheticPackage();
bool _generateSyntheticPackage;
bool _computeGenerateSyntheticPackage() {
if (!_flutterDescriptor.containsKey('generate')) {
return false;
}
final Object value = _flutterDescriptor['generate'];
if (value is! bool) {
return false;
}
return value as bool;
}
}
class Font {
......@@ -438,6 +458,8 @@ void _validateFlutter(YamlMap yaml, List<String> errors) {
final List<String> pluginErrors = Plugin.validatePluginYaml(kvp.value as YamlMap);
errors.addAll(pluginErrors);
break;
case 'generate':
break;
default:
errors.add('Unexpected child "${kvp.key}" found under "flutter".');
break;
......
......@@ -845,10 +845,13 @@ abstract class FlutterCommand extends Command<void> {
await validateCommand();
if (shouldRunPub) {
await pub.get(context: PubContext.getVerifyContext(name));
final FlutterProject project = FlutterProject.current();
await pub.get(
context: PubContext.getVerifyContext(name),
generateSyntheticPackage: project.manifest.generateSyntheticPackage,
);
// All done updating dependencies. Release the cache lock.
Cache.releaseLock();
final FlutterProject project = FlutterProject.current();
await project.ensureReadyForPlatformSpecificTooling(checkProjects: true);
} else {
Cache.releaseLock();
......
......@@ -301,5 +301,6 @@ Future<void> _ensurePackageDependencies(String packagePath) async {
await pub.get(
context: PubContext.pubGet,
directory: packagePath,
generateSyntheticPackage: false,
);
}
......@@ -77,7 +77,11 @@ void main() {
botDetector: globals.botDetector,
usage: globals.flutterUsage,
);
await pub.get(context: PubContext.flutterTests, directory: tempDir.path);
await pub.get(
context: PubContext.flutterTests,
directory: tempDir.path,
generateSyntheticPackage: false,
);
server = AnalysisServer(
globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath),
......@@ -111,8 +115,13 @@ void main() {
platform: const LocalPlatform(),
usage: globals.flutterUsage,
botDetector: globals.botDetector,
toolStampFile: globals.fs.file('test'),
);
await pub.get(
context: PubContext.flutterTests,
directory: tempDir.path,
generateSyntheticPackage: false,
);
await pub.get(context: PubContext.flutterTests, directory: tempDir.path);
server = AnalysisServer(
globals.artifacts.getArtifactPath(Artifact.engineDartSdkPath),
......
......@@ -197,10 +197,10 @@ void main() {
});
testWithoutContext('analytics sent on success', () async {
MockDirectory.findCache = true;
final FileSystem fileSystem = MemoryFileSystem.test();
final MockUsage usage = MockUsage();
final Pub pub = Pub(
fileSystem: MockFileSystem(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
processManager: MockProcessManager(0),
botDetector: const BotDetectorAlwaysNo(),
......@@ -211,8 +211,16 @@ void main() {
}
),
);
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.dart_tool/package_config.json')
..createSync(recursive: true)
..writeAsStringSync('{"configVersion": 2,"packages": []}');
await pub.get(context: PubContext.flutterTests, checkLastModified: false);
await pub.get(
context: PubContext.flutterTests,
generateSyntheticPackage: true,
checkLastModified: false,
);
verify(usage.sendEvent('pub-result', 'flutter-tests', label: 'success')).called(1);
});
......@@ -242,10 +250,10 @@ void main() {
});
testWithoutContext('analytics sent on failed version solve', () async {
MockDirectory.findCache = true;
final MockUsage usage = MockUsage();
final FileSystem fileSystem = MemoryFileSystem.test();
final Pub pub = Pub(
fileSystem: MockFileSystem(),
fileSystem: fileSystem,
logger: BufferLogger.test(),
processManager: MockProcessManager(
1,
......@@ -259,6 +267,7 @@ void main() {
usage: usage,
botDetector: const BotDetectorAlwaysNo(),
);
fileSystem.file('pubspec.yaml').writeAsStringSync('name: foo');
try {
await pub.get(context: PubContext.flutterTests, checkLastModified: false);
......
......@@ -82,6 +82,60 @@ flutter:
expect(flutterManifest.usesMaterialDesign, true);
});
testWithoutContext('FlutterManifest knows if generate is provided', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
generate: true
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.generateSyntheticPackage, true);
});
testWithoutContext('FlutterManifest can parse invalid generate key', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
generate: "invalid"
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.generateSyntheticPackage, false);
});
testWithoutContext('FlutterManifest knows if generate is disabled', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
generate: false
''';
final BufferLogger logger = BufferLogger.test();
final FlutterManifest flutterManifest = FlutterManifest.createFromString(
manifest,
logger: logger,
);
expect(flutterManifest.generateSyntheticPackage, false);
});
testWithoutContext('FlutterManifest has two assets', () async {
const String manifest = '''
name: test
......
......@@ -297,6 +297,7 @@ void main() {
verify(pub.get(
context: PubContext.pubGet,
directory: anyNamed('directory'),
generateSyntheticPackage: false,
)).called(1);
expect(bufferLogger.statusText, contains('Debug service listening on ws://127.0.0.1/abcd/'));
......
// Copyright 2014 The Flutter 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 'package:file/file.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import '../src/common.dart';
import 'test_data/basic_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
Directory tempDir;
final BasicProjectWithFlutterGen project = BasicProjectWithFlutterGen();
FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('run_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
test('can correctly reference flutter generated code.', () async {
await flutter.run();
});
}
......@@ -53,7 +53,11 @@ class BasicProject extends Project {
int get topLevelFunctionBreakpointLine => lineContaining(main, '// TOP LEVEL BREAKPOINT');
}
class BasicProjectWithUnaryMain extends Project {
class BasicProjectWithFlutterGen extends Project {
@override
final String generatedFile = '''
String x = "a";
''';
@override
final String pubspec = '''
......@@ -64,21 +68,42 @@ class BasicProjectWithUnaryMain extends Project {
dependencies:
flutter:
sdk: flutter
flutter:
generate: true
''';
@override
final String main = r'''
import 'dart:async';
import 'package:flutter_gen/flutter_gen.dart';
import 'package:flutter/material.dart';
void main() {}
''';
}
class BasicProjectWithUnaryMain extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
''';
@override
final String main = r'''
import 'dart:async';
import 'package:flutter/material.dart';
Future<void> main(List<String> args) async {
while (true) {
runApp(new MyApp());
await Future.delayed(const Duration(milliseconds: 50));
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
......@@ -89,7 +114,6 @@ class BasicProjectWithUnaryMain extends Project {
);
}
}
topLevelFunction() {
print("topLevelFunction"); // TOP LEVEL BREAKPOINT
}
......
......@@ -27,6 +27,7 @@ abstract class Project {
String get pubspec;
String get main;
String get test => null;
String get generatedFile => null;
Uri get mainDart => Uri.parse('package:test/main.dart');
......@@ -39,6 +40,9 @@ abstract class Project {
if (test != null) {
writeFile(globals.fs.path.join(dir.path, 'test', 'test.dart'), test);
}
if (generatedFile != null) {
writeFile(globals.fs.path.join(dir.path, '.dart_tool', 'flutter_gen', 'flutter_gen.dart'), generatedFile);
}
writeFile(globals.fs.path.join(dir.path, 'web', 'index.html'), _kDefaultHtml);
writePackages(dir.path);
await getPackages(dir.path);
......
......@@ -30,6 +30,7 @@ class ThrowingPub implements Pub {
bool offline = false,
bool checkLastModified = true,
bool skipPubspecYamlCheck = false,
bool generateSyntheticPackage = false,
String flutterRootOverride,
}) {
throw UnsupportedError('Attempted to invoke pub during test.');
......
......@@ -95,6 +95,7 @@ void main() {
when(pub.get(
context: PubContext.pubGet,
directory: anyNamed('directory'),
generateSyntheticPackage: false,
)).thenAnswer((Invocation invocation) async {
// Create valid package entry.
packagesFile.writeAsStringSync('flutter_template_images:file:///flutter_template_images');
......
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