Skip to content

Commit c4388ef

Browse files
committed
everything before non batch check
1 parent a0333a0 commit c4388ef

9 files changed

+419
-266
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
//
@@ -124,14 +124,14 @@ class RepositoryPackage {
124124
/// Caches for future use.
125125
Pubspec parsePubspec() => _parsedPubspec;
126126

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

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

136136
/// Returns true if the package depends on Flutter.
137137
bool requiresFlutter() {
@@ -256,7 +256,7 @@ class RepositoryPackage {
256256
for (final File file in allFiles) {
257257
final String basename = p.basename(file.path);
258258
if (basename.endsWith('.yaml')) {
259-
if (basename != _kTemplateFileName) {
259+
if (!PendingChangelogEntry.isTemplate(file)) {
260260
pendingChangelogFiles.add(file);
261261
}
262262
} else {
@@ -267,8 +267,7 @@ class RepositoryPackage {
267267

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

0 commit comments

Comments
 (0)