Skip to content

Commit 2285870

Browse files
committed
everything before non batch check
1 parent a3c7ab2 commit 2285870

10 files changed

+459
-269
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:yaml/yaml.dart';
6+
7+
/// A class representing the parsed content of a `ci_config.yaml` file.
8+
class CIConfig {
9+
/// Creates a [CIConfig] from a parsed YAML map.
10+
CIConfig._(this.isBatchRelease);
11+
12+
/// Parses a [CIConfig] from a YAML string.
13+
///
14+
/// Throws if the YAML is not a valid ci_config.yaml.
15+
factory CIConfig.parse(String yaml) {
16+
final Object? loaded = loadYaml(yaml);
17+
if (loaded is! YamlMap) {
18+
throw const FormatException('Root of ci_config.yaml must be a map.');
19+
}
20+
21+
_checkCIConfigEntries(loaded, syntax: _validCIConfigSyntax);
22+
23+
bool isBatchRelease = false;
24+
final Object? release = loaded['release'];
25+
if (release is Map) {
26+
isBatchRelease = release['batch'] == true;
27+
}
28+
29+
return CIConfig._(isBatchRelease);
30+
}
31+
32+
static const Map<String, Object?> _validCIConfigSyntax = <String, Object?>{
33+
'release': <String, Object?>{
34+
'batch': <bool>{true, false}
35+
},
36+
};
37+
38+
/// Returns true if the package is configured for batch release.
39+
final bool isBatchRelease;
40+
41+
static void _checkCIConfigEntries(YamlMap config,
42+
{required Map<String, Object?> syntax, String configPrefix = ''}) {
43+
for (final MapEntry<Object?, Object?> entry in config.entries) {
44+
if (!syntax.containsKey(entry.key)) {
45+
throw FormatException(
46+
'Unknown key `${entry.key}` in config${_formatConfigPrefix(configPrefix)}, the possible keys are ${syntax.keys.toList()}');
47+
} else {
48+
final Object syntaxValue = syntax[entry.key]!;
49+
final String newConfigPrefix = configPrefix.isEmpty
50+
? entry.key! as String
51+
: '$configPrefix.${entry.key}';
52+
if (syntaxValue is Set) {
53+
if (!syntaxValue.contains(entry.value)) {
54+
throw FormatException(
55+
'Invalid value `${entry.value}` for key${_formatConfigPrefix(newConfigPrefix)}, the possible values are ${syntaxValue.toList()}');
56+
}
57+
} else if (entry.value is! YamlMap) {
58+
throw FormatException(
59+
'Invalid value `${entry.value}` for key${_formatConfigPrefix(newConfigPrefix)}, the value must be a map');
60+
} else {
61+
_checkCIConfigEntries(entry.value! as YamlMap,
62+
syntax: syntaxValue as Map<String, Object?>,
63+
configPrefix: newConfigPrefix);
64+
}
65+
}
66+
}
67+
}
68+
69+
static String _formatConfigPrefix(String configPrefix) =>
70+
configPrefix.isEmpty ? '' : ' `$configPrefix`';
71+
}

script/tool/lib/src/common/package_state_utils.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Future<PackageChangeState> checkPackageChangeState(
8080
continue;
8181
}
8282

83-
if (package.parseCiConfig()?.isBatchRelease ?? false) {
83+
if (package.parseCIConfig()?.isBatchRelease ?? false) {
8484
if (components.first == 'pending_changelogs') {
8585
hasChangelogChange = true;
8686
continue;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:file/file.dart';
6+
import 'package:path/path.dart' as p;
7+
import 'package:yaml/yaml.dart';
8+
9+
/// The system for managing pending changelog entries.
10+
///
11+
/// When package opts into batch release (through ci_config.yaml), it uses a "pending
12+
/// changelog" system. When a PR makes a change that requires a changelog entry,
13+
/// the entry is written to a new YAML file in the `pending_changelogs` directory
14+
/// of the package, rather than editing `CHANGELOG.md` directly.
15+
///
16+
/// ## Directory Structure
17+
/// For batch release packages, it has a `pending_changelogs` directory containing:
18+
/// - A `template.yaml` file (which is ignored by the release tooling).
19+
/// - One or more YAML files for pending changes (e.g., `fix_issue_123.yaml`).
20+
///
21+
/// ## File Format
22+
/// The YAML file must contain the following keys:
23+
/// - `changelog`: The text of the changelog entry.
24+
/// - `version`: The type of version bump (`major`, `minor`, `patch`, or `skip`).
25+
///
26+
/// Example:
27+
/// ```yaml
28+
/// changelog: Fixes a bug in the foo widget.
29+
/// version: patch
30+
/// ```
31+
///
32+
/// During a release (specifically the `update-changelogs` command), all
33+
/// pending entries are aggregated, the package version is updated based on the
34+
/// highest priority change, and `CHANGELOG.md` is updated.
35+
36+
/// The type of version change described by a changelog entry.
37+
///
38+
/// The order of the enum values is important as it is used to determine which version
39+
/// take priority when multiple version changes are specified. The top most value
40+
/// (the samller the index) has the highest priority.
41+
enum VersionChange {
42+
/// A major version change (e.g., 1.2.3 -> 2.0.0).
43+
major,
44+
45+
/// A minor version change (e.g., 1.2.3 -> 1.3.0).
46+
minor,
47+
48+
/// A patch version change (e.g., 1.2.3 -> 1.2.4).
49+
patch,
50+
51+
/// No version change.
52+
skip,
53+
}
54+
55+
/// Represents a single entry in the pending changelog.
56+
class PendingChangelogEntry {
57+
/// Creates a new pending changelog entry.
58+
PendingChangelogEntry(
59+
{required this.changelog, required this.version, required this.file});
60+
61+
/// The template file name used to draft a pending changelog file.
62+
/// This file will not be picked up by the batch release process.
63+
static const String _batchReleaseChangelogTemplateFileName = 'template.yaml';
64+
65+
/// Returns true if the file is a template file.
66+
static bool isTemplate(File file) {
67+
return p.basename(file.path) == _batchReleaseChangelogTemplateFileName;
68+
}
69+
70+
/// Creates a PendingChangelogEntry from a YAML string.
71+
///
72+
/// Throws if the YAML is not a valid pending changelog entry.
73+
factory PendingChangelogEntry.parse(String yamlContent, File file) {
74+
final dynamic yaml = loadYaml(yamlContent);
75+
if (yaml is! YamlMap) {
76+
throw FormatException(
77+
'Expected a YAML map, but found ${yaml.runtimeType}.');
78+
}
79+
80+
final dynamic changelogYaml = yaml['changelog'];
81+
if (changelogYaml is! String) {
82+
throw FormatException(
83+
'Expected "changelog" to be a string, but found ${changelogYaml.runtimeType}.');
84+
}
85+
final String changelog = changelogYaml.trim();
86+
87+
final String? versionString = yaml['version'] as String?;
88+
if (versionString == null) {
89+
throw const FormatException('Missing "version" key.');
90+
}
91+
final VersionChange version = VersionChange.values.firstWhere(
92+
(VersionChange e) => e.name == versionString,
93+
orElse: () =>
94+
throw FormatException('Invalid version type: $versionString'),
95+
);
96+
97+
return PendingChangelogEntry(
98+
changelog: changelog, version: version, file: file);
99+
}
100+
101+
/// The changelog messages for this entry.
102+
final String changelog;
103+
104+
/// The type of version change for this entry.
105+
final VersionChange version;
106+
107+
/// The file that this entry was parsed from.
108+
final File file;
109+
}

script/tool/lib/src/common/repository_package.dart

Lines changed: 9 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import 'package:path/path.dart' as p;
77
import 'package:pubspec_parse/pubspec_parse.dart';
88
import 'package:yaml/yaml.dart';
99

10+
import 'ci_config.dart';
1011
import 'core.dart';
12+
import 'pending_changelog_entry.dart';
1113

1214
export 'package:pubspec_parse/pubspec_parse.dart' show Pubspec;
15+
export 'ci_config.dart';
1316
export 'core.dart' show FlutterPlatform;
14-
15-
// The template file name used to draft a pending changelog file.
16-
// This file will not be picked up by the batch release process.
17-
const String _kTemplateFileName = 'template.yaml';
17+
export 'pending_changelog_entry.dart';
1818

1919
/// A package in the repository.
2020
//
@@ -125,14 +125,14 @@ class RepositoryPackage {
125125
/// Caches for future use.
126126
Pubspec parsePubspec() => _parsedPubspec;
127127

128-
late final CiConfig? _parsedCiConfig = ciConfigFile.existsSync()
129-
? CiConfig._parse(ciConfigFile.readAsStringSync())
128+
late final CIConfig? _parsedCIConfig = ciConfigFile.existsSync()
129+
? CIConfig.parse(ciConfigFile.readAsStringSync())
130130
: null;
131131

132132
/// Returns the parsed [ciConfigFile], or null if it does not exist.
133133
///
134134
/// Throws if the file exists but is not a valid ci_config.yaml.
135-
CiConfig? parseCiConfig() => _parsedCiConfig;
135+
CIConfig? parseCIConfig() => _parsedCIConfig;
136136

137137
/// Returns true if the package depends on Flutter.
138138
bool requiresFlutter() {
@@ -262,7 +262,7 @@ class RepositoryPackage {
262262
for (final File file in allFiles) {
263263
final String basename = p.basename(file.path);
264264
if (basename.endsWith('.yaml')) {
265-
if (basename != _kTemplateFileName) {
265+
if (!PendingChangelogEntry.isTemplate(file)) {
266266
pendingChangelogFiles.add(file);
267267
}
268268
} else {
@@ -273,8 +273,7 @@ class RepositoryPackage {
273273

274274
for (final File file in pendingChangelogFiles) {
275275
try {
276-
entries
277-
.add(PendingChangelogEntry._parse(file.readAsStringSync(), file));
276+
entries.add(PendingChangelogEntry.parse(file.readAsStringSync(), file));
278277
} on FormatException catch (e) {
279278
throw FormatException(
280279
'Malformed pending changelog file: ${file.path}\n$e');
@@ -283,135 +282,3 @@ class RepositoryPackage {
283282
return entries;
284283
}
285284
}
286-
287-
/// A class representing the parsed content of a `ci_config.yaml` file.
288-
class CiConfig {
289-
/// Creates a [CiConfig] from a parsed YAML map.
290-
CiConfig._(this.isBatchRelease);
291-
292-
/// Parses a [CiConfig] from a YAML string.
293-
///
294-
/// Throws if the YAML is not a valid ci_config.yaml.
295-
factory CiConfig._parse(String yaml) {
296-
final Object? loaded = loadYaml(yaml);
297-
if (loaded is! YamlMap) {
298-
throw const FormatException('Root of ci_config.yaml must be a map.');
299-
}
300-
301-
_checkCiConfigEntries(loaded, syntax: _validCiConfigSyntax);
302-
303-
bool isBatchRelease = false;
304-
final Object? release = loaded['release'];
305-
if (release is Map) {
306-
isBatchRelease = release['batch'] == true;
307-
}
308-
309-
return CiConfig._(isBatchRelease);
310-
}
311-
312-
static const Map<String, Object?> _validCiConfigSyntax = <String, Object?>{
313-
'release': <String, Object?>{
314-
'batch': <bool>{true, false}
315-
},
316-
};
317-
318-
/// Returns true if the package is configured for batch release.
319-
final bool isBatchRelease;
320-
321-
static void _checkCiConfigEntries(YamlMap config,
322-
{required Map<String, Object?> syntax, String configPrefix = ''}) {
323-
for (final MapEntry<Object?, Object?> entry in config.entries) {
324-
if (!syntax.containsKey(entry.key)) {
325-
throw FormatException(
326-
'Unknown key `${entry.key}` in config${_formatConfigPrefix(configPrefix)}, the possible keys are ${syntax.keys.toList()}');
327-
} else {
328-
final Object syntaxValue = syntax[entry.key]!;
329-
final String newConfigPrefix = configPrefix.isEmpty
330-
? entry.key! as String
331-
: '$configPrefix.${entry.key}';
332-
if (syntaxValue is Set) {
333-
if (!syntaxValue.contains(entry.value)) {
334-
throw FormatException(
335-
'Invalid value `${entry.value}` for key${_formatConfigPrefix(newConfigPrefix)}, the possible values are ${syntaxValue.toList()}');
336-
}
337-
} else if (entry.value is! YamlMap) {
338-
throw FormatException(
339-
'Invalid value `${entry.value}` for key${_formatConfigPrefix(newConfigPrefix)}, the value must be a map');
340-
} else {
341-
_checkCiConfigEntries(entry.value! as YamlMap,
342-
syntax: syntaxValue as Map<String, Object?>,
343-
configPrefix: newConfigPrefix);
344-
}
345-
}
346-
}
347-
}
348-
349-
static String _formatConfigPrefix(String configPrefix) =>
350-
configPrefix.isEmpty ? '' : ' `$configPrefix`';
351-
}
352-
353-
/// The type of version change described by a changelog entry.
354-
///
355-
/// The order of the enum values is important as it is used to determine which version
356-
/// take priority when multiple version changes are specified. The top most value
357-
/// (the samller the index) has the highest priority.
358-
enum VersionChange {
359-
/// A major version change (e.g., 1.2.3 -> 2.0.0).
360-
major,
361-
362-
/// A minor version change (e.g., 1.2.3 -> 1.3.0).
363-
minor,
364-
365-
/// A patch version change (e.g., 1.2.3 -> 1.2.4).
366-
patch,
367-
368-
/// No version change.
369-
skip,
370-
}
371-
372-
/// Represents a single entry in the pending changelog.
373-
class PendingChangelogEntry {
374-
/// Creates a new pending changelog entry.
375-
PendingChangelogEntry(
376-
{required this.changelog, required this.version, required this.file});
377-
378-
/// Creates a PendingChangelogEntry from a YAML string.
379-
///
380-
/// Throws if the YAML is not a valid pending changelog entry.
381-
factory PendingChangelogEntry._parse(String yamlContent, File file) {
382-
final dynamic yaml = loadYaml(yamlContent);
383-
if (yaml is! YamlMap) {
384-
throw FormatException(
385-
'Expected a YAML map, but found ${yaml.runtimeType}.');
386-
}
387-
388-
final dynamic changelogYaml = yaml['changelog'];
389-
if (changelogYaml is! String) {
390-
throw FormatException(
391-
'Expected "changelog" to be a string, but found ${changelogYaml.runtimeType}.');
392-
}
393-
final String changelog = changelogYaml.trim();
394-
395-
final String? versionString = yaml['version'] as String?;
396-
if (versionString == null) {
397-
throw const FormatException('Missing "version" key.');
398-
}
399-
final VersionChange version = VersionChange.values.firstWhere(
400-
(VersionChange e) => e.name == versionString,
401-
orElse: () =>
402-
throw FormatException('Invalid version type: $versionString'),
403-
);
404-
405-
return PendingChangelogEntry(
406-
changelog: changelog, version: version, file: file);
407-
}
408-
409-
/// The changelog messages for this entry.
410-
final String changelog;
411-
412-
/// The type of version change for this entry.
413-
final VersionChange version;
414-
415-
/// The file that this entry was parsed from.
416-
final File file;
417-
}

0 commit comments

Comments
 (0)