Class: BottlePublisher

Inherits:
Object
  • Object
show all
Defined in:
brew/Library/Homebrew/bottle_publisher.rb

Instance Method Summary collapse

Constructor Details

#initialize(tap, changed_formulae_names, bintray_org, no_publish, warn_on_publish_failure) ⇒ BottlePublisher

Returns a new instance of BottlePublisher



7
8
9
10
11
12
13
# File 'brew/Library/Homebrew/bottle_publisher.rb', line 7

def initialize(tap, changed_formulae_names, bintray_org, no_publish, warn_on_publish_failure)
  @tap = tap
  @changed_formulae_names = changed_formulae_names
  @no_publish = no_publish
  @bintray_org = bintray_org
  @warn_on_publish_failure = warn_on_publish_failure
end

Instance Method Details

#publish_and_check_bottlesObject



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'brew/Library/Homebrew/bottle_publisher.rb', line 15

def publish_and_check_bottles
  # Formulae with affected bottles that were published
  bintray_published_formulae = []

  # Publish bottles on Bintray
  unless @no_publish
    published = publish_changed_formula_bottles
    bintray_published_formulae.concat(published)
  end

  # Verify bintray publishing after all patches have been applied
  bintray_published_formulae.uniq!
  verify_bintray_published(bintray_published_formulae)
end

#publish_bottle_file_on_bintray(f, bintray_org, creds) ⇒ Object

Publishes the current bottle files for a given formula to Bintray



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'brew/Library/Homebrew/bottle_publisher.rb', line 52

def publish_bottle_file_on_bintray(f, bintray_org, creds)
  repo = Utils::Bottles::Bintray.repository(f.tap)
  package = Utils::Bottles::Bintray.package(f.name)
  info = FormulaInfo.lookup(f.full_name)
  raise "Failed publishing bottle: failed reading formula info for #{f.full_name}" if info.nil?

  unless info.bottle_info_any
    opoo "No bottle defined in formula #{package}"
    return false
  end
  version = info.pkg_version
  ohai "Publishing on Bintray: #{package} #{version}"
  curl "--write-out", '\n', "--silent", "--fail",
       "--user", "#{creds[:user]}:#{creds[:key]}", "--request", "POST",
       "--header", "Content-Type: application/json",
       "--data", '{"publish_wait_for_secs": 0}',
       "https://api.bintray.com/content/#{bintray_org}/#{repo}/#{package}/#{version}/publish"
  true
rescue => e
  raise unless @warn_on_publish_failure

  onoe e
  false
end

#publish_changed_formula_bottlesObject



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'brew/Library/Homebrew/bottle_publisher.rb', line 30

def publish_changed_formula_bottles
  raise "Need to load formulae to publish them!" if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"]

  published = []
  bintray_creds = { user: ENV["HOMEBREW_BINTRAY_USER"], key: ENV["HOMEBREW_BINTRAY_KEY"] }
  if bintray_creds[:user] && bintray_creds[:key]
    @changed_formulae_names.each do |name|
      f = Formula[name]
      next if f.bottle_unneeded? || f.bottle_disabled?

      bintray_org = @bintray_org || @tap.user.downcase
      next unless publish_bottle_file_on_bintray(f, bintray_org, bintray_creds)

      published << f.full_name
    end
  else
    opoo "You must set HOMEBREW_BINTRAY_USER and HOMEBREW_BINTRAY_KEY to add or update bottles on Bintray!"
  end
  published
end

#verify_bintray_published(formulae_names) ⇒ Object

Verifies that formulae have been published on Bintray by downloading a bottle file for each one. Blocks until the published files are available. Raises an error if the verification fails. This does not currently work for brew pull, because it may have cached the old version of a formula.



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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'brew/Library/Homebrew/bottle_publisher.rb', line 82

def verify_bintray_published(formulae_names)
  return if formulae_names.empty?

  raise "Need to load formulae to verify their publication!" if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"]

  ohai "Verifying bottles published on Bintray"
  formulae = formulae_names.map { |n| Formula[n] }
  max_retries = 300 # shared among all bottles
  poll_retry_delay_seconds = 2

  HOMEBREW_CACHE.cd do
    formulae.each do |f|
      retry_count = 0
      wrote_dots = false
      # Choose arbitrary bottle just to get the host/port for Bintray right
      jinfo = FormulaInfo.lookup(f.full_name)
      unless jinfo
        opoo "Cannot publish bottle: Failed reading info for formula #{f.full_name}"
        next
      end
      bottle_info = jinfo.bottle_info_any
      unless bottle_info
        opoo "No bottle defined in formula #{f.full_name}"
        next
      end

      # Poll for publication completion using a quick partial HEAD, to avoid spurious error messages
      # 401 error is normal while file is still in async publishing process
      url = URI(bottle_info["url"])
      puts "Verifying bottle: #{File.basename(url.path)}"
      http = Net::HTTP.new(url.host, url.port)
      http.use_ssl = true
      retry_count = 0
      http.start do
        loop do
          req = Net::HTTP::Head.new url
          req.initialize_http_header "User-Agent" => HOMEBREW_USER_AGENT_RUBY
          res = http.request req
          break if res.is_a?(Net::HTTPSuccess) || res.code == "302"

          unless res.is_a?(Net::HTTPClientError)
            raise "Failed to find published #{f} bottle at #{url} (#{res.code} #{res.message})!"
          end

          raise "Failed to find published #{f} bottle at #{url}!" if retry_count >= max_retries

          print(wrote_dots ? "." : "Waiting on Bintray.")
          wrote_dots = true
          sleep poll_retry_delay_seconds
          retry_count += 1
        end
      end

      # Actual download and verification
      # We do a retry on this, too, because sometimes the external curl will fail even
      # when the prior HEAD has succeeded.
      puts "\n" if wrote_dots
      filename = File.basename(url.path)
      curl_retry_delay_seconds = 4
      max_curl_retries = 1
      retry_count = 0
      # We're in the cache; make sure to force re-download
      loop do
        curl_download url, to: filename
        break
      rescue
        raise "Failed to download #{f} bottle from #{url}!" if retry_count >= max_curl_retries

        puts "curl download failed; retrying in #{curl_retry_delay_seconds} sec"
        sleep curl_retry_delay_seconds
        curl_retry_delay_seconds *= 2
        retry_count += 1
      end
      checksum = Checksum.new(:sha256, bottle_info["sha256"])
      Pathname.new(filename).verify_checksum(checksum)
    end
  end
end