11# frozen_string_literal: true
22
3- require "cli/parser "
3+ require "abstract_command "
44
55module Homebrew
6- def self . check_ci_status_args
7- Homebrew ::CLI ::Parser . new do
8- description <<~EOS
9- Check the status of CI tests. Used to determine whether tests can be
10- cancelled, or whether a long-timeout label can be removed.
11- EOS
12-
13- switch "--cancel" , description : "Determine whether tests can be cancelled."
14- switch "--long-timeout-label" , description : "Determine whether a long-timeout label can be removed."
15-
16- named_args :pull_request_number , number : 1
17-
18- hide_from_man_page!
19- end
20- end
6+ module Cmd
7+ class CheckCiStatusCmd < AbstractCommand
8+ cmd_args do
9+ Homebrew ::CLI ::Parser . new do
10+ description <<~EOS
11+ Check the status of CI tests. Used to determine whether tests can be
12+ cancelled, or whether a long-timeout label can be removed.
13+ EOS
14+
15+ switch "--cancel" , description : "Determine whether tests can be cancelled."
16+ switch "--long-timeout-label" , description : "Determine whether a long-timeout label can be removed."
17+
18+ named_args :pull_request_number , number : 1
19+
20+ hide_from_man_page!
21+ end
22+ end
2123
22- GRAPHQL_WORKFLOW_RUN_QUERY = <<~GRAPHQL
23- query ($owner: String!, $name: String!, $pr: Int!) {
24- repository(owner: $owner, name: $name) {
25- pullRequest(number: $pr) {
26- commits(last: 1) {
27- nodes {
28- commit {
29- checkSuites(last: 100) {
30- nodes {
31- status
32- workflowRun {
33- event
34- databaseId
35- createdAt
36- workflow {
37- name
38- }
39- }
40- checkRuns(last: 100) {
24+ GRAPHQL_WORKFLOW_RUN_QUERY = <<~GRAPHQL
25+ query ($owner: String!, $name: String!, $pr: Int!) {
26+ repository(owner: $owner, name: $name) {
27+ pullRequest(number: $pr) {
28+ commits(last: 1) {
29+ nodes {
30+ commit {
31+ checkSuites(last: 100) {
4132 nodes {
42- name
4333 status
44- conclusion
45- databaseId
34+ workflowRun {
35+ event
36+ databaseId
37+ createdAt
38+ workflow {
39+ name
40+ }
41+ }
42+ checkRuns(last: 100) {
43+ nodes {
44+ name
45+ status
46+ conclusion
47+ databaseId
48+ }
49+ }
4650 }
4751 }
4852 }
@@ -51,105 +55,104 @@ def self.check_ci_status_args
5155 }
5256 }
5357 }
54- }
55- }
56- GRAPHQL
57- ALLOWABLE_REMAINING_MACOS_RUNNERS = 1
58-
59- def self . get_workflow_run_status ( pull_request )
60- @status_cache ||= { }
61- return @status_cache [ pull_request ] if @status_cache . include? pull_request
62-
63- owner , name = ENV . fetch ( "GITHUB_REPOSITORY" ) . split ( "/" )
64- variables = {
65- owner :,
66- name :,
67- pr : pull_request ,
68- }
69- odebug "Checking CI status for #{ owner } /#{ name } ##{ pull_request } ..."
70-
71- response = GitHub ::API . open_graphql ( GRAPHQL_WORKFLOW_RUN_QUERY , variables :, scopes : [ "repo" ] . freeze )
72- commit_node = response . dig ( "repository" , "pullRequest" , "commits" , "nodes" , 0 )
73- check_suite_nodes = commit_node . dig ( "commit" , "checkSuites" , "nodes" )
74- ci_nodes = check_suite_nodes . select do |node |
75- workflow_run = node . fetch ( "workflowRun" )
76- next false if workflow_run . blank?
77-
78- workflow_run . fetch ( "event" ) == "pull_request" && workflow_run . dig ( "workflow" , "name" ) == "CI"
79- end
80- # There can be multiple CI nodes when a PR is closed and reopened.
81- # Make sure we use the latest one in this case.
82- ci_node = ci_nodes . max_by { |node | DateTime . parse ( node . dig ( "workflowRun" , "createdAt" ) ) }
83- return [ nil , nil ] if ci_node . blank?
84-
85- check_run_nodes = ci_node . dig ( "checkRuns" , "nodes" )
86-
87- @status_cache [ pull_request ] = [ ci_node , check_run_nodes ]
88- [ ci_node , check_run_nodes ]
89- end
58+ GRAPHQL
59+ ALLOWABLE_REMAINING_MACOS_RUNNERS = 1
60+
61+ def get_workflow_run_status ( pull_request )
62+ @status_cache ||= { }
63+ return @status_cache [ pull_request ] if @status_cache . include? pull_request
64+
65+ owner , name = ENV . fetch ( "GITHUB_REPOSITORY" ) . split ( "/" )
66+ variables = {
67+ owner :,
68+ name :,
69+ pr : pull_request ,
70+ }
71+ odebug "Checking CI status for #{ owner } /#{ name } ##{ pull_request } ..."
72+
73+ response = GitHub ::API . open_graphql ( GRAPHQL_WORKFLOW_RUN_QUERY , variables :, scopes : [ "repo" ] . freeze )
74+ commit_node = response . dig ( "repository" , "pullRequest" , "commits" , "nodes" , 0 )
75+ check_suite_nodes = commit_node . dig ( "commit" , "checkSuites" , "nodes" )
76+ ci_nodes = check_suite_nodes . select do |node |
77+ workflow_run = node . fetch ( "workflowRun" )
78+ next false if workflow_run . blank?
79+
80+ workflow_run . fetch ( "event" ) == "pull_request" && workflow_run . dig ( "workflow" , "name" ) == "CI"
81+ end
82+ # There can be multiple CI nodes when a PR is closed and reopened.
83+ # Make sure we use the latest one in this case.
84+ ci_node = ci_nodes . max_by { |node | DateTime . parse ( node . dig ( "workflowRun" , "createdAt" ) ) }
85+ return [ nil , nil ] if ci_node . blank?
86+
87+ check_run_nodes = ci_node . dig ( "checkRuns" , "nodes" )
88+
89+ @status_cache [ pull_request ] = [ ci_node , check_run_nodes ]
90+ [ ci_node , check_run_nodes ]
91+ end
9092
91- def self . run_id_if_cancellable ( pull_request )
92- ci_node , = get_workflow_run_status ( pull_request )
93- return if ci_node . nil?
93+ def run_id_if_cancellable ( pull_request )
94+ ci_node , = get_workflow_run_status ( pull_request )
95+ return if ci_node . nil?
9496
95- # Possible values: COMPLETED, IN_PROGRESS, PENDING, QUEUED, REQUESTED, WAITING
96- # https://docs.github.com/en/graphql/reference/enums#checkstatusstateb
97- ci_status = ci_node . fetch ( "status" )
98- odebug "CI status: #{ ci_status } "
99- return if ci_status == "COMPLETED"
97+ # Possible values: COMPLETED, IN_PROGRESS, PENDING, QUEUED, REQUESTED, WAITING
98+ # https://docs.github.com/en/graphql/reference/enums#checkstatusstateb
99+ ci_status = ci_node . fetch ( "status" )
100+ odebug "CI status: #{ ci_status } "
101+ return if ci_status == "COMPLETED"
100102
101- ci_run_id = ci_node . dig ( "workflowRun" , "databaseId" )
102- odebug "CI run ID: #{ ci_run_id } "
103- ci_run_id
104- end
103+ ci_run_id = ci_node . dig ( "workflowRun" , "databaseId" )
104+ odebug "CI run ID: #{ ci_run_id } "
105+ ci_run_id
106+ end
105107
106- def self . allow_long_timeout_label_removal? ( pull_request )
107- ci_node , check_run_nodes = get_workflow_run_status ( pull_request )
108- return false if ci_node . nil?
108+ def allow_long_timeout_label_removal? ( pull_request )
109+ ci_node , check_run_nodes = get_workflow_run_status ( pull_request )
110+ return false if ci_node . nil?
109111
110- ci_status = ci_node . fetch ( "status" )
111- odebug "CI status: #{ ci_status } "
112- return true if ci_status == "COMPLETED"
112+ ci_status = ci_node . fetch ( "status" )
113+ odebug "CI status: #{ ci_status } "
114+ return true if ci_status == "COMPLETED"
113115
114- # The `test_deps` job is still waiting to be processed.
115- return false if check_run_nodes . none? { |node | node . fetch ( "name" ) . end_with? ( "(deps)" ) }
116+ # The `test_deps` job is still waiting to be processed.
117+ return false if check_run_nodes . none? { |node | node . fetch ( "name" ) . end_with? ( "(deps)" ) }
116118
117- incomplete_macos_checks = check_run_nodes . select do |node |
118- check_run_status = node . fetch ( "status" )
119- check_run_name = node . fetch ( "name" )
120- odebug "#{ check_run_name } : #{ check_run_status } "
119+ incomplete_macos_checks = check_run_nodes . select do |node |
120+ check_run_status = node . fetch ( "status" )
121+ check_run_name = node . fetch ( "name" )
122+ odebug "#{ check_run_name } : #{ check_run_status } "
121123
122- check_run_status != "COMPLETED" && check_run_name . start_with? ( "macOS" )
123- end
124+ check_run_status != "COMPLETED" && check_run_name . start_with? ( "macOS" )
125+ end
124126
125- incomplete_macos_checks . count <= ALLOWABLE_REMAINING_MACOS_RUNNERS
126- end
127+ incomplete_macos_checks . count <= ALLOWABLE_REMAINING_MACOS_RUNNERS
128+ end
127129
128- def self . check_ci_status
129- args = check_ci_status_args . parse
130- pr = args . named . first . to_i
130+ def run
131+ pr = args . named . first . to_i
131132
132- if !args . cancel? && !args . long_timeout_label?
133- raise UsageError , "At least one of `--cancel` and `--long-timeout-label` is needed."
134- end
133+ if !args . cancel? && !args . long_timeout_label?
134+ raise UsageError , "At least one of `--cancel` and `--long-timeout-label` is needed."
135+ end
135136
136- outputs = { }
137+ outputs = { }
137138
138- if args . cancel?
139- run_id = run_id_if_cancellable ( pr )
140- outputs [ "cancellable-run-id" ] = run_id . to_json
141- end
139+ if args . cancel?
140+ run_id = run_id_if_cancellable ( pr )
141+ outputs [ "cancellable-run-id" ] = run_id . to_json
142+ end
142143
143- if args . long_timeout_label?
144- allow_removal = allow_long_timeout_label_removal? ( pr )
145- outputs [ "allow-long-timeout-label-removal" ] = allow_removal
146- end
144+ if args . long_timeout_label?
145+ allow_removal = allow_long_timeout_label_removal? ( pr )
146+ outputs [ "allow-long-timeout-label-removal" ] = allow_removal
147+ end
147148
148- github_output = ENV . fetch ( "GITHUB_OUTPUT" )
149- File . open ( github_output , "a" ) do |f |
150- outputs . each do |key , value |
151- odebug "#{ key } : #{ value } "
152- f . puts "#{ key } =#{ value } "
149+ github_output = ENV . fetch ( "GITHUB_OUTPUT" )
150+ File . open ( github_output , "a" ) do |f |
151+ outputs . each do |key , value |
152+ odebug "#{ key } : #{ value } "
153+ f . puts "#{ key } =#{ value } "
154+ end
155+ end
153156 end
154157 end
155158 end
0 commit comments