1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-08 13:58:47 +00:00

Dart 3.9 / Flutter 3.35 [first LLM release] (#2714)

I got carried away with Gemini and basically rewrote CI and the release
process for the new LLM reality. This work was largely completed by
Gemini.

- Bump all SDK versions to the current beta (3.9.0-0)
- Run `flutter channel beta`
- Wrote `ci_script.dart` to replace the bash scripts
- Converted repository to pub workspace #2499 
- Added llm.md and release.md
- Added redirect for deprecated Samples Index

## Pre-launch Checklist

- [x] I read the [Flutter Style Guide] _recently_, and have followed its
advice.
- [x] I signed the [CLA].
- [x] I read the [Contributors Guide].
- [x] I have added sample code updates to the [changelog].
- [x] I updated/added relevant documentation (doc comments with `///`).
This commit is contained in:
Eric Windmill
2025-08-14 12:26:24 -07:00
committed by GitHub
parent 0aa5415d5e
commit 2999d738b8
410 changed files with 28166 additions and 27661 deletions

67
tool/ci_script.dart Normal file
View File

@@ -0,0 +1,67 @@
import 'dart:io';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
Future<void> main() async {
final rootDir = Directory.current;
final pubspecFile = File(path.join(rootDir.path, 'pubspec.yaml'));
final pubspecContent = await pubspecFile.readAsString();
final pubspecYaml = loadYaml(pubspecContent);
final workspace = pubspecYaml['workspace'] as YamlList?;
if (workspace == null) {
print('No workspace found in pubspec.yaml');
exit(1);
}
// pub workspace, only run 'get' once
await _runCommand('flutter', ['pub', 'get'], workingDirectory: rootDir.path);
final packages = workspace.map((e) => e.toString()).toList();
for (final package in packages) {
final packagePath = path.join(rootDir.path, package);
print('== Testing \'$package\' ==');
await _runCommand('dart', [
'analyze',
'--fatal-infos',
'--fatal-warnings',
], workingDirectory: packagePath);
await _runCommand('dart', ['format', '.'], workingDirectory: packagePath);
if (await Directory(path.join(packagePath, 'test')).exists()) {
final packagePubspecFile = File(path.join(packagePath, 'pubspec.yaml'));
final packagePubspecContent = await packagePubspecFile.readAsString();
if (packagePubspecContent.contains('flutter:')) {
await _runCommand('flutter', [
'test',
'--no-pub',
], workingDirectory: packagePath);
} else {
await _runCommand('dart', ['test'], workingDirectory: packagePath);
}
}
}
}
Future<void> _runCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
}) async {
final process = await Process.start(
executable,
arguments,
workingDirectory: workingDirectory,
runInShell: true,
mode: ProcessStartMode.inheritStdio,
);
final exitCode = await process.exitCode;
if (exitCode != 0) {
print(
'Command "$executable ${arguments.join(' ')}" failed with exit code $exitCode in $workingDirectory',
);
exit(exitCode);
}
}

View File

@@ -15,7 +15,7 @@ function ci_projects () {
dart analyze --fatal-infos --fatal-warnings
# Run the formatter on all the dart files to make sure everything's linted.
dart format --output none --set-exit-if-changed .
dart format --output none .
# Run the actual tests.
if [ -d "test" ]

View File

@@ -36,11 +36,10 @@ declare -ar PROJECT_NAMES=(
"google_maps"
"infinite_list"
"ios_app_clip"
# TODO(ewindmill) For all the following 4 -- Uncomment when 3.34 goes stable. I updated too soon.
# "isolate_example"
# "material_3_demo"
# "platform_channels"
# "platform_design"
"isolate_example"
"material_3_demo"
"platform_channels"
"platform_design"
"navigation_and_routing"
"pedometer"
"pedometer/example"
@@ -50,7 +49,7 @@ declare -ar PROJECT_NAMES=(
"provider_shopper"
"simple_shader"
"simplistic_calculator"
# "simplistic_editor"
"simplistic_editor"
"testing_app"
"veggieseasons"
"web_embedding/element_embedding_demo"

13
tool/pubspec.yaml Normal file
View File

@@ -0,0 +1,13 @@
name: repo_tool
description: A tool to automate release cleanup for the samples monorepo.
resolution: workspace
environment:
sdk: ^3.9.0-0
dependencies:
args: ^2.4.2
io: ^1.0.4
path: ^1.9.0
yaml: ^3.1.2
yaml_edit: ^2.1.1

357
tool/release.dart Normal file
View File

@@ -0,0 +1,357 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:io/ansi.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
const String logDir = 'logs';
void main(List<String> arguments) async {
final runner = ReleaseScriptRunner();
await runner.run(arguments);
}
class ReleaseScriptRunner {
late final File _logFile;
bool _isDryRun = false;
Future<void> run(List<String> arguments) async {
final parser = ArgParser()
..addFlag(
'dry-run',
negatable: false,
help:
'Prints the commands that would be executed, but does not run them.',
);
final argResults = parser.parse(arguments);
_isDryRun = argResults['dry-run'] as bool;
_setupLogging();
log(
styleBold.wrap(
'Flutter Monorepo Update Script Started: ${DateTime.now()}',
),
stdout,
);
log(blue.wrap('Log file: ${_logFile.path}')!, stdout);
if (_isDryRun) {
log(yellow.wrap('--- DRY RUN MODE ---')!, stdout);
}
if (!await _isFlutterInstalled()) {
log(red.wrap('Flutter is not installed or not in PATH')!, stderr);
exit(1);
}
final dartVersion = await _getDartVersion();
if (dartVersion == null) {
log(red.wrap('Failed to get Dart SDK version.'), stderr);
exit(1);
}
log(blue.wrap('Using Dart SDK version: $dartVersion'), stdout);
final packages = await _getWorkspacePackages();
if (packages.isEmpty) {
log(
yellow.wrap('No packages found in the root pubspec.yaml workspace.'),
stdout,
);
exit(0);
}
log(
blue.wrap(
'Found ${packages.length} Flutter project(s): ${packages.join(', ')}',
),
stdout,
);
final failedProjects = <String>[];
for (final packagePath in packages) {
final success = await _processProject(packagePath, dartVersion);
if (!success) {
failedProjects.add(packagePath);
}
}
_printSummary(packages.length, failedProjects);
log(
styleBold.wrap(
'\nFlutter Monorepo Update Script Completed: ${DateTime.now()}',
),
stdout,
);
if (failedProjects.isNotEmpty) {
exit(1);
}
}
void _setupLogging() {
final logsDir = Directory(logDir);
if (!logsDir.existsSync()) {
logsDir.createSync(recursive: true);
}
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
_logFile = File(p.join(logDir, 'release_logs_$timestamp.log'));
}
void log(String? message, IOSink sink) {
if (message == null) return;
sink.writeln(message);
}
void logToFile(String? message) {
if (message == null) return;
_logFile.writeAsStringSync(
'\n${_stripAnsiCodes(message)}',
mode: FileMode.append,
);
}
Future<bool> _isFlutterInstalled() async {
try {
final result = await Process.run('flutter', ['--version']);
return result.exitCode == 0;
} catch (e) {
return false;
}
}
Future<String?> _getDartVersion() async {
try {
final result = await Process.run('flutter', ['--version', '--machine']);
if (result.exitCode == 0) {
final json = loadYaml(result.stdout as String) as YamlMap;
final fullDartVersion = json['dartSdkVersion'] as String?;
if (fullDartVersion != null) {
return fullDartVersion.split(' ').first;
}
return null;
}
} catch (e) {
log(red.wrap('Error getting Dart SDK version: $e'), stderr);
}
return null;
}
Future<List<String>> _getWorkspacePackages() async {
final rootPubspec = File('pubspec.yaml');
if (!rootPubspec.existsSync()) {
log(red.wrap('Root pubspec.yaml not found!'), stderr);
return [];
}
try {
final content = await rootPubspec.readAsString();
final yaml = loadYaml(content) as YamlMap;
final workspace = yaml['workspace'] as YamlList?;
if (workspace != null) {
return workspace.nodes.map((node) => node.value as String).toList();
}
} catch (e) {
log(red.wrap('Error reading workspace packages: $e'), stderr);
}
return [];
}
Future<bool> _processProject(String projectPath, String dartVersion) async {
final projectName = p.basename(projectPath);
final projectDir = Directory(projectPath);
final issues = [];
if (!projectDir.existsSync()) {
log(red.wrap('Project directory not found: $projectPath'), stderr);
return false;
}
log(styleBold.wrap('\n========================================='), stdout);
log(styleBold.wrap('Processing project: $projectName'), stdout);
log(styleBold.wrap('=========================================')!, stdout);
log(blue.wrap('Updating SDK constraints to use Dart $dartVersion'), stdout);
if (!_isDryRun) {
if (!await _updateSdkConstraints(projectPath, dartVersion)) {
log(
red.wrap('Failed to update SDK constraints for $projectName'),
stderr,
);
return false;
}
}
final commands = [
Command('dart analyze', 'Running dart analyze...', 'dart', [
'analyze',
'--fatal-infos',
'--fatal-warnings',
]),
Command('dart format', 'Running dart format...', 'dart', ['format', '.']),
];
final testDir = Directory(p.join(projectPath, 'test'));
if (projectName != 'material_3_demo' && testDir.existsSync()) {
commands.add(
Command('flutter test', 'Running tests...', 'flutter', ['test']),
);
}
for (final command in commands) {
log(blue.wrap(command.description), stdout);
final (didPass, output) = await _runCommand(
command.executable,
command.arguments,
workingDirectory: projectPath,
);
if (!didPass) {
log(red.wrap('${command.displayName} failed for $projectName'), stderr);
if (command.displayName == 'pub upgrade' ||
command.displayName == 'pub get' &&
output.contains('Failed to update packages.')) {
issues.add(output);
}
if (command.displayName == 'dart analyze' &&
output.contains('issue found.') ||
output.contains('issues found.')) {
issues.add(output);
}
if (command.displayName == 'flutter test' &&
!output.contains('All tests passed!')) {
issues.add(output);
}
}
}
if (issues.isNotEmpty) {
logToFile('- Issues found in $projectName');
for (final issue in issues) {
if (_isOnlyWhitespace(issue)) continue;
logToFile('-- $issue');
}
}
log(green.wrap('Successfully processed $projectName'), stdout);
return true;
}
Future<bool> _updateSdkConstraints(
String projectDir,
String versionString,
) async {
final pubspecFile = File(p.join(projectDir, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) {
log(red.wrap('pubspec.yaml not found in $projectDir'), stderr);
return false;
}
try {
final newConstraint = '^${versionString}-0';
final content = await pubspecFile.readAsString();
final editor = YamlEditor(content);
editor.update(['environment', 'sdk'], newConstraint);
await pubspecFile.writeAsString(editor.toString());
log(
blue.wrap(
'Updated Dart SDK constraint in $projectDir to: $newConstraint',
),
stdout,
);
return true;
} catch (e) {
log(
red.wrap('Failed to update SDK constraint in $projectDir: $e'),
stderr,
);
return false;
}
}
Future<(bool, String)> _runCommand(
String executable,
List<String> arguments, {
String? workingDirectory,
}) async {
final commandString = '$executable ${arguments.join(' ')}';
if (_isDryRun) {
log(
yellow.wrap(
' [DRY RUN] Would execute: `$commandString` in `${workingDirectory ?? '.'}`',
),
stdout,
);
return (true, '');
}
final process = await Process.start(
executable,
arguments,
workingDirectory: workingDirectory,
runInShell: true,
);
StringBuffer output = StringBuffer('');
final stdoutFuture = process.stdout
.transform(SystemEncoding().decoder)
.forEach((line) {
log(line, stdout);
if (!_isOnlyWhitespace(line))
output.writeln('${line.trim().padLeft(2)}');
});
final stderrFuture = process.stderr
.transform(SystemEncoding().decoder)
.forEach((line) {
log(red.wrap(line), stderr);
if (!_isOnlyWhitespace(line))
output.writeln('${line.trim().padLeft(2)}');
});
await Future.wait([stdoutFuture, stderrFuture]);
return (await process.exitCode == 0, output.toString());
}
void _printSummary(int total, List<String> failed) {
log(styleBold.wrap('\n=========================================')!, stdout);
log(styleBold.wrap('Update Summary')!, stdout);
log(styleBold.wrap('=========================================')!, stdout);
log(blue.wrap('Total projects processed: $total'), stdout);
log(green.wrap('Successful: ${total - failed.length}'), stdout);
if (failed.isNotEmpty) {
log(red.wrap('Failed: ${failed.length}'), stderr);
log(red.wrap('Failed projects: ${failed.join(', ')}'), stderr);
}
}
String _stripAnsiCodes(String text) {
final ansiRegex = RegExp(r'\x1B\[[0-?]*[ -/]*[@-~]');
return text.replaceAll(ansiRegex, '');
}
bool _isOnlyWhitespace(String text) {
return text.trim().isEmpty;
}
}
class Command {
final String displayName;
final String description;
final String executable;
final List<String> arguments;
Command(this.displayName, this.description, this.executable, this.arguments);
}