aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cmd/gist-logs.rb
blob: 630361ca2b840bcc5042a96eff0935be52679231 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#:  * `gist-logs` [`--new-issue`|`-n`] <formula>:
#:    Upload logs for a failed build of <formula> to a new Gist.
#:
#:    <formula> is usually the name of the formula to install, but it can be specified
#:    in several different ways. See [SPECIFYING FORMULAE](#specifying-formulae).
#:
#:    If `--with-hostname` is passed, include the hostname in the Gist.
#:
#:    If `--new-issue` is passed, automatically create a new issue in the appropriate
#:    GitHub repository as well as creating the Gist.
#:
#:    If no logs are found, an error message is presented.

require "formula"
require "system_config"
require "stringio"
require "socket"

module Homebrew
  module_function

  def gistify_logs(f)
    files = load_logs(f.logs)
    build_time = f.logs.ctime
    timestamp = build_time.strftime("%Y-%m-%d_%H-%M-%S")

    s = StringIO.new
    SystemConfig.dump_verbose_config s
    # Dummy summary file, asciibetically first, to control display title of gist
    files["# #{f.name} - #{timestamp}.txt"] = { content: brief_build_info(f) }
    files["00.config.out"] = { content: s.string }
    files["00.doctor.out"] = { content: Utils.popen_read("#{HOMEBREW_PREFIX}/bin/brew", "doctor", err: :out) }
    unless f.core_formula?
      tap = <<~EOS
        Formula: #{f.name}
        Tap: #{f.tap}
        Path: #{f.path}
      EOS
      files["00.tap.out"] = { content: tap }
    end

    # Description formatted to work well as page title when viewing gist
    if f.core_formula?
      descr = "#{f.name} on #{OS_VERSION} - Homebrew build logs"
    else
      descr = "#{f.name} (#{f.full_name}) on #{OS_VERSION} - Homebrew build logs"
    end
    url = create_gist(files, descr)

    if ARGV.include?("--new-issue") || ARGV.switch?("n")
      if GitHub.api_credentials_type == :none
        puts <<~EOS
          You can create a new personal access token:
           #{GitHub::ALL_SCOPES_URL}
          and then set the new HOMEBREW_GITHUB_API_TOKEN as the authentication method.

        EOS
        login!
      end

      url = create_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url)
    end

    puts url if url
  end

  def brief_build_info(f)
    build_time_str = f.logs.ctime.strftime("%Y-%m-%d %H:%M:%S")
    s = <<~EOS
      Homebrew build logs for #{f.full_name} on #{OS_VERSION}
    EOS
    if ARGV.include?("--with-hostname")
      hostname = Socket.gethostname
      s << "Host: #{hostname}\n"
    end
    s << "Build date: #{build_time_str}\n"
    s
  end

  # Hack for ruby < 1.9.3
  def noecho_gets
    system "stty -echo"
    result = $stdin.gets
    system "stty echo"
    puts
    result
  end

  def login!
    print "GitHub User: "
    ENV["HOMEBREW_GITHUB_API_USERNAME"] = $stdin.gets.chomp
    print "Password: "
    ENV["HOMEBREW_GITHUB_API_PASSWORD"] = noecho_gets.chomp
    puts
  end

  def load_logs(dir)
    logs = {}
    if dir.exist?
      dir.children.sort.each do |file|
        contents = file.size? ? file.read : "empty log"
        # small enough to avoid GitHub "unicorn" page-load-timeout errors
        max_file_size = 1_000_000
        contents = truncate_text_to_approximate_size(contents, max_file_size, front_weight: 0.2)
        logs[file.basename.to_s] = { content: contents }
      end
    end
    raise "No logs." if logs.empty?
    logs
  end

  def create_gist(files, description)
    url = "https://api.github.com/gists"
    data = { "public" => true, "files" => files, "description" => description }
    scopes = GitHub::CREATE_GIST_SCOPES
    GitHub.open(url, data: data, scopes: scopes)["html_url"]
  end

  def create_issue(repo, title, body)
    url = "https://api.github.com/repos/#{repo}/issues"
    data = { "title" => title, "body" => body }
    scopes = GitHub::CREATE_ISSUE_SCOPES
    GitHub.open(url, data: data, scopes: scopes)["html_url"]
  end

  def gist_logs
    raise FormulaUnspecifiedError if ARGV.resolved_formulae.length != 1

    gistify_logs(ARGV.resolved_formulae.first)
  end
end