Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions packages/go_router/pending_changelogs/template.yaml

This file was deleted.

6 changes: 0 additions & 6 deletions packages/go_router/pending_changelogs/test_only_1.yaml

This file was deleted.

5 changes: 0 additions & 5 deletions packages/go_router/pending_changelogs/test_only_2.yaml

This file was deleted.

156 changes: 21 additions & 135 deletions script/tool/lib/src/branch_for_batch_release_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'dart:math' as math;
import 'package:file/file.dart';
import 'package:git/git.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';

import 'common/core.dart';
Expand All @@ -19,10 +18,6 @@ import 'common/repository_package.dart';
const int _kExitPackageMalformed = 3;
const int _kGitFailedToPush = 4;

// The template file name used to draft a pending changelog file.
// This file will not be picked up by the batch release process.
const String _kTemplateFileName = 'template.yaml';

/// A command to create a remote branch with release changes for a single package.
class BranchForBatchReleaseCommand extends PackageCommand {
/// Creates a new `branch-for-batch-release` command.
Expand Down Expand Up @@ -69,10 +64,15 @@ class BranchForBatchReleaseCommand extends PackageCommand {
final GitDir repository = await gitDir;

print('Parsing package "${package.displayName}"...');
final _PendingChangelogs pendingChangelogs = await _getPendingChangelogs(
package,
);
if (pendingChangelogs.entries.isEmpty) {
final List<PendingChangelogEntry> pendingChangelogs;
try {
pendingChangelogs = package.getPendingChangelogs();
} on FormatException catch (e) {
printError('Failed to parse pending changelogs: ${e.message}');
throw ToolExit(_kExitPackageMalformed);
}

if (pendingChangelogs.isEmpty) {
print('No pending changelogs found for ${package.displayName}.');
return;
}
Expand All @@ -85,7 +85,7 @@ class BranchForBatchReleaseCommand extends PackageCommand {
throw ToolExit(_kExitPackageMalformed);
}
final _ReleaseInfo releaseInfo = _getReleaseInfo(
pendingChangelogs.entries,
pendingChangelogs,
pubspec.version!,
);

Expand All @@ -101,77 +101,36 @@ class BranchForBatchReleaseCommand extends PackageCommand {
git: repository,
package: package,
branchName: branchName,
pendingChangelogFiles: pendingChangelogs.files,
pendingChangelogFiles: pendingChangelogs
.map<File>((PendingChangelogEntry e) => e.file)
.toList(),
releaseInfo: releaseInfo,
remoteName: remoteName,
);
}

/// Returns the parsed changelog entries for the given package.
///
/// This method read through the files in the pending_changelogs folder
/// and parsed each file as a changelog entry.
///
/// Throws a [ToolExit] if the package does not have a pending_changelogs folder.
Future<_PendingChangelogs> _getPendingChangelogs(
RepositoryPackage package,
) async {
final Directory pendingChangelogsDir = package.directory.childDirectory(
'pending_changelogs',
);
if (!pendingChangelogsDir.existsSync()) {
printError(
'No pending_changelogs folder found for ${package.displayName}.',
);
throw ToolExit(_kExitPackageMalformed);
}
final List<File> pendingChangelogFiles = pendingChangelogsDir
.listSync()
.whereType<File>()
.where(
(File f) =>
f.basename.endsWith('.yaml') && f.basename != _kTemplateFileName,
)
.toList();
try {
final List<_PendingChangelogEntry> entries = pendingChangelogFiles
.map<_PendingChangelogEntry>(
(File f) => _PendingChangelogEntry.parse(f.readAsStringSync()),
)
.toList();
return _PendingChangelogs(entries, pendingChangelogFiles);
} on FormatException catch (e) {
printError('Malformed pending changelog file: $e');
throw ToolExit(_kExitPackageMalformed);
}
}

/// Returns the release info for the given package.
///
/// This method read through the parsed changelog entries decide the new version
/// by following the version change rules. See [_VersionChange] for more details.
_ReleaseInfo _getReleaseInfo(
List<_PendingChangelogEntry> pendingChangelogEntries,
List<PendingChangelogEntry> pendingChangelogEntries,
Version oldVersion,
) {
final changelogs = <String>[];
int versionIndex = _VersionChange.skip.index;
int versionIndex = VersionChange.skip.index;
for (final entry in pendingChangelogEntries) {
changelogs.add(entry.changelog);
versionIndex = math.min(versionIndex, entry.version.index);
}
final _VersionChange effectiveVersionChange =
_VersionChange.values[versionIndex];
final VersionChange effectiveVersionChange =
VersionChange.values[versionIndex];

final Version? newVersion = switch (effectiveVersionChange) {
_VersionChange.skip => null,
_VersionChange.major => Version(oldVersion.major + 1, 0, 0),
_VersionChange.minor => Version(
oldVersion.major,
oldVersion.minor + 1,
0,
),
_VersionChange.patch => Version(
VersionChange.skip => null,
VersionChange.major => Version(oldVersion.major + 1, 0, 0),
VersionChange.minor => Version(oldVersion.major, oldVersion.minor + 1, 0),
VersionChange.patch => Version(
oldVersion.major,
oldVersion.minor,
oldVersion.patch + 1,
Expand Down Expand Up @@ -305,18 +264,6 @@ class BranchForBatchReleaseCommand extends PackageCommand {
}
}

/// A data class for pending changelogs.
class _PendingChangelogs {
/// Creates a new instance.
_PendingChangelogs(this.entries, this.files);

/// The parsed pending changelog entries.
final List<_PendingChangelogEntry> entries;

/// The files that the pending changelog entries were parsed from.
final List<File> files;
}

/// A data class for processed release information.
class _ReleaseInfo {
/// Creates a new instance.
Expand All @@ -328,64 +275,3 @@ class _ReleaseInfo {
/// The combined changelog entries.
final List<String> changelogs;
}

/// The type of version change for a release.
///
/// The order of the enum values is important as it is used to determine which version
/// take priority when multiple version changes are specified. The top most value
/// (the samller the index) has the highest priority.
enum _VersionChange {
/// A major version change (e.g., 1.2.3 -> 2.0.0).
major,

/// A minor version change (e.g., 1.2.3 -> 1.3.0).
minor,

/// A patch version change (e.g., 1.2.3 -> 1.2.4).
patch,

/// No version change.
skip,
}

/// Represents a single entry in the pending changelog.
class _PendingChangelogEntry {
/// Creates a new pending changelog entry.
_PendingChangelogEntry({required this.changelog, required this.version});

/// Creates a PendingChangelogEntry from a YAML string.
factory _PendingChangelogEntry.parse(String yamlContent) {
final dynamic yaml = loadYaml(yamlContent);
if (yaml is! YamlMap) {
throw FormatException(
'Expected a YAML map, but found ${yaml.runtimeType}.',
);
}

final dynamic changelogYaml = yaml['changelog'];
if (changelogYaml is! String) {
throw FormatException(
'Expected "changelog" to be a string, but found ${changelogYaml.runtimeType}.',
);
}
final String changelog = changelogYaml.trim();

final versionString = yaml['version'] as String?;
if (versionString == null) {
throw const FormatException('Missing "version" key.');
}
final _VersionChange version = _VersionChange.values.firstWhere(
(_VersionChange e) => e.name == versionString,
orElse: () =>
throw FormatException('Invalid version type: $versionString'),
);

return _PendingChangelogEntry(changelog: changelog, version: version);
}

/// The changelog messages for this entry.
final String changelog;

/// The type of version change for this entry.
final _VersionChange version;
}
79 changes: 79 additions & 0 deletions script/tool/lib/src/common/ci_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:yaml/yaml.dart';

/// A class representing the parsed content of a `ci_config.yaml` file.
class CIConfig {
/// Creates a [CIConfig] from a parsed YAML map.
CIConfig._(this.isBatchRelease);

/// Parses a [CIConfig] from a YAML string.
///
/// Throws if the YAML is not a valid ci_config.yaml.
factory CIConfig.parse(String yaml) {
final Object? loaded = loadYaml(yaml);
if (loaded is! YamlMap) {
throw const FormatException('Root of ci_config.yaml must be a map.');
}

_checkCIConfigEntries(loaded, syntax: _validCIConfigSyntax);

var isBatchRelease = false;
final Object? release = loaded['release'];
if (release is Map) {
isBatchRelease = release['batch'] == true;
}

return CIConfig._(isBatchRelease);
}

static const Map<String, Object?> _validCIConfigSyntax = <String, Object?>{
'release': <String, Object?>{
'batch': <bool>{true, false},
},
};

/// Returns true if the package is configured for batch release.
final bool isBatchRelease;

static void _checkCIConfigEntries(
YamlMap config, {
required Map<String, Object?> syntax,
String configPrefix = '',
}) {
for (final MapEntry<Object?, Object?> entry in config.entries) {
if (!syntax.containsKey(entry.key)) {
throw FormatException(
'Unknown key `${entry.key}` in config${_formatConfigPrefix(configPrefix)}, the possible keys are ${syntax.keys.toList()}',
);
} else {
final Object syntaxValue = syntax[entry.key]!;
final newConfigPrefix = configPrefix.isEmpty
? entry.key! as String
: '$configPrefix.${entry.key}';
if (syntaxValue is Set) {
if (!syntaxValue.contains(entry.value)) {
throw FormatException(
'Invalid value `${entry.value}` for key${_formatConfigPrefix(newConfigPrefix)}, the possible values are ${syntaxValue.toList()}',
);
}
} else if (entry.value is! YamlMap) {
throw FormatException(
'Invalid value `${entry.value}` for key${_formatConfigPrefix(newConfigPrefix)}, the value must be a map',
);
} else {
_checkCIConfigEntries(
entry.value! as YamlMap,
syntax: syntaxValue as Map<String, Object?>,
configPrefix: newConfigPrefix,
);
}
}
}
}

static String _formatConfigPrefix(String configPrefix) =>
configPrefix.isEmpty ? '' : ' `$configPrefix`';
}
7 changes: 7 additions & 0 deletions script/tool/lib/src/common/package_state_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ Future<PackageChangeState> checkPackageChangeState(
continue;
}

if (package.parseCIConfig()?.isBatchRelease ?? false) {
if (components.first == 'pending_changelogs') {
hasChangelogChange = true;
continue;
}
}

if (!needsVersionChange) {
// Developer-only changes don't need version changes or changelog changes.
if (await _isDevChange(components, git: git, repoPath: path)) {
Expand Down
Loading