Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5d587f95
Unverified
Commit
5d587f95
authored
Oct 14, 2021
by
Christopher Fujino
Committed by
GitHub
Oct 14, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter_conductor] Refactor next command (#91768)
parent
7a7d9a27
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
356 additions
and
306 deletions
+356
-306
next.dart
dev/conductor/core/lib/src/next.dart
+328
-306
next_test.dart
dev/conductor/core/test/next_test.dart
+28
-0
No files found.
dev/conductor/core/lib/src/next.dart
View file @
5d587f95
...
...
@@ -4,12 +4,12 @@
import
'package:args/command_runner.dart'
;
import
'package:file/file.dart'
show
File
;
import
'package:meta/meta.dart'
show
visibleForTesting
;
import
'package:meta/meta.dart'
show
visibleForTesting
,
visibleForOverriding
;
import
'./globals.dart'
;
import
'./proto/conductor_state.pb.dart'
as
pb
;
import
'./proto/conductor_state.pbenum.dart'
;
import
'./repository.dart'
;
import
'./state.dart'
;
import
'./state.dart'
as
state_import
;
import
'./stdio.dart'
;
const
String
kStateOption
=
'state-file'
;
...
...
@@ -21,7 +21,7 @@ class NextCommand extends Command<void> {
NextCommand
({
required
this
.
checkouts
,
})
{
final
String
defaultPath
=
defaultStateFilePath
(
checkouts
.
platform
);
final
String
defaultPath
=
state_import
.
defaultStateFilePath
(
checkouts
.
platform
);
argParser
.
addOption
(
kStateOption
,
defaultsTo:
defaultPath
,
...
...
@@ -48,352 +48,374 @@ class NextCommand extends Command<void> {
@override
Future
<
void
>
run
()
async
{
await
runN
ext
(
await
NextCont
ext
(
autoAccept:
argResults
![
kYesFlag
]
as
bool
,
checkouts:
checkouts
,
force:
argResults
![
kForceFlag
]
as
bool
,
stateFile:
checkouts
.
fileSystem
.
file
(
argResults
![
kStateOption
]),
);
}
}
@visibleForTesting
bool
prompt
(
String
message
,
Stdio
stdio
)
{
stdio
.
write
(
'
${message.trim()}
(y/n) '
);
final
String
response
=
stdio
.
readLineSync
().
trim
();
final
String
firstChar
=
response
[
0
].
toUpperCase
();
if
(
firstChar
==
'Y'
)
{
return
true
;
}
if
(
firstChar
==
'N'
)
{
return
false
;
).
run
();
}
throw
ConductorException
(
'Unknown user input (expected "y" or "n"):
$response
'
,
);
}
@visibleForTesting
Future
<
void
>
runNext
({
required
bool
autoAccept
,
required
bool
force
,
required
Checkouts
checkouts
,
required
File
stateFile
,
})
async
{
final
Stdio
stdio
=
checkouts
.
stdio
;
const
List
<
CherrypickState
>
finishedStates
=
<
CherrypickState
>[
CherrypickState
.
COMPLETED
,
CherrypickState
.
ABANDONED
,
];
if
(!
stateFile
.
existsSync
())
{
throw
ConductorException
(
'No persistent state file found at
${stateFile.path}
.'
,
);
}
/// Utility class for proceeding to the next step in a release.
///
/// Any calls to functions that cause side effects are wrapped in methods to
/// allow overriding in unit tests.
class
NextContext
{
NextContext
({
required
this
.
autoAccept
,
required
this
.
force
,
required
this
.
checkouts
,
required
this
.
stateFile
,
});
final
pb
.
ConductorState
state
=
readStateFromFile
(
stateFile
);
final
bool
autoAccept
;
final
bool
force
;
final
Checkouts
checkouts
;
final
File
stateFile
;
switch
(
state
.
currentPhase
)
{
case
pb
.
ReleasePhase
.
APPLY_ENGINE_CHERRYPICKS
:
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
engine
.
upstream
.
url
,
);
final
EngineRepository
engine
=
EngineRepository
(
checkouts
,
initialRef:
state
.
engine
.
workingBranch
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
engine
.
checkoutPath
,
Future
<
void
>
run
()
async
{
final
Stdio
stdio
=
checkouts
.
stdio
;
const
List
<
CherrypickState
>
finishedStates
=
<
CherrypickState
>[
CherrypickState
.
COMPLETED
,
CherrypickState
.
ABANDONED
,
];
if
(!
stateFile
.
existsSync
())
{
throw
ConductorException
(
'No persistent state file found at
${stateFile.path}
.'
,
);
// check if the candidate branch is enabled in .ci.yaml
final
CiYaml
engineCiYaml
=
await
engine
.
ciYaml
;
if
(!
engineCiYaml
.
enabledBranches
.
contains
(
state
.
engine
.
candidateBranch
))
{
engineCiYaml
.
enableBranch
(
state
.
engine
.
candidateBranch
);
// commit
final
String
revision
=
await
engine
.
commit
(
'add branch
${state.engine.candidateBranch}
to enabled_branches in .ci.yaml'
,
addFirst:
true
,
);
// append to list of cherrypicks so we know a PR is required
state
.
engine
.
cherrypicks
.
add
(
pb
.
Cherrypick
(
appliedRevision:
revision
,
state:
pb
.
CherrypickState
.
COMPLETED
,
));
}
}
if
(!
requiresEnginePR
(
state
))
{
stdio
.
printStatus
(
'This release has no engine cherrypicks. No Engine PR is necessary.
\n
'
,
final
pb
.
ConductorState
state
=
readStateFromFile
(
stateFile
);
switch
(
state
.
currentPhase
)
{
case
pb
.
ReleasePhase
.
APPLY_ENGINE_CHERRYPICKS
:
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
engine
.
upstream
.
url
,
);
break
;
}
final
EngineRepository
engine
=
EngineRepository
(
checkouts
,
initialRef:
state
.
engine
.
workingBranch
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
engine
.
checkoutPath
,
);
// check if the candidate branch is enabled in .ci.yaml
final
CiYaml
engineCiYaml
=
await
engine
.
ciYaml
;
if
(!
engineCiYaml
.
enabledBranches
.
contains
(
state
.
engine
.
candidateBranch
))
{
engineCiYaml
.
enableBranch
(
state
.
engine
.
candidateBranch
);
// commit
final
String
revision
=
await
engine
.
commit
(
'add branch
${state.engine.candidateBranch}
to enabled_branches in .ci.yaml'
,
addFirst:
true
,
);
// append to list of cherrypicks so we know a PR is required
state
.
engine
.
cherrypicks
.
add
(
pb
.
Cherrypick
(
appliedRevision:
revision
,
state:
pb
.
CherrypickState
.
COMPLETED
,
));
}
if
(!
state_import
.
requiresEnginePR
(
state
))
{
stdio
.
printStatus
(
'This release has no engine cherrypicks. No Engine PR is necessary.
\n
'
,
);
break
;
}
final
List
<
pb
.
Cherrypick
>
unappliedCherrypicks
=
<
pb
.
Cherrypick
>[];
for
(
final
pb
.
Cherrypick
cherrypick
in
state
.
engine
.
cherrypicks
)
{
if
(!
finishedStates
.
contains
(
cherrypick
.
state
))
{
unappliedCherrypicks
.
add
(
cherrypick
);
final
List
<
pb
.
Cherrypick
>
unappliedCherrypicks
=
<
pb
.
Cherrypick
>[];
for
(
final
pb
.
Cherrypick
cherrypick
in
state
.
engine
.
cherrypicks
)
{
if
(!
finishedStates
.
contains
(
cherrypick
.
state
))
{
unappliedCherrypicks
.
add
(
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.'
);
if
(
unappliedCherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'All engine cherrypicks have been auto-applied by the conductor.
\n
'
);
}
else
{
stdio
.
printStatus
(
'There were
${unappliedCherrypicks.length}
cherrypicks that were not auto-applied.'
);
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
'
);
}
stdio
.
printStatus
(
'These must be applied manually in the directory '
'
${state.engine.checkoutPath}
before proceeding.
\n
'
);
}
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Are you ready to push your engine branch to the repository '
'
${state.engine.mirror.url}
?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Are you ready to push your engine branch to the repository '
'
${state.engine.mirror.url}
?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
}
await
engine
.
pushRef
(
fromRef:
'HEAD'
,
// Explicitly create new branch
toRef:
'refs/heads/
${state.engine.workingBranch}
'
,
remote:
state
.
engine
.
mirror
.
name
,
);
break
;
case
pb
.
ReleasePhase
.
CODESIGN_ENGINE_BINARIES
:
stdio
.
printStatus
(<
String
>[
'You must validate pre-submit CI for your engine PR, merge it, and codesign'
,
'binaries before proceeding.
\n
'
,
].
join
(
'
\n
'
));
if
(
autoAccept
==
false
)
{
// TODO(fujino): actually test if binaries have been codesigned on macOS
final
bool
response
=
prompt
(
'Has CI passed for the engine PR and binaries been codesigned?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
break
;
case
pb
.
ReleasePhase
.
APPLY_FRAMEWORK_CHERRYPICKS
:
if
(
state
.
engine
.
cherrypicks
.
isEmpty
&&
state
.
engine
.
dartRevision
.
isEmpty
)
{
stdio
.
printStatus
(
'This release has no engine cherrypicks, and thus the engine.version file
\n
'
'in the framework does not need to be updated.'
,
await
engine
.
pushRef
(
fromRef:
'HEAD'
,
// Explicitly create new branch
toRef:
'refs/heads/
${state.engine.workingBranch}
'
,
remote:
state
.
engine
.
mirror
.
name
,
);
if
(
state
.
framework
.
cherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'This release also has no framework cherrypicks. Therefore, a framework
\n
'
'pull request is not required.'
,
break
;
case
pb
.
ReleasePhase
.
CODESIGN_ENGINE_BINARIES
:
stdio
.
printStatus
(<
String
>[
'You must validate pre-submit CI for your engine PR, merge it, and codesign'
,
'binaries before proceeding.
\n
'
,
].
join
(
'
\n
'
));
if
(
autoAccept
==
false
)
{
// TODO(fujino): actually test if binaries have been codesigned on macOS
final
bool
response
=
prompt
(
'Has CI passed for the engine PR and binaries been codesigned?'
,
stdio
,
);
break
;
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
}
final
Remote
engineUpstreamRemote
=
Remote
(
name:
RemoteName
.
upstream
,
url:
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
,
);
break
;
case
pb
.
ReleasePhase
.
APPLY_FRAMEWORK_CHERRYPICKS
:
if
(
state
.
engine
.
cherrypicks
.
isEmpty
&&
state
.
engine
.
dartRevision
.
isEmpty
)
{
stdio
.
printStatus
(
'This release has no engine cherrypicks, and thus the engine.version file
\n
'
'in the framework does not need to be updated.'
,
);
final
String
engineRevision
=
await
engine
.
reverseParse
(
'HEAD'
);
if
(
state
.
framework
.
cherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'This release also has no framework cherrypicks. Therefore, a framework
\n
'
'pull request is not required.'
,
);
break
;
}
}
final
Remote
engineUpstreamRemote
=
Remote
(
name:
RemoteName
.
upstream
,
url:
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
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
framework
.
upstream
.
url
,
);
final
FrameworkRepository
framework
=
FrameworkRepository
(
checkouts
,
initialRef:
state
.
framework
.
workingBranch
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
framework
.
checkoutPath
,
);
final
String
engineRevision
=
await
engine
.
reverseParse
(
'HEAD'
);
// Check if the current candidate branch is enabled
if
(!(
await
framework
.
ciYaml
).
enabledBranches
.
contains
(
state
.
framework
.
candidateBranch
))
{
(
await
framework
.
ciYaml
).
enableBranch
(
state
.
framework
.
candidateBranch
);
// commit
final
String
revision
=
await
framework
.
commit
(
'add branch
${state.framework.candidateBranch}
to enabled_branches in .ci.yaml'
,
addFirst:
true
,
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
framework
.
upstream
.
url
,
);
// append to list of cherrypicks so we know a PR is required
state
.
framework
.
cherrypicks
.
add
(
pb
.
Cherrypick
(
appliedRevision:
revision
,
state:
pb
.
CherrypickState
.
COMPLETED
,
));
}
stdio
.
printStatus
(
'Rolling new engine hash
$engineRevision
to framework checkout...'
);
final
bool
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
,
final
FrameworkRepository
framework
=
FrameworkRepository
(
checkouts
,
initialRef:
state
.
framework
.
workingBranch
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
framework
.
checkoutPath
,
);
// append to list of cherrypicks so we know a PR is required
state
.
framework
.
cherrypicks
.
add
(
pb
.
Cherrypick
(
appliedRevision:
revision
,
state:
pb
.
CherrypickState
.
COMPLETED
,
));
}
final
List
<
pb
.
Cherrypick
>
unappliedCherrypicks
=
<
pb
.
Cherrypick
>[];
for
(
final
pb
.
Cherrypick
cherrypick
in
state
.
framework
.
cherrypicks
)
{
if
(!
finishedStates
.
contains
(
cherrypick
.
state
))
{
unappliedCherrypicks
.
add
(
cherrypick
);
// Check if the current candidate branch is enabled
if
(!(
await
framework
.
ciYaml
).
enabledBranches
.
contains
(
state
.
framework
.
candidateBranch
))
{
(
await
framework
.
ciYaml
).
enableBranch
(
state
.
framework
.
candidateBranch
);
// commit
final
String
revision
=
await
framework
.
commit
(
'add branch
${state.framework.candidateBranch}
to enabled_branches in .ci.yaml'
,
addFirst:
true
,
);
// append to list of cherrypicks so we know a PR is required
state
.
framework
.
cherrypicks
.
add
(
pb
.
Cherrypick
(
appliedRevision:
revision
,
state:
pb
.
CherrypickState
.
COMPLETED
,
));
}
stdio
.
printStatus
(
'Rolling new engine hash
$engineRevision
to framework checkout...'
);
final
bool
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
(
appliedRevision:
revision
,
state:
pb
.
CherrypickState
.
COMPLETED
,
));
}
}
if
(
state
.
framework
.
cherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'This release has no framework cherrypicks. However, a framework PR is still
\n
'
'required to roll engine cherrypicks.'
,
);
}
else
if
(
unappliedCherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'All framework cherrypicks were auto-applied by the conductor.'
);
}
else
{
if
(
unappliedCherrypicks
.
length
==
1
)
{
stdio
.
printStatus
(
'There was
${unappliedCherrypicks.length}
cherrypick that was not auto-applied.'
,);
final
List
<
pb
.
Cherrypick
>
unappliedCherrypicks
=
<
pb
.
Cherrypick
>[];
for
(
final
pb
.
Cherrypick
cherrypick
in
state
.
framework
.
cherrypicks
)
{
if
(!
finishedStates
.
contains
(
cherrypick
.
state
))
{
unappliedCherrypicks
.
add
(
cherrypick
);
}
}
else
{
stdio
.
printStatus
(
'There were
${unappliedCherrypicks.length}
cherrypicks that were not auto-applied.'
,);
if
(
state
.
framework
.
cherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'This release has no framework cherrypicks. However, a framework PR is still
\n
'
'required to roll engine cherrypicks.'
,
);
}
else
if
(
unappliedCherrypicks
.
isEmpty
)
{
stdio
.
printStatus
(
'All framework cherrypicks were auto-applied by the conductor.'
);
}
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.framework.checkoutPath}
before proceeding.
\n
'
,
);
}
stdio
.
printStatus
(
'These must be applied manually in the directory '
'
${state.framework.checkoutPath}
before proceeding.
\n
'
,
);
}
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Are you ready to push your framework branch to the repository '
'
${state.framework.mirror.url}
?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Are you ready to push your framework branch to the repository '
'
${state.framework.mirror.url}
?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
}
await
framework
.
pushRef
(
fromRef:
'HEAD'
,
// Explicitly create new branch
toRef:
'refs/heads/
${state.framework.workingBranch}
'
,
remote:
state
.
framework
.
mirror
.
name
,
);
break
;
case
pb
.
ReleasePhase
.
PUBLISH_VERSION
:
stdio
.
printStatus
(
'Please ensure that you have merged your framework PR and that'
);
stdio
.
printStatus
(
'post-submit CI has finished successfully.
\n
'
);
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
framework
.
upstream
.
url
,
);
final
FrameworkRepository
framework
=
FrameworkRepository
(
checkouts
,
// We explicitly want to check out the merged version from upstream
initialRef:
'
${upstream.name}
/
${state.framework.candidateBranch}
'
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
framework
.
checkoutPath
,
);
final
String
headRevision
=
await
framework
.
reverseParse
(
'HEAD'
);
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Are you ready to tag commit
$headRevision
as
${state.releaseVersion}
\n
'
'and push to remote
${state.framework.upstream.url}
?'
,
stdio
,
await
framework
.
pushRef
(
fromRef:
'HEAD'
,
// Explicitly create new branch
toRef:
'refs/heads/
${state.framework.workingBranch}
'
,
remote:
state
.
framework
.
mirror
.
name
,
);
break
;
case
pb
.
ReleasePhase
.
PUBLISH_VERSION
:
stdio
.
printStatus
(
'Please ensure that you have merged your framework PR and that'
);
stdio
.
printStatus
(
'post-submit CI has finished successfully.
\n
'
);
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
framework
.
upstream
.
url
,
);
final
FrameworkRepository
framework
=
FrameworkRepository
(
checkouts
,
// We explicitly want to check out the merged version from upstream
initialRef:
'
${upstream.name}
/
${state.framework.candidateBranch}
'
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
framework
.
checkoutPath
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
final
String
headRevision
=
await
framework
.
reverseParse
(
'HEAD'
);
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Are you ready to tag commit
$headRevision
as
${state.releaseVersion}
\n
'
'and push to remote
${state.framework.upstream.url}
?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
}
await
framework
.
tag
(
headRevision
,
state
.
releaseVersion
,
upstream
.
name
);
break
;
case
pb
.
ReleasePhase
.
PUBLISH_CHANNEL
:
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
framework
.
upstream
.
url
,
);
final
FrameworkRepository
framework
=
FrameworkRepository
(
checkouts
,
// We explicitly want to check out the merged version from upstream
initialRef:
'
${upstream.name}
/
${state.framework.candidateBranch}
'
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
framework
.
checkoutPath
,
);
final
String
headRevision
=
await
framework
.
reverseParse
(
'HEAD'
);
if
(
autoAccept
==
false
)
{
// dryRun: true means print out git command
await
framework
.
pushRef
(
fromRef:
headRevision
,
toRef:
state
.
releaseChannel
,
remote:
state
.
framework
.
upstream
.
url
,
force:
force
,
dryRun:
true
,
await
framework
.
tag
(
headRevision
,
state
.
releaseVersion
,
upstream
.
name
);
break
;
case
pb
.
ReleasePhase
.
PUBLISH_CHANNEL
:
final
Remote
upstream
=
Remote
(
name:
RemoteName
.
upstream
,
url:
state
.
framework
.
upstream
.
url
,
);
final
bool
response
=
prompt
(
'Are you ready to publish this release?'
,
stdio
,
final
FrameworkRepository
framework
=
FrameworkRepository
(
checkouts
,
// We explicitly want to check out the merged version from upstream
initialRef:
'
${upstream.name}
/
${state.framework.candidateBranch}
'
,
upstreamRemote:
upstream
,
previousCheckoutLocation:
state
.
framework
.
checkoutPath
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
final
String
headRevision
=
await
framework
.
reverseParse
(
'HEAD'
);
if
(
autoAccept
==
false
)
{
// dryRun: true means print out git command
await
framework
.
pushRef
(
fromRef:
headRevision
,
toRef:
state
.
releaseChannel
,
remote:
state
.
framework
.
upstream
.
url
,
force:
force
,
dryRun:
true
,
);
final
bool
response
=
prompt
(
'Are you ready to publish this release?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
}
await
framework
.
pushRef
(
fromRef:
headRevision
,
toRef:
state
.
releaseChannel
,
remote:
state
.
framework
.
upstream
.
url
,
force:
force
,
);
break
;
case
pb
.
ReleasePhase
.
VERIFY_RELEASE
:
stdio
.
printStatus
(
'The current status of packaging builds can be seen at:
\n
'
'
\t
$kLuciPackagingConsoleLink
'
,
);
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Have all packaging builds finished successfully?'
,
stdio
,
await
framework
.
pushRef
(
fromRef:
headRevision
,
toRef:
state
.
releaseChannel
,
remote:
state
.
framework
.
upstream
.
url
,
force:
force
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
break
;
case
pb
.
ReleasePhase
.
VERIFY_RELEASE
:
stdio
.
printStatus
(
'The current status of packaging builds can be seen at:
\n
'
'
\t
$kLuciPackagingConsoleLink
'
,
);
if
(
autoAccept
==
false
)
{
final
bool
response
=
prompt
(
'Have all packaging builds finished successfully?'
,
stdio
,
);
if
(!
response
)
{
stdio
.
printError
(
'Aborting command.'
);
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
return
;
}
}
}
break
;
case
pb
.
ReleasePhase
.
RELEASE_COMPLETED
:
throw
ConductorException
(
'This release is finished.'
);
break
;
case
pb
.
ReleasePhase
.
RELEASE_COMPLETED
:
throw
ConductorException
(
'This release is finished.'
);
}
final
ReleasePhase
nextPhase
=
state_import
.
getNextPhase
(
state
.
currentPhase
);
stdio
.
printStatus
(
'
\n
Updating phase from
${state.currentPhase}
to
$nextPhase
...
\n
'
);
state
.
currentPhase
=
nextPhase
;
stdio
.
printStatus
(
state_import
.
phaseInstructions
(
state
));
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
}
/// Persist the state to a file.
@visibleForOverriding
void
writeStateToFile
(
File
file
,
pb
.
ConductorState
state
,
[
List
<
String
>
logs
=
const
<
String
>[]])
{
state_import
.
writeStateToFile
(
file
,
state
,
logs
);
}
@visibleForTesting
bool
prompt
(
String
message
,
Stdio
stdio
)
{
stdio
.
write
(
'
${message.trim()}
(y/n) '
);
final
String
response
=
stdio
.
readLineSync
().
trim
();
final
String
firstChar
=
response
[
0
].
toUpperCase
();
if
(
firstChar
==
'Y'
)
{
return
true
;
}
if
(
firstChar
==
'N'
)
{
return
false
;
}
throw
ConductorException
(
'Unknown user input (expected "y" or "n"):
$response
'
,
);
}
final
ReleasePhase
nextPhase
=
getNextPhase
(
state
.
currentPhase
);
stdio
.
printStatus
(
'
\n
Updating phase from
${state.currentPhase}
to
$nextPhase
...
\n
'
);
state
.
currentPhase
=
nextPhase
;
stdio
.
printStatus
(
phaseInstructions
(
state
));
writeStateToFile
(
stateFile
,
state
,
stdio
.
logs
);
/// Read the state from a file.
@visibleForOverriding
pb
.
ConductorState
readStateFromFile
(
File
file
)
=>
state_import
.
readStateFromFile
(
file
);
}
dev/conductor/core/test/next_test.dart
View file @
5d587f95
...
...
@@ -1052,6 +1052,34 @@ void main() {
},
onPlatform:
<
String
,
dynamic
>{
'windows'
:
const
Skip
(
'Flutter Conductor only supported on macos/linux'
),
});
group
(
'prompt'
,
()
{
test
(
'throws if user inputs character that is not "y" or "n"'
,
()
{
final
FileSystem
fileSystem
=
MemoryFileSystem
.
test
();
final
TestStdio
stdio
=
TestStdio
(
stdin:
<
String
>[
'x'
],
verbose:
true
,
);
final
Checkouts
checkouts
=
Checkouts
(
fileSystem:
fileSystem
,
parentDirectory:
fileSystem
.
directory
(
'/'
),
platform:
FakePlatform
(),
processManager:
FakeProcessManager
.
empty
(),
stdio:
stdio
,
);
final
NextContext
context
=
NextContext
(
autoAccept:
false
,
force:
false
,
checkouts:
checkouts
,
stateFile:
fileSystem
.
file
(
'/statefile.json'
),
);
expect
(
()
=>
context
.
prompt
(
'Asking a question?'
,
stdio
),
throwsExceptionWith
(
'Unknown user input (expected "y" or "n")'
),
);
});
});
}
void
_initializeCiYamlFile
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment