mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

Calls to `opoo` and `onoe` produce duplicate `Warning:` and `Error:` messages in CI logs because we print something to stdout and print an annotation. Annotations also produce `Error:` and `Warning:` lines in the log. Let's fix this by skipping printing the message if we've already printed an annotation.
136 lines
4.3 KiB
Ruby
136 lines
4.3 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
module GitHub
|
|
# Helper functions for interacting with GitHub Actions.
|
|
#
|
|
# @api internal
|
|
module Actions
|
|
sig { params(string: String).returns(String) }
|
|
def self.escape(string)
|
|
# See https://github.community/t/set-output-truncates-multiline-strings/16852/3.
|
|
string.gsub("%", "%25")
|
|
.gsub("\n", "%0A")
|
|
.gsub("\r", "%0D")
|
|
end
|
|
|
|
sig { params(name: String, value: String).returns(String) }
|
|
def self.format_multiline_string(name, value)
|
|
# Format multiline strings for environment files
|
|
# See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
|
|
|
|
require "securerandom"
|
|
delimiter = "ghadelimiter_#{SecureRandom.uuid}"
|
|
|
|
if name.include?(delimiter) || value.include?(delimiter)
|
|
raise "`name` and `value` must not contain the delimiter"
|
|
end
|
|
|
|
<<~EOS
|
|
#{name}<<#{delimiter}
|
|
#{value}
|
|
#{delimiter}
|
|
EOS
|
|
end
|
|
|
|
sig { returns(T::Boolean) }
|
|
def self.env_set?
|
|
ENV.fetch("GITHUB_ACTIONS", false).present?
|
|
end
|
|
|
|
sig {
|
|
params(
|
|
type: Symbol, message: String,
|
|
file: T.nilable(T.any(String, Pathname)),
|
|
line: T.nilable(Integer)
|
|
).returns(T::Boolean)
|
|
}
|
|
def self.puts_annotation_if_env_set(type, message, file: nil, line: nil)
|
|
# Don't print annotations during tests, too messy to handle these.
|
|
return false if ENV.fetch("HOMEBREW_TESTS", false)
|
|
return false unless env_set?
|
|
|
|
std = (type == :notice) ? $stdout : $stderr
|
|
std.puts Annotation.new(type, message)
|
|
|
|
true
|
|
end
|
|
|
|
# Helper class for formatting annotations on GitHub Actions.
|
|
class Annotation
|
|
ANNOTATION_TYPES = [:notice, :warning, :error].freeze
|
|
|
|
sig { params(path: T.any(String, Pathname)).returns(T.nilable(Pathname)) }
|
|
def self.path_relative_to_workspace(path)
|
|
workspace = Pathname(ENV.fetch("GITHUB_WORKSPACE", Dir.pwd)).realpath
|
|
path = Pathname(path)
|
|
return path unless path.exist?
|
|
|
|
path.realpath.relative_path_from(workspace)
|
|
end
|
|
|
|
sig {
|
|
params(
|
|
type: Symbol,
|
|
message: String,
|
|
file: T.nilable(T.any(String, Pathname)),
|
|
title: T.nilable(String),
|
|
line: T.nilable(Integer),
|
|
end_line: T.nilable(Integer),
|
|
column: T.nilable(Integer),
|
|
end_column: T.nilable(Integer),
|
|
).void
|
|
}
|
|
def initialize(type, message, file: nil, title: nil, line: nil, end_line: nil, column: nil, end_column: nil)
|
|
raise ArgumentError, "Unsupported type: #{type.inspect}" if ANNOTATION_TYPES.exclude?(type)
|
|
raise ArgumentError, "`title` must not contain `::`" if title.present? && title.include?("::")
|
|
|
|
require "utils/tty"
|
|
@type = type
|
|
@message = T.let(Tty.strip_ansi(message), String)
|
|
@file = T.let(self.class.path_relative_to_workspace(file), T.nilable(Pathname)) if file.present?
|
|
@title = T.let(Tty.strip_ansi(title), String) if title
|
|
@line = T.let(Integer(line), Integer) if line
|
|
@end_line = T.let(Integer(end_line), Integer) if end_line
|
|
@column = T.let(Integer(column), Integer) if column
|
|
@end_column = T.let(Integer(end_column), Integer) if end_column
|
|
end
|
|
|
|
sig { returns(String) }
|
|
def to_s
|
|
metadata = @type.to_s
|
|
if @file
|
|
metadata << " file=#{Actions.escape(@file.to_s)}"
|
|
|
|
if @line
|
|
metadata << ",line=#{@line}"
|
|
metadata << ",endLine=#{@end_line}" if @end_line
|
|
|
|
if @column
|
|
metadata << ",col=#{@column}"
|
|
metadata << ",endColumn=#{@end_column}" if @end_column
|
|
end
|
|
end
|
|
end
|
|
|
|
if @title
|
|
metadata << (@file ? "," : " ")
|
|
metadata << "title=#{Actions.escape(@title)}"
|
|
end
|
|
metadata << " " if metadata.end_with?(":")
|
|
|
|
"::#{metadata}::#{Actions.escape(@message)}"
|
|
end
|
|
|
|
# An annotation is only relevant if the corresponding `file` is relative to
|
|
# the `GITHUB_WORKSPACE` directory or if no `file` is specified.
|
|
sig { returns(T::Boolean) }
|
|
def relevant?
|
|
return true if @file.blank?
|
|
|
|
@file.descend.next.to_s != ".."
|
|
end
|
|
end
|
|
end
|
|
end
|