diff --git a/dev/conductor/core/lib/src/next.dart b/dev/conductor/core/lib/src/next.dart index 242a10ac98..864517935e 100644 --- a/dev/conductor/core/lib/src/next.dart +++ b/dev/conductor/core/lib/src/next.dart @@ -85,79 +85,7 @@ class NextContext extends Context { CherrypickState.ABANDONED, ]; switch (state.currentPhase) { - case pb.ReleasePhase.APPLY_ENGINE_CHERRYPICKS: - final Remote upstream = Remote.upstream(state.engine.upstream.url); - final EngineRepository engine = EngineRepository( - checkouts, - initialRef: state.engine.workingBranch, - upstreamRemote: upstream, - previousCheckoutLocation: state.engine.checkoutPath, - ); - if (!state_import.requiresEnginePR(state)) { - stdio.printStatus('This release has no engine cherrypicks. No Engine PR is necessary.\n'); - break; - } - - final List unappliedCherrypicks = [ - for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) - if (!finishedStates.contains(cherrypick.state)) cherrypick, - ]; - - if (unappliedCherrypicks.isEmpty) { - stdio.printStatus('All engine cherrypicks have been auto-applied by the conductor.\n'); - } else { - if (unappliedCherrypicks.length == 1) { - stdio.printStatus( - 'There was ${unappliedCherrypicks.length} cherrypick that was not auto-applied.', - ); - } else { - stdio.printStatus( - 'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.', - ); - } - stdio.printStatus( - 'These must be applied manually in the directory ' - '${state.engine.checkoutPath} before proceeding.\n', - ); - } - if (!autoAccept) { - final bool response = await prompt( - 'Are you ready to push your engine branch to the repository ' - '${state.engine.mirror.url}?', - ); - if (!response) { - stdio.printError('Aborting command.'); - updateState(state, stdio.logs); - return; - } - } - - await pushWorkingBranch(engine, state.engine); - case pb.ReleasePhase.VERIFY_ENGINE_CI: - stdio.printStatus('You must validate post-submit CI for your engine PR and merge it'); - if (!autoAccept) { - final bool response = await prompt( - 'Has CI passed for the engine PR?\n\n' - '${state_import.luciConsoleLink(state.engine.candidateBranch, 'engine')}', - ); - if (!response) { - stdio.printError('Aborting command.'); - updateState(state, stdio.logs); - return; - } - } case pb.ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: - final Remote engineUpstreamRemote = Remote.upstream(state.engine.upstream.url); - final EngineRepository engine = EngineRepository( - checkouts, - // We explicitly want to check out the merged version from upstream - initialRef: '${engineUpstreamRemote.name}/${state.engine.candidateBranch}', - upstreamRemote: engineUpstreamRemote, - previousCheckoutLocation: state.engine.checkoutPath, - ); - - final String engineRevision = await engine.reverseParse('HEAD'); - final Remote upstream = Remote.upstream(state.framework.upstream.url); final FrameworkRepository framework = FrameworkRepository( checkouts, @@ -166,7 +94,7 @@ class NextContext extends Context { previousCheckoutLocation: state.framework.checkoutPath, ); stdio.printStatus('Writing candidate branch...'); - bool needsCommit = await framework.updateCandidateBranchVersion( + final bool needsCommit = await framework.updateCandidateBranchVersion( state.framework.candidateBranch, ); if (needsCommit) { @@ -181,20 +109,6 @@ class NextContext extends Context { ..state = pb.CherrypickState.COMPLETED, ); } - stdio.printStatus('Rolling new engine hash $engineRevision to framework checkout...'); - needsCommit = await framework.updateEngineRevision(engineRevision); - if (needsCommit) { - final String revision = await framework.commit( - 'Update Engine revision to $engineRevision for ${state.releaseChannel} release ${state.releaseVersion}', - addFirst: true, - ); - // append to list of cherrypicks so we know a PR is required - state.framework.cherrypicks.add( - pb.Cherrypick.create() - ..appliedRevision = revision - ..state = pb.CherrypickState.COMPLETED, - ); - } final List unappliedCherrypicks = [ for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) diff --git a/dev/conductor/core/lib/src/proto/compile_proto.sh b/dev/conductor/core/lib/src/proto/compile_proto.sh index 4ef9281dbd..26f049936a 100755 --- a/dev/conductor/core/lib/src/proto/compile_proto.sh +++ b/dev/conductor/core/lib/src/proto/compile_proto.sh @@ -3,11 +3,25 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -# //flutter/dev/tools/lib/proto -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +set -euo pipefail + +function follow_links() ( + cd -P "$(dirname -- "$1")" + file="$PWD/$(basename -- "$1")" + while [[ -h "$file" ]]; do + cd -P "$(dirname -- "$file")" + file="$(readlink -- "$file")" + cd -P "$(dirname -- "$file")" + file="$PWD/$(basename -- "$file")" + done + echo "$file" +) + +PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")" +DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" # Ensure dart-sdk is cached -"$DIR/../../../../bin/dart" --version +"$DIR/../../../../../../bin/dart" --version if ! type protoc >/dev/null 2>&1; then PROTOC_LINK='https://grpc.io/docs/protoc-installation/' @@ -22,13 +36,13 @@ if ! type dart >/dev/null 2>&1; then fi # Use null-safe protoc_plugin -dart pub global activate protoc_plugin 20.0.0 +dart pub global activate protoc_plugin 21.1.2 protoc --dart_out="$DIR" --proto_path="$DIR" "$DIR/conductor_state.proto" for SOURCE_FILE in $(ls "$DIR"/*.pb*.dart); do # Format in place file - dart format --output=write --line-length 120 "$SOURCE_FILE" + dart format --output=write "$SOURCE_FILE" # Create temp copy with the license header prepended cp "$DIR/license_header.txt" "${SOURCE_FILE}.tmp" diff --git a/dev/conductor/core/lib/src/proto/conductor_state.pb.dart b/dev/conductor/core/lib/src/proto/conductor_state.pb.dart index 1a5bc623ff..55d3ae9fcf 100644 --- a/dev/conductor/core/lib/src/proto/conductor_state.pb.dart +++ b/dev/conductor/core/lib/src/proto/conductor_state.pb.dart @@ -8,7 +8,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -22,8 +22,21 @@ import 'conductor_state.pbenum.dart'; export 'conductor_state.pbenum.dart'; +/// A git remote class Remote extends $pb.GeneratedMessage { - factory Remote() => create(); + factory Remote({ + $core.String? name, + $core.String? url, + }) { + final $result = create(); + if (name != null) { + $result.name = name; + } + if (url != null) { + $result.url = url; + } + return $result; + } Remote._() : super(); factory Remote.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => @@ -86,7 +99,23 @@ class Remote extends $pb.GeneratedMessage { } class Cherrypick extends $pb.GeneratedMessage { - factory Cherrypick() => create(); + factory Cherrypick({ + $core.String? trunkRevision, + $core.String? appliedRevision, + CherrypickState? state, + }) { + final $result = create(); + if (trunkRevision != null) { + $result.trunkRevision = trunkRevision; + } + if (appliedRevision != null) { + $result.appliedRevision = appliedRevision; + } + if (state != null) { + $result.state = state; + } + return $result; + } Cherrypick._() : super(); factory Cherrypick.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => @@ -127,6 +156,7 @@ class Cherrypick extends $pb.GeneratedMessage { _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Cherrypick? _defaultInstance; + /// The revision on trunk to cherrypick. @$pb.TagNumber(1) $core.String get trunkRevision => $_getSZ(0); @$pb.TagNumber(1) @@ -139,6 +169,7 @@ class Cherrypick extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearTrunkRevision() => clearField(1); + /// Once applied, the actual commit revision of the cherrypick. @$pb.TagNumber(2) $core.String get appliedRevision => $_getSZ(1); @$pb.TagNumber(2) @@ -165,7 +196,47 @@ class Cherrypick extends $pb.GeneratedMessage { } class Repository extends $pb.GeneratedMessage { - factory Repository() => create(); + factory Repository({ + $core.String? candidateBranch, + $core.String? startingGitHead, + $core.String? currentGitHead, + $core.String? checkoutPath, + Remote? upstream, + Remote? mirror, + $core.Iterable? cherrypicks, + $core.String? dartRevision, + $core.String? workingBranch, + }) { + final $result = create(); + if (candidateBranch != null) { + $result.candidateBranch = candidateBranch; + } + if (startingGitHead != null) { + $result.startingGitHead = startingGitHead; + } + if (currentGitHead != null) { + $result.currentGitHead = currentGitHead; + } + if (checkoutPath != null) { + $result.checkoutPath = checkoutPath; + } + if (upstream != null) { + $result.upstream = upstream; + } + if (mirror != null) { + $result.mirror = mirror; + } + if (cherrypicks != null) { + $result.cherrypicks.addAll(cherrypicks); + } + if (dartRevision != null) { + $result.dartRevision = dartRevision; + } + if (workingBranch != null) { + $result.workingBranch = workingBranch; + } + return $result; + } Repository._() : super(); factory Repository.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => @@ -210,6 +281,9 @@ class Repository extends $pb.GeneratedMessage { _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static Repository? _defaultInstance; + /// The development git branch the release is based on. + /// + /// Must be of the form /flutter-(\d+)\.(\d+)-candidate\.(\d+)/ @$pb.TagNumber(1) $core.String get candidateBranch => $_getSZ(0); @$pb.TagNumber(1) @@ -222,6 +296,7 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearCandidateBranch() => clearField(1); + /// The commit hash at the tip of the branch before cherrypicks were applied. @$pb.TagNumber(2) $core.String get startingGitHead => $_getSZ(1); @$pb.TagNumber(2) @@ -234,6 +309,8 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(2) void clearStartingGitHead() => clearField(2); + /// The difference in commits between this and [startingGitHead] is the number + /// of cherrypicks that have been currently applied. @$pb.TagNumber(3) $core.String get currentGitHead => $_getSZ(2); @$pb.TagNumber(3) @@ -246,6 +323,7 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(3) void clearCurrentGitHead() => clearField(3); + /// Path to the git checkout on local disk. @$pb.TagNumber(4) $core.String get checkoutPath => $_getSZ(3); @$pb.TagNumber(4) @@ -258,6 +336,7 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(4) void clearCheckoutPath() => clearField(4); + /// The remote commits will be fetched from. @$pb.TagNumber(5) Remote get upstream => $_getN(4); @$pb.TagNumber(5) @@ -272,6 +351,9 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(5) Remote ensureUpstream() => $_ensure(4); + /// The remote cherrypicks will be pushed to create a Pull Request. + /// + /// This should be a mirror owned by the user conducting the release. @$pb.TagNumber(6) Remote get mirror => $_getN(5); @$pb.TagNumber(6) @@ -286,9 +368,11 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(6) Remote ensureMirror() => $_ensure(5); + /// Desired cherrypicks. @$pb.TagNumber(7) $core.List get cherrypicks => $_getList(6); + /// For the repository that has a dart_revision in a DEPS file. @$pb.TagNumber(8) $core.String get dartRevision => $_getSZ(7); @$pb.TagNumber(8) @@ -301,6 +385,10 @@ class Repository extends $pb.GeneratedMessage { @$pb.TagNumber(8) void clearDartRevision() => clearField(8); + /// Name of local and remote branch for applying cherrypicks. + /// + /// When the pull request is merged, all commits here will be squashed to a + /// single commit on the [candidateBranch]. @$pb.TagNumber(9) $core.String get workingBranch => $_getSZ(8); @$pb.TagNumber(9) @@ -315,7 +403,51 @@ class Repository extends $pb.GeneratedMessage { } class ConductorState extends $pb.GeneratedMessage { - factory ConductorState() => create(); + factory ConductorState({ + $core.String? releaseChannel, + $core.String? releaseVersion, + Repository? engine, + Repository? framework, + $fixnum.Int64? createdDate, + $fixnum.Int64? lastUpdatedDate, + $core.Iterable<$core.String>? logs, + ReleasePhase? currentPhase, + $core.String? conductorVersion, + ReleaseType? releaseType, + }) { + final $result = create(); + if (releaseChannel != null) { + $result.releaseChannel = releaseChannel; + } + if (releaseVersion != null) { + $result.releaseVersion = releaseVersion; + } + if (engine != null) { + $result.engine = engine; + } + if (framework != null) { + $result.framework = framework; + } + if (createdDate != null) { + $result.createdDate = createdDate; + } + if (lastUpdatedDate != null) { + $result.lastUpdatedDate = lastUpdatedDate; + } + if (logs != null) { + $result.logs.addAll(logs); + } + if (currentPhase != null) { + $result.currentPhase = currentPhase; + } + if (conductorVersion != null) { + $result.conductorVersion = conductorVersion; + } + if (releaseType != null) { + $result.releaseType = releaseType; + } + return $result; + } ConductorState._() : super(); factory ConductorState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => @@ -336,7 +468,7 @@ class ConductorState extends $pb.GeneratedMessage { ..pPS(8, _omitFieldNames ? '' : 'logs') ..e(9, _omitFieldNames ? '' : 'currentPhase', $pb.PbFieldType.OE, protoName: 'currentPhase', - defaultOrMaker: ReleasePhase.APPLY_ENGINE_CHERRYPICKS, + defaultOrMaker: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS, valueOf: ReleasePhase.valueOf, enumValues: ReleasePhase.values) ..aOS(10, _omitFieldNames ? '' : 'conductorVersion', protoName: 'conductorVersion') @@ -368,6 +500,7 @@ class ConductorState extends $pb.GeneratedMessage { _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); static ConductorState? _defaultInstance; + /// One of 'stable', 'beta', or 'dev' @$pb.TagNumber(1) $core.String get releaseChannel => $_getSZ(0); @$pb.TagNumber(1) @@ -380,6 +513,7 @@ class ConductorState extends $pb.GeneratedMessage { @$pb.TagNumber(1) void clearReleaseChannel() => clearField(1); + /// The name of the release. @$pb.TagNumber(2) $core.String get releaseVersion => $_getSZ(1); @$pb.TagNumber(2) @@ -447,6 +581,7 @@ class ConductorState extends $pb.GeneratedMessage { @$pb.TagNumber(8) $core.List<$core.String> get logs => $_getList(6); + /// The current [ReleasePhase] that has yet to be completed. @$pb.TagNumber(9) ReleasePhase get currentPhase => $_getN(7); @$pb.TagNumber(9) @@ -459,6 +594,8 @@ class ConductorState extends $pb.GeneratedMessage { @$pb.TagNumber(9) void clearCurrentPhase() => clearField(9); + /// A string used to validate that the current conductor is the same version + /// that created the [ConductorState] object. @$pb.TagNumber(10) $core.String get conductorVersion => $_getSZ(8); @$pb.TagNumber(10) diff --git a/dev/conductor/core/lib/src/proto/conductor_state.pbenum.dart b/dev/conductor/core/lib/src/proto/conductor_state.pbenum.dart index 62b95f75de..f40a50ef2e 100644 --- a/dev/conductor/core/lib/src/proto/conductor_state.pbenum.dart +++ b/dev/conductor/core/lib/src/proto/conductor_state.pbenum.dart @@ -8,7 +8,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -18,22 +18,16 @@ import 'dart:core' as $core; import 'package:protobuf/protobuf.dart' as $pb; class ReleasePhase extends $pb.ProtobufEnum { - static const ReleasePhase APPLY_ENGINE_CHERRYPICKS = - ReleasePhase._(0, _omitEnumNames ? '' : 'APPLY_ENGINE_CHERRYPICKS'); - static const ReleasePhase VERIFY_ENGINE_CI = - ReleasePhase._(1, _omitEnumNames ? '' : 'VERIFY_ENGINE_CI'); static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS = - ReleasePhase._(2, _omitEnumNames ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS'); + ReleasePhase._(0, _omitEnumNames ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS'); static const ReleasePhase PUBLISH_VERSION = - ReleasePhase._(3, _omitEnumNames ? '' : 'PUBLISH_VERSION'); + ReleasePhase._(1, _omitEnumNames ? '' : 'PUBLISH_VERSION'); static const ReleasePhase VERIFY_RELEASE = - ReleasePhase._(5, _omitEnumNames ? '' : 'VERIFY_RELEASE'); + ReleasePhase._(2, _omitEnumNames ? '' : 'VERIFY_RELEASE'); static const ReleasePhase RELEASE_COMPLETED = - ReleasePhase._(6, _omitEnumNames ? '' : 'RELEASE_COMPLETED'); + ReleasePhase._(3, _omitEnumNames ? '' : 'RELEASE_COMPLETED'); static const $core.List values = [ - APPLY_ENGINE_CHERRYPICKS, - VERIFY_ENGINE_CI, APPLY_FRAMEWORK_CHERRYPICKS, PUBLISH_VERSION, VERIFY_RELEASE, @@ -67,6 +61,9 @@ class CherrypickState extends $pb.ProtobufEnum { const CherrypickState._($core.int v, $core.String n) : super(v, n); } +/// The type of release that is being created. +/// +/// This determines how the version will be calculated. class ReleaseType extends $pb.ProtobufEnum { static const ReleaseType STABLE_INITIAL = ReleaseType._(0, _omitEnumNames ? '' : 'STABLE_INITIAL'); diff --git a/dev/conductor/core/lib/src/proto/conductor_state.pbjson.dart b/dev/conductor/core/lib/src/proto/conductor_state.pbjson.dart index 6f2b44428b..30ce3a32ed 100644 --- a/dev/conductor/core/lib/src/proto/conductor_state.pbjson.dart +++ b/dev/conductor/core/lib/src/proto/conductor_state.pbjson.dart @@ -8,7 +8,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields // ignore_for_file: unnecessary_import, unnecessary_this, unused_import @@ -21,24 +21,17 @@ import 'dart:typed_data' as $typed_data; const ReleasePhase$json = { '1': 'ReleasePhase', '2': [ - {'1': 'APPLY_ENGINE_CHERRYPICKS', '2': 0}, - {'1': 'VERIFY_ENGINE_CI', '2': 1}, - {'1': 'APPLY_FRAMEWORK_CHERRYPICKS', '2': 2}, - {'1': 'PUBLISH_VERSION', '2': 3}, - {'1': 'VERIFY_RELEASE', '2': 5}, - {'1': 'RELEASE_COMPLETED', '2': 6}, - ], - '4': [ - {'1': 4, '2': 4}, + {'1': 'APPLY_FRAMEWORK_CHERRYPICKS', '2': 0}, + {'1': 'PUBLISH_VERSION', '2': 1}, + {'1': 'VERIFY_RELEASE', '2': 2}, + {'1': 'RELEASE_COMPLETED', '2': 3}, ], }; /// Descriptor for `ReleasePhase`. Decode as a `google.protobuf.EnumDescriptorProto`. final $typed_data.Uint8List releasePhaseDescriptor = $convert - .base64Decode('CgxSZWxlYXNlUGhhc2USHAoYQVBQTFlfRU5HSU5FX0NIRVJSWVBJQ0tTEAASFAoQVkVSSUZZX0' - 'VOR0lORV9DSRABEh8KG0FQUExZX0ZSQU1FV09SS19DSEVSUllQSUNLUxACEhMKD1BVQkxJU0hf' - 'VkVSU0lPThADEhIKDlZFUklGWV9SRUxFQVNFEAUSFQoRUkVMRUFTRV9DT01QTEVURUQQBiIECA' - 'QQBA=='); + .base64Decode('CgxSZWxlYXNlUGhhc2USHwobQVBQTFlfRlJBTUVXT1JLX0NIRVJSWVBJQ0tTEAASEwoPUFVCTE' + 'lTSF9WRVJTSU9OEAESEgoOVkVSSUZZX1JFTEVBU0UQAhIVChFSRUxFQVNFX0NPTVBMRVRFRBAD'); @$core.Deprecated('Use cherrypickStateDescriptor instead') const CherrypickState$json = { diff --git a/dev/conductor/core/lib/src/proto/conductor_state.pbserver.dart b/dev/conductor/core/lib/src/proto/conductor_state.pbserver.dart index cdb739fa56..47ea59ca60 100644 --- a/dev/conductor/core/lib/src/proto/conductor_state.pbserver.dart +++ b/dev/conductor/core/lib/src/proto/conductor_state.pbserver.dart @@ -8,7 +8,7 @@ // // @dart = 2.12 -// ignore_for_file: annotate_overrides, camel_case_types +// ignore_for_file: annotate_overrides, camel_case_types, comment_references // ignore_for_file: constant_identifier_names // ignore_for_file: deprecated_member_use_from_same_package, library_prefixes // ignore_for_file: non_constant_identifier_names, prefer_final_fields diff --git a/dev/conductor/core/lib/src/proto/conductor_state.proto b/dev/conductor/core/lib/src/proto/conductor_state.proto index 26fd9c92cc..a37ff917f8 100644 --- a/dev/conductor/core/lib/src/proto/conductor_state.proto +++ b/dev/conductor/core/lib/src/proto/conductor_state.proto @@ -9,24 +9,16 @@ message Remote { } enum ReleasePhase { - // Release was started with `conductor start` and repositories cloned. - APPLY_ENGINE_CHERRYPICKS = 0; - - // Verify engine CI is green before opening framework PR. - VERIFY_ENGINE_CI = 1; - - APPLY_FRAMEWORK_CHERRYPICKS = 2; + APPLY_FRAMEWORK_CHERRYPICKS = 0; // Git tag applied to framework RC branch HEAD and pushed upstream. - PUBLISH_VERSION = 3; - - reserved 4; // Formerly PUBLISH_CHANNEL, merged into PUBLISH_VERSION. + PUBLISH_VERSION = 1; // Package artifacts verified to exist on cloud storage. - VERIFY_RELEASE = 5; + VERIFY_RELEASE = 2; // There is no further work to be done. - RELEASE_COMPLETED = 6; + RELEASE_COMPLETED = 3; } enum CherrypickState { @@ -100,7 +92,7 @@ message Repository { // Desired cherrypicks. repeated Cherrypick cherrypicks = 7; - // Only for engine repositories. + // For the repository that has a dart_revision in a DEPS file. string dartRevision = 8; // Name of local and remote branch for applying cherrypicks. diff --git a/dev/conductor/core/lib/src/repository.dart b/dev/conductor/core/lib/src/repository.dart index 466d5c4769..037cada1df 100644 --- a/dev/conductor/core/lib/src/repository.dart +++ b/dev/conductor/core/lib/src/repository.dart @@ -683,33 +683,9 @@ class FrameworkRepository extends Repository { return true; } - /// Update this framework's engine version file. - /// - /// Returns [true] if the version file was updated and a commit is needed. - Future updateEngineRevision( - String newEngine, { - @visibleForTesting File? engineVersionFile, - }) async { - assert(newEngine.isNotEmpty); - engineVersionFile ??= (await checkoutDirectory) - .childDirectory('bin') - .childDirectory('internal') - .childFile('engine.version'); - assert(engineVersionFile.existsSync()); - final String oldEngine = engineVersionFile.readAsStringSync(); - if (oldEngine.trim() == newEngine.trim()) { - stdio.printTrace( - 'Tried to update the engine revision but version file is already up to date at: $newEngine', - ); - return false; - } - stdio.printStatus('Updating engine revision from $oldEngine to $newEngine'); - engineVersionFile.writeAsStringSync( - // Version files have trailing newlines - '${newEngine.trim()}\n', - flush: true, - ); - return true; + /// Update the `dart_revision` entry in the DEPS file. + Future updateDartRevision(String newRevision, {@visibleForTesting File? depsFile}) async { + return _updateDartRevision(this, newRevision, depsFile: depsFile); } } @@ -770,63 +746,45 @@ class HostFrameworkRepository extends FrameworkRepository { } } -class EngineRepository extends Repository { - EngineRepository( - this.checkouts, { - super.name = 'engine', - String super.initialRef = EngineRepository.defaultBranch, - super.upstreamRemote = const Remote.upstream(EngineRepository.defaultUpstream), - super.localUpstream, - super.previousCheckoutLocation, - super.mirrorRemote, - List? additionalRequiredLocalBranches, - }) : super( - fileSystem: checkouts.fileSystem, - parentDirectory: checkouts.directory, - platform: checkouts.platform, - processManager: checkouts.processManager, - stdio: checkouts.stdio, - requiredLocalBranches: additionalRequiredLocalBranches ?? const [], - ); - - final Checkouts checkouts; - - static const String defaultUpstream = 'git@github.com:flutter/engine.git'; - static const String defaultBranch = 'main'; - - /// Update the `dart_revision` entry in the DEPS file. - Future updateDartRevision(String newRevision, {@visibleForTesting File? depsFile}) async { - assert(newRevision.length == 40); - depsFile ??= (await checkoutDirectory).childFile('DEPS'); - final String fileContent = depsFile.readAsStringSync(); - final RegExp dartPattern = RegExp("[ ]+'dart_revision': '([a-z0-9]{40})',"); - final Iterable allMatches = dartPattern.allMatches(fileContent); - if (allMatches.length != 1) { - throw ConductorException( - 'Unexpected content in the DEPS file at ${depsFile.path}\n' - 'Expected to find pattern ${dartPattern.pattern} 1 times, but got ' - '${allMatches.length}.', - ); - } - final String updatedFileContent = fileContent.replaceFirst( - dartPattern, - " 'dart_revision': '$newRevision',", - ); - - depsFile.writeAsStringSync(updatedFileContent, flush: true); - } - - @override - Future cloneRepository(String? cloneName) async { - assert(localUpstream); - cloneName ??= 'clone-of-$name'; - return EngineRepository( - checkouts, - name: cloneName, - upstreamRemote: Remote.upstream('file://${(await checkoutDirectory).path}/'), - ); - } -} +//class EngineRepository extends Repository { +// EngineRepository( +// this.checkouts, { +// super.name = 'engine', +// String super.initialRef = EngineRepository.defaultBranch, +// super.upstreamRemote = const Remote.upstream(EngineRepository.defaultUpstream), +// super.localUpstream, +// super.previousCheckoutLocation, +// super.mirrorRemote, +// List? additionalRequiredLocalBranches, +// }) : super( +// fileSystem: checkouts.fileSystem, +// parentDirectory: checkouts.directory, +// platform: checkouts.platform, +// processManager: checkouts.processManager, +// stdio: checkouts.stdio, +// requiredLocalBranches: additionalRequiredLocalBranches ?? const [], +// ); +// +// final Checkouts checkouts; +// +// static const String defaultUpstream = 'git@github.com:flutter/engine.git'; +// static const String defaultBranch = 'main'; +// +// /// Update the `dart_revision` entry in the DEPS file. +// Future updateDartRevision(String newRevision, {@visibleForTesting File? depsFile}) => +// _updateDartRevision(this, newRevision, depsFile: depsFile); +// +// @override +// Future cloneRepository(String? cloneName) async { +// assert(localUpstream); +// cloneName ??= 'clone-of-$name'; +// return EngineRepository( +// checkouts, +// name: cloneName, +// upstreamRemote: Remote.upstream('file://${(await checkoutDirectory).path}/'), +// ); +// } +//} /// An enum of all the repositories that the Conductor supports. enum RepositoryType { framework, engine } @@ -851,3 +809,28 @@ class Checkouts { final ProcessManager processManager; final Stdio stdio; } + +Future _updateDartRevision( + Repository repo, + String newRevision, { + @visibleForTesting File? depsFile, +}) async { + assert(newRevision.length == 40); + depsFile ??= (await repo.checkoutDirectory).childFile('DEPS'); + final String fileContent = depsFile.readAsStringSync(); + final RegExp dartPattern = RegExp("[ ]+'dart_revision': '([a-z0-9]{40})',"); + final Iterable allMatches = dartPattern.allMatches(fileContent); + if (allMatches.length != 1) { + throw ConductorException( + 'Unexpected content in the DEPS file at ${depsFile.path}\n' + 'Expected to find pattern ${dartPattern.pattern} 1 times, but got ' + '${allMatches.length}.', + ); + } + final String updatedFileContent = fileContent.replaceFirst( + dartPattern, + " 'dart_revision': '$newRevision',", + ); + + depsFile.writeAsStringSync(updatedFileContent, flush: true); +} diff --git a/dev/conductor/core/lib/src/start.dart b/dev/conductor/core/lib/src/start.dart index ca4254e9ef..495092c1bb 100644 --- a/dev/conductor/core/lib/src/start.dart +++ b/dev/conductor/core/lib/src/start.dart @@ -65,12 +65,6 @@ class StartCommand extends Command { help: 'Configurable Framework repo upstream remote. Primarily for testing.', hide: true, ); - argParser.addOption( - kEngineUpstreamOption, - defaultsTo: EngineRepository.defaultUpstream, - help: 'Configurable Engine repo upstream remote. Primarily for testing.', - hide: true, - ); argParser.addOption( kStateOption, defaultsTo: defaultPath, @@ -119,9 +113,6 @@ class StartCommand extends Command { allowNull: true, ) ?? 'git@github.com:$githubUsername/flutter.git'; - final String engineUpstream = - getValueFromEnvOrArgs(kEngineUpstreamOption, argumentResults, platform.environment)!; - final String engineMirror = 'git@github.com:$githubUsername/engine.git'; final String candidateBranch = getValueFromEnvOrArgs(kCandidateOption, argumentResults, platform.environment)!; final String releaseChannel = @@ -151,8 +142,6 @@ class StartCommand extends Command { candidateBranch: candidateBranch, checkouts: checkouts, dartRevision: dartRevision, - engineMirror: engineMirror, - engineUpstream: engineUpstream, conductorVersion: conductorVersion, frameworkMirror: frameworkMirror, frameworkUpstream: frameworkUpstream, @@ -183,8 +172,6 @@ class StartContext extends Context { StartContext({ required this.candidateBranch, required this.dartRevision, - required this.engineMirror, - required this.engineUpstream, required this.frameworkMirror, required this.frameworkUpstream, required this.conductorVersion, @@ -196,12 +183,6 @@ class StartContext extends Context { this.force = false, this.versionOverride, }) : git = Git(processManager), - engine = EngineRepository( - checkouts, - initialRef: 'upstream/$candidateBranch', - upstreamRemote: Remote.upstream(engineUpstream), - mirrorRemote: Remote.mirror(engineMirror), - ), framework = FrameworkRepository( checkouts, initialRef: 'upstream/$candidateBranch', @@ -211,8 +192,6 @@ class StartContext extends Context { final String candidateBranch; final String? dartRevision; - final String engineMirror; - final String engineUpstream; final String frameworkMirror; final String frameworkUpstream; final String conductorVersion; @@ -225,7 +204,6 @@ class StartContext extends Context { /// If validations should be overridden. final bool force; - final EngineRepository engine; final FrameworkRepository framework; /// Determine which part of the version to increment in the next release. @@ -272,71 +250,8 @@ class StartContext extends Context { // Create a new branch so that we don't accidentally push to upstream // candidateBranch. final String workingBranchName = 'cherrypicks-$candidateBranch'; - await engine.newBranch(workingBranchName); - - if (dartRevision != null && dartRevision!.isNotEmpty) { - await engine.updateDartRevision(dartRevision!); - await engine.commit('Update Dart SDK to $dartRevision', addFirst: true); - } - - final String engineHead = await engine.reverseParse('HEAD'); - state.engine = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..workingBranch = workingBranchName - ..startingGitHead = engineHead - ..currentGitHead = engineHead - ..checkoutPath = (await engine.checkoutDirectory).path - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = engine.upstreamRemote.url) - ..mirror = - (pb.Remote.create() - ..name = 'mirror' - ..url = engine.mirrorRemote!.url)); - if (dartRevision != null && dartRevision!.isNotEmpty) { - state.engine.dartRevision = dartRevision!; - } - - await framework.newBranch(workingBranchName); - - // Get framework version - final Version lastVersion = Version.fromString( - await framework.getFullTag(framework.upstreamRemote.name, candidateBranch, exact: false), - ); final String frameworkHead = await framework.reverseParse('HEAD'); - final String branchPoint = await framework.branchPoint( - '${framework.upstreamRemote.name}/$candidateBranch', - '${framework.upstreamRemote.name}/${FrameworkRepository.defaultBranch}', - ); - final bool atBranchPoint = branchPoint == frameworkHead; - - final ReleaseType releaseType = computeReleaseType(lastVersion, atBranchPoint); - state.releaseType = releaseType; - - try { - lastVersion.ensureValid(candidateBranch, releaseType); - } on ConductorException catch (e) { - // Let the user know, but resume execution - stdio.printError(e.message); - } - - Version nextVersion; - if (versionOverride != null) { - nextVersion = versionOverride!; - } else { - nextVersion = calculateNextVersion(lastVersion, releaseType); - nextVersion = await ensureBranchPointTagged( - branchPoint: branchPoint, - requestedVersion: nextVersion, - framework: framework, - ); - } - - state.releaseVersion = nextVersion.toString(); - state.framework = (pb.Repository.create() ..candidateBranch = candidateBranch @@ -353,7 +268,46 @@ class StartContext extends Context { ..name = 'mirror' ..url = framework.mirrorRemote!.url)); - state.currentPhase = ReleasePhase.APPLY_ENGINE_CHERRYPICKS; + if (dartRevision != null && dartRevision!.isNotEmpty) { + // In the monorepo, the DEPS file is in flutter/flutter + state.framework.dartRevision = dartRevision!; + } + + // Get framework version + final Version lastVersion = Version.fromString( + await framework.getFullTag(framework.upstreamRemote.name, candidateBranch, exact: false), + ); + + final String branchPoint = await framework.branchPoint( + '${framework.upstreamRemote.name}/$candidateBranch', + '${framework.upstreamRemote.name}/${FrameworkRepository.defaultBranch}', + ); + final bool atBranchPoint = branchPoint == frameworkHead; + + final ReleaseType releaseType = computeReleaseType(lastVersion, atBranchPoint); + state.releaseType = releaseType; + + Version nextVersion; + if (versionOverride != null) { + nextVersion = versionOverride!; + } else { + nextVersion = calculateNextVersion(lastVersion, releaseType); + nextVersion = await ensureBranchPointTagged( + branchPoint: branchPoint, + requestedVersion: nextVersion, + framework: framework, + ); + } + + await framework.newBranch(workingBranchName); + if (dartRevision != null && dartRevision!.isNotEmpty) { + await framework.updateDartRevision(dartRevision!); + await framework.commit('Update Dart SDK to $dartRevision', addFirst: true); + } + + state.releaseVersion = nextVersion.toString(); + + state.currentPhase = ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS; state.conductorVersion = conductorVersion; diff --git a/dev/conductor/core/lib/src/state.dart b/dev/conductor/core/lib/src/state.dart index e823fb186f..f9853c7844 100644 --- a/dev/conductor/core/lib/src/state.dart +++ b/dev/conductor/core/lib/src/state.dart @@ -82,26 +82,6 @@ String presentState(pb.ConductorState state) { buffer.writeln( 'Last updated at: ${DateTime.fromMillisecondsSinceEpoch(state.lastUpdatedDate.toInt())}', ); - buffer.writeln(); - buffer.writeln('Engine Repo'); - buffer.writeln('\tCandidate branch: ${state.engine.candidateBranch}'); - buffer.writeln('\tStarting git HEAD: ${state.engine.startingGitHead}'); - buffer.writeln('\tCurrent git HEAD: ${state.engine.currentGitHead}'); - buffer.writeln('\tPath to checkout: ${state.engine.checkoutPath}'); - buffer.writeln( - '\tPost-submit LUCI dashboard: ${luciConsoleLink(state.engine.candidateBranch, 'engine')}', - ); - if (state.engine.cherrypicks.isNotEmpty) { - buffer.writeln('${state.engine.cherrypicks.length} Engine Cherrypicks:'); - for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) { - buffer.writeln('\t${cherrypick.trunkRevision} - ${cherrypick.state}'); - } - } else { - buffer.writeln('0 Engine cherrypicks.'); - } - if (state.engine.dartRevision.isNotEmpty) { - buffer.writeln('New Dart SDK revision: ${state.engine.dartRevision}'); - } buffer.writeln('Framework Repo'); buffer.writeln('\tCandidate branch: ${state.framework.candidateBranch}'); buffer.writeln('\tStarting git HEAD: ${state.framework.startingGitHead}'); @@ -118,6 +98,9 @@ String presentState(pb.ConductorState state) { } else { buffer.writeln('0 Framework cherrypicks.'); } + if (state.framework.dartRevision.isNotEmpty) { + buffer.writeln('New Dart SDK revision: ${state.engine.dartRevision}'); + } buffer.writeln(); if (state.currentPhase == ReleasePhase.VERIFY_RELEASE) { buffer.writeln( @@ -156,43 +139,6 @@ String presentPhases(ReleasePhase currentPhase) { String phaseInstructions(pb.ConductorState state) { switch (state.currentPhase) { - case ReleasePhase.APPLY_ENGINE_CHERRYPICKS: - if (state.engine.cherrypicks.isEmpty) { - return [ - 'There are no engine cherrypicks, so issue `conductor next` to continue', - 'to the next step.', - '\n', - '******************************************************', - '* Create a new entry in http://go/release-eng-retros *', - '******************************************************', - ].join('\n'); - } - return [ - 'You must now manually apply the following engine cherrypicks to the checkout', - 'at ${state.engine.checkoutPath} in order:', - for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) - '\t${cherrypick.trunkRevision}', - 'See ${globals.kReleaseDocumentationUrl} for more information.', - ].join('\n'); - case ReleasePhase.VERIFY_ENGINE_CI: - if (!requiresEnginePR(state)) { - return 'You must verify engine CI has passed: ' - '${luciConsoleLink(state.engine.candidateBranch, 'engine')}'; - } - // User's working branch was pushed to their mirror, but a PR needs to be - // opened on GitHub. - final String newPrLink = globals.getNewPrLink( - userName: githubAccount(state.engine.mirror.url), - repoName: 'engine', - state: state, - ); - final String consoleLink = luciConsoleLink(state.engine.candidateBranch, 'engine'); - return [ - 'Your working branch ${state.engine.workingBranch} was pushed to your mirror.', - 'You must now open a pull request at $newPrLink, verify pre-submit CI', - 'builds on your engine pull request are successful, merge your pull request,', - 'validate post-submit CI at $consoleLink.', - ].join('\n'); case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: final List outstandingCherrypicks = state.framework.cherrypicks.where((pb.Cherrypick cp) { @@ -280,8 +226,6 @@ ReleasePhase getNextPhase(ReleasePhase currentPhase) { switch (currentPhase) { case ReleasePhase.PUBLISH_VERSION: return ReleasePhase.VERIFY_RELEASE; - case ReleasePhase.APPLY_ENGINE_CHERRYPICKS: - case ReleasePhase.VERIFY_ENGINE_CI: case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: case ReleasePhase.VERIFY_RELEASE: case ReleasePhase.RELEASE_COMPLETED: diff --git a/dev/conductor/core/test/next_test.dart b/dev/conductor/core/test/next_test.dart index a960f2b890..42956db112 100644 --- a/dev/conductor/core/test/next_test.dart +++ b/dev/conductor/core/test/next_test.dart @@ -22,11 +22,8 @@ void main() { const String checkoutsParentDirectory = '$flutterRoot/dev/conductor'; const String candidateBranch = 'flutter-1.2-candidate.3'; const String workingBranch = 'cherrypicks-$candidateBranch'; - const String remoteUrl = 'https://github.com/org/repo.git'; const String revision1 = 'd3af60d18e01fcb36e0c0fa06c8502e4935ed095'; - const String revision2 = 'f99555c1e1392bf2a8135056b9446680c2af4ddf'; const String revision3 = 'ffffffffffffffffffffffffffffffffffffffff'; - const String revision4 = '280e23318a0d8341415c66aa32581352a421d974'; const String releaseVersion = '1.2.0-3.0.pre'; const String releaseChannel = 'beta'; const String stateFile = '/state-file.json'; @@ -73,226 +70,6 @@ void main() { ); }); - group('APPLY_ENGINE_CHERRYPICKS to VERIFY_ENGINE_CI', () { - test('confirms to stdout when all engine cherrypicks were auto-applied', () async { - stdio.stdin.add('n'); - final File ciYaml = fileSystem.file('$checkoutsParentDirectory/engine/.ci.yaml') - ..createSync(recursive: true); - _initializeCiYamlFile(ciYaml); - final FakeProcessManager processManager = FakeProcessManager.empty(); - final FakePlatform platform = FakePlatform( - environment: { - 'HOME': ['path', 'to', 'home'].join(localPathSeparator), - }, - operatingSystem: localOperatingSystem, - pathSeparator: localPathSeparator, - ); - final pb.ConductorState state = - (pb.ConductorState.create() - ..releaseChannel = releaseChannel - ..engine = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..cherrypicks.add( - pb.Cherrypick.create() - ..trunkRevision = 'abc123' - ..state = pb.CherrypickState.COMPLETED, - ) - ..checkoutPath = fileSystem.path.join(checkoutsParentDirectory, 'engine') - ..workingBranch = workingBranch - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = remoteUrl) - ..mirror = - (pb.Remote.create() - ..name = 'mirror' - ..url = remoteUrl)) - ..currentPhase = ReleasePhase.APPLY_ENGINE_CHERRYPICKS); - writeStateToFile(fileSystem.file(stateFile), state, []); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory) - ..createSync(recursive: true), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final CommandRunner runner = createRunner(checkouts: checkouts); - await runner.run(['next', '--$kStateOption', stateFile]); - - expect(processManager, hasNoRemainingExpectations); - expect( - stdio.stdout, - contains('All engine cherrypicks have been auto-applied by the conductor'), - ); - }); - - test('updates lastPhase if user responds yes', () async { - const String remoteUrl = 'https://github.com/org/repo.git'; - const String releaseChannel = 'beta'; - stdio.stdin.add('y'); - final FakeProcessManager processManager = FakeProcessManager.list([ - const FakeCommand(command: ['git', 'fetch', 'upstream']), - FakeCommand( - command: const ['git', 'checkout', workingBranch], - onRun: (_) { - final File file = fileSystem.file('$checkoutsParentDirectory/engine/.ci.yaml') - ..createSync(recursive: true); - _initializeCiYamlFile(file); - }, - ), - const FakeCommand( - command: ['git', 'push', 'mirror', 'HEAD:refs/heads/$workingBranch'], - ), - ]); - final FakePlatform platform = FakePlatform( - environment: { - 'HOME': ['path', 'to', 'home'].join(localPathSeparator), - }, - operatingSystem: localOperatingSystem, - pathSeparator: localPathSeparator, - ); - final pb.ConductorState state = - (pb.ConductorState.create() - ..currentPhase = ReleasePhase.APPLY_ENGINE_CHERRYPICKS - ..engine = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..checkoutPath = fileSystem.path.join(checkoutsParentDirectory, 'engine') - ..cherrypicks.add( - pb.Cherrypick.create() - ..trunkRevision = revision2 - ..state = pb.CherrypickState.PENDING, - ) - ..workingBranch = workingBranch - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = remoteUrl) - ..mirror = - (pb.Remote.create() - ..name = 'mirror' - ..url = remoteUrl)) - ..releaseChannel = releaseChannel - ..releaseVersion = releaseVersion); - writeStateToFile(fileSystem.file(stateFile), state, []); - // engine dir is expected to already exist - fileSystem - .directory(checkoutsParentDirectory) - .childDirectory('engine') - .createSync(recursive: true); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final CommandRunner runner = createRunner(checkouts: checkouts); - await runner.run(['next', '--$kStateOption', stateFile]); - - final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile)); - - expect(processManager, hasNoRemainingExpectations); - expect( - stdio.stdout, - contains( - 'You must now open a pull request at https://github.com/flutter/engine/compare/flutter-1.2-candidate.3...org:cherrypicks-flutter-1.2-candidate.3?expand=1', - ), - ); - expect( - stdio.stdout, - contains( - 'Are you ready to push your engine branch to the repository $remoteUrl? (y/n) ', - ), - ); - expect(finalState.currentPhase, ReleasePhase.VERIFY_ENGINE_CI); - expect(stdio.error, isEmpty); - }); - }); - - group('VERIFY_ENGINE_CI to APPLY_FRAMEWORK_CHERRYPICKS', () { - late pb.ConductorState state; - late FakeProcessManager processManager; - late FakePlatform platform; - - setUp(() { - state = - (pb.ConductorState.create() - ..releaseChannel = releaseChannel - ..engine = - (pb.Repository.create() - ..cherrypicks.add( - pb.Cherrypick.create() - ..trunkRevision = 'abc123' - ..state = pb.CherrypickState.PENDING, - ) - ..candidateBranch = 'flutter-1.0-candidate.0') - ..currentPhase = ReleasePhase.VERIFY_ENGINE_CI); - - processManager = FakeProcessManager.empty(); - - platform = FakePlatform( - environment: { - 'HOME': ['path', 'to', 'home'].join(localPathSeparator), - }, - operatingSystem: localOperatingSystem, - pathSeparator: localPathSeparator, - ); - }); - - test('does not update currentPhase if user responds no', () async { - stdio.stdin.add('n'); - writeStateToFile(fileSystem.file(stateFile), state, []); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory) - ..createSync(recursive: true), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final CommandRunner runner = createRunner(checkouts: checkouts); - await runner.run(['next', '--$kStateOption', stateFile]); - - final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile)); - - expect(processManager, hasNoRemainingExpectations); - expect(stdio.stdout, contains('Has CI passed for the engine PR?')); - expect(finalState.currentPhase, ReleasePhase.VERIFY_ENGINE_CI); - expect(stdio.error.contains('Aborting command.'), true); - }); - - test('updates currentPhase if user responds yes', () async { - stdio.stdin.add('y'); - final FakePlatform platform = FakePlatform( - environment: { - 'HOME': ['path', 'to', 'home'].join(localPathSeparator), - }, - operatingSystem: localOperatingSystem, - pathSeparator: localPathSeparator, - ); - writeStateToFile(fileSystem.file(stateFile), state, []); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory) - ..createSync(recursive: true), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final CommandRunner runner = createRunner(checkouts: checkouts); - await runner.run(['next', '--$kStateOption', stateFile]); - - final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile)); - - expect(processManager, hasNoRemainingExpectations); - expect(stdio.stdout, contains('Has CI passed for the engine PR?')); - expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS); - }); - }); - group('APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION', () { const String mirrorRemoteUrl = 'https://github.com/org/repo.git'; const String upstreamRemoteUrl = 'https://github.com/mirror/repo.git'; @@ -359,218 +136,9 @@ void main() { engineRevisionFile.writeAsStringSync(oldEngineVersion, flush: true); }); - test( - 'with no dart, engine or framework cherrypicks, updates engine revision if version mismatch', - () async { - stdio.stdin.add('n'); - processManager.addCommands([ - const FakeCommand(command: ['git', 'fetch', 'upstream']), - // we want merged upstream commit, not local working commit - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision1), - const FakeCommand(command: ['git', 'fetch', 'upstream']), - FakeCommand( - command: const ['git', 'checkout', workingBranch], - onRun: (_) { - final File file = fileSystem.file('$checkoutsParentDirectory/framework/.ci.yaml') - ..createSync(); - _initializeCiYamlFile(file); - }, - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM bin/internal/release-candidate-branch.version', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: [ - 'git', - 'commit', - '--message', - 'Create candidate branch version $candidateBranch for $releaseChannel', - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM bin/internal/engine.version', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: [ - 'git', - 'commit', - '--message', - 'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion', - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision4), - ]); - final pb.ConductorState state = - (pb.ConductorState.create() - ..releaseChannel = releaseChannel - ..releaseVersion = releaseVersion - ..currentPhase = ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS - ..framework = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..checkoutPath = frameworkCheckoutPath - ..mirror = - (pb.Remote.create() - ..name = 'mirror' - ..url = mirrorRemoteUrl) - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = upstreamRemoteUrl) - ..workingBranch = workingBranch) - ..engine = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..checkoutPath = engineCheckoutPath - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = engineUpstreamRemoteUrl) - ..currentGitHead = revision1)); - writeStateToFile(fileSystem.file(stateFile), state, []); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory) - ..createSync(recursive: true), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final CommandRunner runner = createRunner(checkouts: checkouts); - await runner.run(['next', '--$kStateOption', stateFile]); - - expect(processManager, hasNoRemainingExpectations); - expect( - stdio.stdout, - contains('release-candidate-branch.version containing $candidateBranch'), - ); - expect( - stdio.stdout, - contains('Updating engine revision from $oldEngineVersion to $revision1'), - ); - expect(stdio.stdout, contains('Are you ready to push your framework branch')); - }, - ); - - test( - 'with no engine cherrypicks but a dart revision update, updates engine revision', - () async { - stdio.stdin.add('n'); - processManager.addCommands([ - const FakeCommand(command: ['git', 'fetch', 'upstream']), - // we want merged upstream commit, not local working commit - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision1), - const FakeCommand(command: ['git', 'fetch', 'upstream']), - FakeCommand( - command: const ['git', 'checkout', workingBranch], - onRun: (_) { - final File file = fileSystem.file('$checkoutsParentDirectory/framework/.ci.yaml') - ..createSync(); - _initializeCiYamlFile(file); - }, - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM bin/internal/release-candidate-branch.version', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: [ - 'git', - 'commit', - '--message', - 'Create candidate branch version $candidateBranch for $releaseChannel', - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM bin/internal/engine.version', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: [ - 'git', - 'commit', - '--message', - 'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion', - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision4), - ]); - final pb.ConductorState state = - (pb.ConductorState.create() - ..releaseChannel = releaseChannel - ..releaseVersion = releaseVersion - ..currentPhase = ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS - ..framework = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..checkoutPath = frameworkCheckoutPath - ..mirror = - (pb.Remote.create() - ..name = 'mirror' - ..url = mirrorRemoteUrl) - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = upstreamRemoteUrl) - ..workingBranch = workingBranch) - ..engine = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..checkoutPath = engineCheckoutPath - ..upstream = - (pb.Remote.create() - ..name = 'upstream' - ..url = engineUpstreamRemoteUrl) - ..dartRevision = 'abc123')); - writeStateToFile(fileSystem.file(stateFile), state, []); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(checkoutsParentDirectory) - ..createSync(recursive: true), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - final CommandRunner runner = createRunner(checkouts: checkouts); - await runner.run(['next', '--$kStateOption', stateFile]); - - expect(processManager, hasNoRemainingExpectations); - expect( - stdio.stdout, - contains('release-candidate-branch.version containing $candidateBranch'), - ); - expect( - stdio.stdout, - contains('Updating engine revision from $oldEngineVersion to $revision1'), - ); - expect(stdio.stdout, contains('Are you ready to push your framework branch')); - }, - ); - test('does not update state.currentPhase if user responds no', () async { stdio.stdin.add('n'); processManager.addCommands([ - const FakeCommand(command: ['git', 'fetch', 'upstream']), - // we want merged upstream commit, not local working commit - FakeCommand( - command: const ['git', 'checkout', 'upstream/$candidateBranch'], - onRun: (_) { - final File file = fileSystem.file('$checkoutsParentDirectory/framework/.ci.yaml') - ..createSync(); - _initializeCiYamlFile(file); - }, - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision1), const FakeCommand(command: ['git', 'fetch', 'upstream']), const FakeCommand(command: ['git', 'checkout', workingBranch]), const FakeCommand( @@ -587,20 +155,6 @@ void main() { ], ), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM bin/internal/engine.version', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: [ - 'git', - 'commit', - '--message', - 'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion', - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision4), ]); writeStateToFile(fileSystem.file(stateFile), state, []); final Checkouts checkouts = Checkouts( @@ -616,6 +170,7 @@ void main() { final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile)); + expect(processManager, hasNoRemainingExpectations); expect( stdio.stdout, contains( @@ -631,11 +186,6 @@ void main() { processManager.addCommands([ // Engine repo const FakeCommand(command: ['git', 'fetch', 'upstream']), - // we want merged upstream commit, not local working commit - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision1), - // Framework repo - const FakeCommand(command: ['git', 'fetch', 'upstream']), FakeCommand( command: const ['git', 'checkout', workingBranch], onRun: (_) { @@ -658,20 +208,6 @@ void main() { ], ), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM bin/internal/engine.version', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: [ - 'git', - 'commit', - '--message', - 'Update Engine revision to $revision1 for $releaseChannel release $releaseVersion', - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision4), const FakeCommand( command: ['git', 'push', 'mirror', 'HEAD:refs/heads/$workingBranch'], ), @@ -691,10 +227,6 @@ void main() { final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile)); expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION); - expect( - stdio.stdout, - contains('Rolling new engine hash $revision1 to framework checkout...'), - ); expect(stdio.stdout, contains('There was 1 cherrypick that was not auto-applied')); expect( stdio.stdout, @@ -723,10 +255,6 @@ void main() { (pb.Repository.create() ..candidateBranch = candidateBranch ..upstream = (pb.Remote.create()..url = FrameworkRepository.defaultUpstream)) - ..engine = - (pb.Repository.create() - ..candidateBranch = candidateBranch - ..upstream = (pb.Remote.create()..url = EngineRepository.defaultUpstream)) ..releaseVersion = releaseVersion); }); diff --git a/dev/conductor/core/test/repository_test.dart b/dev/conductor/core/test/repository_test.dart index 8a0221cb77..6b448a343b 100644 --- a/dev/conductor/core/test/repository_test.dart +++ b/dev/conductor/core/test/repository_test.dart @@ -43,7 +43,7 @@ void main() { stdio: stdio, ); - final EngineRepository repo = EngineRepository(checkouts); + final FrameworkRepository repo = FrameworkRepository(checkouts); final File depsFile = fileSystem.file('/DEPS'); depsFile.writeAsStringSync(generateMockDeps(previousDartRevision)); await repo.updateDartRevision(nextDartRevision, depsFile: depsFile); @@ -62,7 +62,7 @@ void main() { stdio: stdio, ); - final EngineRepository repo = EngineRepository(checkouts); + final FrameworkRepository repo = FrameworkRepository(checkouts); final File depsFile = fileSystem.file('/DEPS'); depsFile.writeAsStringSync(''' vars = { @@ -85,11 +85,11 @@ vars = { '--origin', 'upstream', '--', - EngineRepository.defaultUpstream, - fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'engine'), + FrameworkRepository.defaultUpstream, + fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'framework'), ], ), - const FakeCommand(command: ['git', 'checkout', EngineRepository.defaultBranch]), + const FakeCommand(command: ['git', 'checkout', FrameworkRepository.defaultBranch]), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: commit1), const FakeCommand(command: ['git', 'status', '--porcelain']), const FakeCommand(command: ['git', 'commit', '--message', message]), @@ -104,7 +104,7 @@ vars = { stdio: stdio, ); - final EngineRepository repo = EngineRepository(checkouts); + final FrameworkRepository repo = FrameworkRepository(checkouts); expect( () async => repo.commit(message), throwsExceptionWith('Tried to commit with message $message but no changes were present'), @@ -123,11 +123,11 @@ vars = { '--origin', 'upstream', '--', - EngineRepository.defaultUpstream, - fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'engine'), + FrameworkRepository.defaultUpstream, + fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'framework'), ], ), - const FakeCommand(command: ['git', 'checkout', EngineRepository.defaultBranch]), + const FakeCommand(command: ['git', 'checkout', FrameworkRepository.defaultBranch]), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: commit1), const FakeCommand( command: ['git', 'status', '--porcelain'], @@ -145,7 +145,7 @@ vars = { stdio: stdio, ); - final EngineRepository repo = EngineRepository(checkouts); + final FrameworkRepository repo = FrameworkRepository(checkouts); await repo.commit(message); expect(processManager.hasRemainingExpectations, false); }); @@ -174,41 +174,6 @@ vars = { }, ); - test('updateEngineRevision() returns false if newCommit is the same as version file', () async { - const String commit1 = 'abc123'; - const String commit2 = 'def456'; - final File engineVersionFile = fileSystem.file('/engine.version')..writeAsStringSync(commit2); - processManager.addCommands([ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'framework'), - ], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: commit1), - ]); - - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(rootDir), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - - final FrameworkRepository repo = FrameworkRepository(checkouts); - final bool didUpdate = await repo.updateEngineRevision( - commit2, - engineVersionFile: engineVersionFile, - ); - expect(didUpdate, false); - }); - test( 'framework repo set as localUpstream ensures requiredLocalBranches exist locally', () async { @@ -262,49 +227,6 @@ vars = { }, ); - test('engine repo set as localUpstream ensures requiredLocalBranches exist locally', () async { - const String commit = 'deadbeef'; - const String candidateBranch = 'flutter-1.2-candidate.3'; - bool createdCandidateBranch = false; - processManager.addCommands([ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - EngineRepository.defaultUpstream, - fileSystem.path.join(rootDir, 'flutter_conductor_checkouts', 'engine'), - ], - ), - FakeCommand( - command: const ['git', 'checkout', candidateBranch, '--'], - onRun: (_) => createdCandidateBranch = true, - ), - const FakeCommand(command: ['git', 'checkout', EngineRepository.defaultBranch]), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: commit), - ]); - final Checkouts checkouts = Checkouts( - fileSystem: fileSystem, - parentDirectory: fileSystem.directory(rootDir), - platform: platform, - processManager: processManager, - stdio: stdio, - ); - - final Repository repo = EngineRepository( - checkouts, - additionalRequiredLocalBranches: [candidateBranch], - localUpstream: true, - ); - // call this so that repo.lazilyInitialize() is called. - await repo.checkoutDirectory; - - expect(processManager.hasRemainingExpectations, false); - expect(createdCandidateBranch, true); - }); - test('.listRemoteBranches() parses git output', () async { const String remoteName = 'mirror'; const String lsRemoteOutput = ''' @@ -326,11 +248,14 @@ Extraneous debug information that should be ignored. '--origin', 'upstream', '--', - EngineRepository.defaultUpstream, - '${rootDir}flutter_conductor_checkouts/engine', + FrameworkRepository.defaultUpstream, + '${rootDir}flutter_conductor_checkouts/framework', ], ), - FakeCommand(command: ['git', 'checkout', 'main']), + FakeCommand(command: ['git', 'checkout', 'stable', '--']), + FakeCommand(command: ['git', 'checkout', 'beta', '--']), + FakeCommand(command: ['git', 'checkout', 'master', '--']), + FakeCommand(command: ['git', 'checkout', 'master']), FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision), FakeCommand( command: ['git', 'ls-remote', '--heads', remoteName], @@ -345,7 +270,7 @@ Extraneous debug information that should be ignored. stdio: stdio, ); - final Repository repo = EngineRepository(checkouts, localUpstream: true); + final Repository repo = FrameworkRepository(checkouts, localUpstream: true); final List branchNames = await repo.listRemoteBranches(remoteName); expect( branchNames, diff --git a/dev/conductor/core/test/start_test.dart b/dev/conductor/core/test/start_test.dart index fbeb087304..22f04cae94 100644 --- a/dev/conductor/core/test/start_test.dart +++ b/dev/conductor/core/test/start_test.dart @@ -25,7 +25,6 @@ void main() { const String checkoutsParentDirectory = '$flutterRoot/dev/tools/'; const String githubUsername = 'user'; const String frameworkMirror = 'git@github.com:$githubUsername/flutter.git'; - const String engineMirror = 'git@github.com:$githubUsername/engine.git'; const String candidateBranch = 'flutter-1.2-candidate.3'; const String releaseChannel = 'beta'; const String revision = 'abcd1234'; @@ -117,7 +116,6 @@ void main() { expect(context.frameworkUpstream, FrameworkRepository.defaultUpstream); expect(context.frameworkMirror, contains(githubUsername)); expect(context.frameworkMirror, contains('/flutter.git')); - expect(context.engineUpstream, EngineRepository.defaultUpstream); }); test('overridden mirror', () async { @@ -202,50 +200,8 @@ void main() { const String nextVersion = '1.2.0-1.1.pre'; const String candidateBranch = 'flutter-1.2-candidate.1'; - final Directory engine = fileSystem - .directory(checkoutsParentDirectory) - .childDirectory('flutter_conductor_checkouts') - .childDirectory('engine'); - - final File depsFile = engine.childFile('DEPS'); - - final List engineCommands = [ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - EngineRepository.defaultUpstream, - engine.path, - ], - onRun: (_) { - // Create the DEPS file which the tool will update - engine.createSync(recursive: true); - depsFile.writeAsStringSync(generateMockDeps(previousDartRevision)); - }, - ), - const FakeCommand(command: ['git', 'remote', 'add', 'mirror', engineMirror]), - const FakeCommand(command: ['git', 'fetch', 'mirror']), - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM path/to/DEPS', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - ]; - - final List frameworkCommands = [ + final List commands = [ + // clone and rev-parse framework FakeCommand( command: [ 'git', @@ -260,14 +216,35 @@ void main() { 'framework', ), ], + onRun: (_) { + // ensure this is a monorepo checkout + fileSystem + .directory( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'engine', + ), + ) + .createSync(recursive: true); + fileSystem + .file( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'DEPS', + ), + ) + .writeAsStringSync(generateMockDeps(previousDartRevision)); + }, ), const FakeCommand(command: ['git', 'remote', 'add', 'mirror', frameworkMirror]), const FakeCommand(command: ['git', 'fetch', 'mirror']), const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( command: [ 'git', @@ -279,7 +256,7 @@ void main() { ], stdout: '$previousVersion-42-gabc123', ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), + //const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( command: ['git', 'merge-base', 'upstream/$candidateBranch', 'upstream/master'], stdout: branchPointRevision, @@ -288,11 +265,24 @@ void main() { const FakeCommand( command: ['git', 'describe', '--exact-match', '--tags', branchPointRevision], ), + + const FakeCommand( + command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], + ), + + // update DEPS + const FakeCommand( + command: ['git', 'status', '--porcelain'], + stdout: 'MM path/to/DEPS', + ), + const FakeCommand(command: ['git', 'add', '--all']), + const FakeCommand( + command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], + ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), ]; - final CommandRunner runner = createRunner( - commands: [...engineCommands, ...frameworkCommands], - ); + final CommandRunner runner = createRunner(commands: commands); final String stateFilePath = fileSystem.path.join( platform.environment['HOME']!, @@ -327,14 +317,10 @@ void main() { expect(state.isInitialized(), true); expect(state.releaseChannel, releaseChannel); expect(state.releaseVersion, nextVersion); - expect(state.engine.candidateBranch, candidateBranch); - expect(state.engine.startingGitHead, revision2); - expect(state.engine.dartRevision, nextDartRevision); - expect(state.engine.upstream.url, 'git@github.com:flutter/engine.git'); expect(state.framework.candidateBranch, candidateBranch); expect(state.framework.startingGitHead, revision3); expect(state.framework.upstream.url, 'git@github.com:flutter/flutter.git'); - expect(state.currentPhase, ReleasePhase.APPLY_ENGINE_CHERRYPICKS); + expect(state.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS); expect(state.conductorVersion, conductorVersion); }); @@ -348,50 +334,8 @@ void main() { const String candidateBranch = 'flutter-1.2-candidate.1'; const String versionOverride = '42.0.0-42.0.pre'; - final Directory engine = fileSystem - .directory(checkoutsParentDirectory) - .childDirectory('flutter_conductor_checkouts') - .childDirectory('engine'); - - final File depsFile = engine.childFile('DEPS'); - - final List engineCommands = [ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - EngineRepository.defaultUpstream, - engine.path, - ], - onRun: (_) { - // Create the DEPS file which the tool will update - engine.createSync(recursive: true); - depsFile.writeAsStringSync(generateMockDeps(previousDartRevision)); - }, - ), - const FakeCommand(command: ['git', 'remote', 'add', 'mirror', engineMirror]), - const FakeCommand(command: ['git', 'fetch', 'mirror']), - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM path/to/DEPS', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - ]; - - final List frameworkCommands = [ + final List commands = [ + // clone and rev-parse framework FakeCommand( command: [ 'git', @@ -406,14 +350,35 @@ void main() { 'framework', ), ], + onRun: (_) { + // ensure this is a monorepo checkout + fileSystem + .directory( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'engine', + ), + ) + .createSync(recursive: true); + fileSystem + .file( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'DEPS', + ), + ) + .writeAsStringSync(generateMockDeps(previousDartRevision)); + }, ), const FakeCommand(command: ['git', 'remote', 'add', 'mirror', frameworkMirror]), const FakeCommand(command: ['git', 'fetch', 'mirror']), const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( command: [ 'git', @@ -425,16 +390,26 @@ void main() { ], stdout: '$previousVersion-42-gabc123', ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( command: ['git', 'merge-base', 'upstream/$candidateBranch', 'upstream/master'], stdout: branchPointRevision, ), + + const FakeCommand( + command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], + ), + const FakeCommand( + command: ['git', 'status', '--porcelain'], + stdout: 'MM path/to/DEPS', + ), + const FakeCommand(command: ['git', 'add', '--all']), + const FakeCommand( + command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], + ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), ]; - final CommandRunner runner = createRunner( - commands: [...engineCommands, ...frameworkCommands], - ); + final CommandRunner runner = createRunner(commands: commands); final String stateFilePath = fileSystem.path.join( platform.environment['HOME']!, @@ -466,156 +441,6 @@ void main() { expect(state.releaseVersion, versionOverride); }); - test('logs to STDERR but does not fail on an unexpected candidate branch', () async { - stdio.stdin.add('y'); // accept prompt from ensureBranchPointTagged() - const String revision2 = 'def789'; - const String revision3 = '123abc'; - const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef'; - const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e'; - // This is significantly behind the candidate branch name - const String previousVersion = '0.9.0-1.0.pre'; - // This is what this release will be - const String nextVersion = '0.9.0-1.1.pre'; - - final Directory engine = fileSystem - .directory(checkoutsParentDirectory) - .childDirectory('flutter_conductor_checkouts') - .childDirectory('engine'); - - final File depsFile = engine.childFile('DEPS'); - - final List engineCommands = [ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - EngineRepository.defaultUpstream, - engine.path, - ], - onRun: (_) { - // Create the DEPS file which the tool will update - engine.createSync(recursive: true); - depsFile.writeAsStringSync(generateMockDeps(previousDartRevision)); - }, - ), - const FakeCommand(command: ['git', 'remote', 'add', 'mirror', engineMirror]), - const FakeCommand(command: ['git', 'fetch', 'mirror']), - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM path/to/DEPS', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - ]; - - final List frameworkCommands = [ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - FrameworkRepository.defaultUpstream, - fileSystem.path.join( - checkoutsParentDirectory, - 'flutter_conductor_checkouts', - 'framework', - ), - ], - ), - const FakeCommand(command: ['git', 'remote', 'add', 'mirror', frameworkMirror]), - const FakeCommand(command: ['git', 'fetch', 'mirror']), - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), - const FakeCommand( - command: [ - 'git', - 'describe', - '--match', - '*.*.*', - '--tags', - 'refs/remotes/upstream/$candidateBranch', - ], - stdout: '$previousVersion-42-gabc123', - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'merge-base', 'upstream/$candidateBranch', 'upstream/master'], - stdout: branchPointRevision, - ), - // check if commit is tagged, 0 exit code means it is tagged - const FakeCommand( - command: ['git', 'describe', '--exact-match', '--tags', branchPointRevision], - ), - ]; - - final CommandRunner runner = createRunner( - commands: [...engineCommands, ...frameworkCommands], - ); - - final String stateFilePath = fileSystem.path.join( - platform.environment['HOME']!, - kStateFileName, - ); - - await runner.run([ - 'start', - '--$kCandidateOption', - candidateBranch, - '--$kReleaseOption', - releaseChannel, - '--$kStateOption', - stateFilePath, - '--$kDartRevisionOption', - nextDartRevision, - '--$kGithubUsernameOption', - githubUsername, - ]); - - final File stateFile = fileSystem.file(stateFilePath); - - final pb.ConductorState state = pb.ConductorState(); - state.mergeFromProto3Json(jsonDecode(stateFile.readAsStringSync())); - - expect(stdio.error, isNot(contains('Tried to tag the branch point, however'))); - expect(processManager, hasNoRemainingExpectations); - expect(state.isInitialized(), true); - expect(state.releaseChannel, releaseChannel); - expect(state.releaseVersion, nextVersion); - expect(state.engine.candidateBranch, candidateBranch); - expect(state.engine.startingGitHead, revision2); - expect(state.engine.dartRevision, nextDartRevision); - expect(state.engine.upstream.url, 'git@github.com:flutter/engine.git'); - expect(state.framework.candidateBranch, candidateBranch); - expect(state.framework.startingGitHead, revision3); - expect(state.framework.upstream.url, 'git@github.com:flutter/flutter.git'); - expect(state.currentPhase, ReleasePhase.APPLY_ENGINE_CHERRYPICKS); - expect(state.conductorVersion, conductorVersion); - expect(state.releaseType, ReleaseType.BETA_HOTFIX); - expect( - stdio.error, - contains( - 'Parsed version $previousVersion.42 has a different x value than candidate branch $candidateBranch', - ), - ); - }); - test('can convert from dev style version to stable version', () async { const String revision2 = 'def789'; const String revision3 = '123abc'; @@ -624,50 +449,8 @@ void main() { const String previousVersion = '1.2.0-3.0.pre'; const String nextVersion = '1.2.0'; - final Directory engine = fileSystem - .directory(checkoutsParentDirectory) - .childDirectory('flutter_conductor_checkouts') - .childDirectory('engine'); - - final File depsFile = engine.childFile('DEPS'); - - final List engineCommands = [ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - EngineRepository.defaultUpstream, - engine.path, - ], - onRun: (_) { - // Create the DEPS file which the tool will update - engine.createSync(recursive: true); - depsFile.writeAsStringSync(generateMockDeps(previousDartRevision)); - }, - ), - const FakeCommand(command: ['git', 'remote', 'add', 'mirror', engineMirror]), - const FakeCommand(command: ['git', 'fetch', 'mirror']), - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM path/to/DEPS', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - ]; - - final List frameworkCommands = [ + final List commands = [ + // rev-parse framework FakeCommand( command: [ 'git', @@ -682,14 +465,35 @@ void main() { 'framework', ), ], + onRun: (_) { + // ensure this is a monorepo checkout + fileSystem + .directory( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'engine', + ), + ) + .createSync(recursive: true); + fileSystem + .file( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'DEPS', + ), + ) + .writeAsStringSync(generateMockDeps(previousDartRevision)); + }, ), const FakeCommand(command: ['git', 'remote', 'add', 'mirror', frameworkMirror]), const FakeCommand(command: ['git', 'fetch', 'mirror']), const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( command: [ 'git', @@ -701,7 +505,6 @@ void main() { ], stdout: '$previousVersion-42-gabc123', ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( command: ['git', 'merge-base', 'upstream/$candidateBranch', 'upstream/master'], stdout: branchPointRevision, @@ -710,11 +513,22 @@ void main() { const FakeCommand( command: ['git', 'describe', '--exact-match', '--tags', branchPointRevision], ), + + const FakeCommand( + command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], + ), + const FakeCommand( + command: ['git', 'status', '--porcelain'], + stdout: 'MM path/to/DEPS', + ), + const FakeCommand(command: ['git', 'add', '--all']), + const FakeCommand( + command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], + ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), ]; - final CommandRunner runner = createRunner( - commands: [...engineCommands, ...frameworkCommands], - ); + final CommandRunner runner = createRunner(commands: commands); final String stateFilePath = fileSystem.path.join( platform.environment['HOME']!, @@ -740,23 +554,21 @@ void main() { final pb.ConductorState state = pb.ConductorState(); state.mergeFromProto3Json(jsonDecode(stateFile.readAsStringSync())); - expect(processManager.hasRemainingExpectations, false); + expect(processManager, hasNoRemainingExpectations); expect(state.isInitialized(), true); expect(state.releaseChannel, 'stable'); expect(state.releaseVersion, nextVersion); - expect(state.engine.candidateBranch, candidateBranch); - expect(state.engine.startingGitHead, revision2); - expect(state.engine.dartRevision, nextDartRevision); + expect(state.framework.dartRevision, nextDartRevision); expect(state.framework.candidateBranch, candidateBranch); expect(state.framework.startingGitHead, revision3); - expect(state.currentPhase, ReleasePhase.APPLY_ENGINE_CHERRYPICKS); + expect(state.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS); expect(state.conductorVersion, conductorVersion); expect(state.releaseType, ReleaseType.STABLE_INITIAL); }); - test('StartContext gets engine and framework checkout directories after run', () async { + + test('StartContext gets framework checkout directory after run', () async { stdio.stdin.add('y'); const String revision2 = 'def789'; - const String revision3 = '123abc'; const String branchPointRevision = 'deadbeef'; const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef'; const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e'; @@ -764,55 +576,13 @@ void main() { // This is a git tag applied to the branch point, not an actual release const String branchPointTag = '1.2.0-3.0.pre'; - final Directory engine = fileSystem - .directory(checkoutsParentDirectory) - .childDirectory('flutter_conductor_checkouts') - .childDirectory('engine'); - final Directory framework = fileSystem .directory(checkoutsParentDirectory) .childDirectory('flutter_conductor_checkouts') .childDirectory('framework'); - final File depsFile = engine.childFile('DEPS'); - - final List engineCommands = [ - FakeCommand( - command: [ - 'git', - 'clone', - '--origin', - 'upstream', - '--', - EngineRepository.defaultUpstream, - engine.path, - ], - onRun: (_) { - // Create the DEPS file which the tool will update - engine.createSync(recursive: true); - depsFile.writeAsStringSync(generateMockDeps(previousDartRevision)); - }, - ), - const FakeCommand(command: ['git', 'remote', 'add', 'mirror', engineMirror]), - const FakeCommand(command: ['git', 'fetch', 'mirror']), - const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], - ), - const FakeCommand( - command: ['git', 'status', '--porcelain'], - stdout: 'MM path/to/DEPS', - ), - const FakeCommand(command: ['git', 'add', '--all']), - const FakeCommand( - command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], - ), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), - ]; - - final List frameworkCommands = [ + final List commands = [ + // checkout and rev-parse framework FakeCommand( command: [ 'git', @@ -823,13 +593,41 @@ void main() { FrameworkRepository.defaultUpstream, framework.path, ], + onRun: (_) { + // ensure this is a monorepo checkout + fileSystem + .directory( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'engine', + ), + ) + .createSync(recursive: true); + fileSystem + .file( + fileSystem.path.join( + checkoutsParentDirectory, + 'flutter_conductor_checkouts', + 'framework', + 'DEPS', + ), + ) + .writeAsStringSync(generateMockDeps(previousDartRevision)); + }, ), const FakeCommand(command: ['git', 'remote', 'add', 'mirror', frameworkMirror]), const FakeCommand(command: ['git', 'fetch', 'mirror']), const FakeCommand(command: ['git', 'checkout', 'upstream/$candidateBranch']), - const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision3), const FakeCommand( - command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], + command: ['git', 'rev-parse', 'HEAD'], + stdout: branchPointRevision, + ), + + const FakeCommand( + command: ['git', 'rev-parse', 'HEAD'], + stdout: branchPointRevision, ), const FakeCommand( command: [ @@ -842,11 +640,6 @@ void main() { ], stdout: '$previousVersion-42-gabc123', ), - // HEAD and branch point are same - const FakeCommand( - command: ['git', 'rev-parse', 'HEAD'], - stdout: branchPointRevision, - ), const FakeCommand( command: ['git', 'merge-base', 'upstream/$candidateBranch', 'upstream/master'], stdout: branchPointRevision, @@ -857,10 +650,25 @@ void main() { // non-zero exit code means branch point is NOT tagged exitCode: 128, ), + const FakeCommand(command: ['git', 'tag', branchPointTag, branchPointRevision]), const FakeCommand( command: ['git', 'push', FrameworkRepository.defaultUpstream, branchPointTag], ), + + const FakeCommand( + command: ['git', 'checkout', '-b', 'cherrypicks-$candidateBranch'], + ), + + const FakeCommand( + command: ['git', 'status', '--porcelain'], + stdout: 'MM path/to/DEPS', + ), + const FakeCommand(command: ['git', 'add', '--all']), + const FakeCommand( + command: ['git', 'commit', '--message', 'Update Dart SDK to $nextDartRevision'], + ), + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: revision2), ]; final String operatingSystem = const LocalPlatform().operatingSystem; @@ -876,10 +684,7 @@ void main() { ); final File stateFile = fileSystem.file(stateFilePath); - processManager = FakeProcessManager.list([ - ...engineCommands, - ...frameworkCommands, - ]); + processManager = FakeProcessManager.list([...commands]); checkouts = Checkouts( fileSystem: fileSystem, parentDirectory: fileSystem.directory(checkoutsParentDirectory), @@ -892,8 +697,6 @@ void main() { candidateBranch: candidateBranch, checkouts: checkouts, dartRevision: nextDartRevision, - engineMirror: engineMirror, - engineUpstream: EngineRepository.defaultUpstream, frameworkMirror: frameworkMirror, frameworkUpstream: FrameworkRepository.defaultUpstream, releaseChannel: releaseChannel, @@ -908,7 +711,6 @@ void main() { final pb.ConductorState state = pb.ConductorState(); state.mergeFromProto3Json(jsonDecode(stateFile.readAsStringSync())); - expect((await startContext.engine.checkoutDirectory).path, equals(engine.path)); expect((await startContext.framework.checkoutDirectory).path, equals(framework.path)); expect(state.releaseType, ReleaseType.BETA_INITIAL); expect(processManager, hasNoRemainingExpectations);