homebrew_automation 0.0.4 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0afb1407753967ec435daea068803517ef0daeda28eb6bbc5407176499714a6c
4
- data.tar.gz: d07ac2517ffd1a629372e0802bdfb38c19712216ca75fbdd6dcb50e5b78e9cb4
3
+ metadata.gz: 97e03de71e29414ed00f37914b8964e91421465de75ffd1cd39c85a4ceb274da
4
+ data.tar.gz: da6cf8c8bd4e2ad86ccd2f0242c0407ef2955b74e5fc70c8b0fbf39e1fa6ffe9
5
5
  SHA512:
6
- metadata.gz: e1d8400c5edcefe664f0c29e955309b56b96023990961bcf4cd697088c5b57ba5e75906d851083330c6522f5938c726a966828d98236752d3711d4db21d4907c
7
- data.tar.gz: 60b4bf05c87d8df1e8224cb73998cdc6a6e793d111296b272348c3d888dff5e111aa93207c494da27a05ac2e17459b635d41bf027b4f44aed6546b11a0b38bb6
6
+ metadata.gz: 6ee552dac8c240c7585073046f2c6c1385bbd65802146aa55bd1e6bd074b9d122d8e018b2135ca13ee053df4f3bd113b52f687906bd08858748bdf0f03f1d83d
7
+ data.tar.gz: 6dd74ac44f8d465976426baf3f201ca5786d15e9f9c557610e19297d870764eef511390cfcbeeb1e7d5a5b5eb7ccba2b0d242668129e21c3bf765ec513030e38
@@ -2,22 +2,20 @@
2
2
 
3
3
  require 'thor'
4
4
 
5
- require 'homebrew_automation'
5
+ require_relative '../lib/homebrew_automation.rb'
6
6
 
7
- class MyCliApp < Thor
7
+ class FormulaCommands < Thor
8
8
 
9
- desc 'put-sdist', 'update the URL and sha256 checksum of the source tarball'
9
+ desc 'put-sdist', 'Update the URL and sha256 checksum of the source tarball'
10
10
  option :url, :required => true
11
11
  option :sha256, :required => true
12
12
  def put_sdist
13
13
  before = HomebrewAutomation::Formula.parse_string($stdin.read)
14
- after = before.
15
- update_field("url", options[:url]).
16
- update_field("sha256", options[:sha256])
14
+ after = before.put_sdist options[:url], options[:sha256]
17
15
  $stdout.write after
18
16
  end
19
17
 
20
- desc 'put-bottle', 'insert or update a bottle reference for a given OS'
18
+ desc 'put-bottle', 'Insert or update a bottle reference for a given OS'
21
19
  option :os, :required => true
22
20
  option :sha256, :required => true
23
21
  def put_bottle
@@ -28,6 +26,74 @@ class MyCliApp < Thor
28
26
 
29
27
  end
30
28
 
29
+ class WorkflowCommands < Thor
30
+ class_option :tap_user, :required => true
31
+ class_option :tap_repo, :required => true
32
+ class_option :tap_token, :required => true
33
+ class_option :bintray_user, :required => true
34
+ class_option :bintray_token, :required => true
35
+
36
+ desc 'build-and-upload', 'Build binary tarball from source tarball, then upload to Bintray'
37
+ long_desc <<-HERE_HERE
38
+ Since we're uploading to Bintray, we need a Bintray API KEY at `bintray_token`.
39
+
40
+ `formula_name` defaults to the same as `source_repo`.
41
+ `formula_version` defaults to `source_tag` with a leading `v` stripped off.
42
+ HERE_HERE
43
+ option :source_user, :required => true
44
+ option :source_repo, :required => true
45
+ option :source_tag, :required => true
46
+ option :formula_name
47
+ option :formula_version
48
+ def build_and_upload
49
+ workflow.build_and_upload_bottle(
50
+ HomebrewAutomation::SourceDist.new(
51
+ options[:source_user],
52
+ options[:source_repo],
53
+ options[:source_tag]),
54
+ formula_name: options[:formula_name],
55
+ version_name: options[:formula_version])
56
+ end
57
+
58
+ desc 'gather-and-publish', 'Make the Tap aware of new Bottles'
59
+ long_desc <<-HERE_HERE
60
+ See what bottles have been built and uploaded to Bintray, then publish them into the Tap.
61
+
62
+ Since we're publishing updates to the Formula in our Tap, we need Git push access to the
63
+ Tap repo on Github via a Github OAuth token via `tap_token`.
64
+
65
+ `formula-name` should be both the formula name as appears in the Tap and also the Bintray package name.
66
+ `formula-version` should be the Bintray "Version" name.
67
+ HERE_HERE
68
+ option :formula_name, :required => true
69
+ option :formula_version, :required => true
70
+ def gather_and_publish
71
+ workflow.gather_and_publish_bottles(
72
+ options[:formula_name],
73
+ options[:formula_version])
74
+ end
75
+
76
+ private
77
+
78
+ def workflow
79
+ HomebrewAutomation::Workflow.new(
80
+ HomebrewAutomation::Tap.new(options[:tap_user], options[:tap_repo], options[:tap_token]),
81
+ HomebrewAutomation::Bintray.new(options[:bintray_user], options[:bintray_token]))
82
+ end
83
+
84
+ end
85
+
86
+ class MyCliApp < Thor
87
+
88
+ desc 'formula (...)', 'Modify Formula DSL source (read stdin, write stdout)'
89
+ subcommand "formula", FormulaCommands
90
+
91
+ desc 'bottle (...)', 'Workflows for dealing with binary artifacts'
92
+ subcommand "bottle", WorkflowCommands
93
+
94
+ end
95
+
96
+
31
97
  MyCliApp.start(ARGV)
32
98
 
33
99
 
@@ -0,0 +1,86 @@
1
+
2
+ require 'json'
3
+ require 'base64'
4
+ require 'uri'
5
+ require 'rest-client'
6
+
7
+ module HomebrewAutomation
8
+
9
+ class Bintray
10
+
11
+ def initialize(
12
+ username,
13
+ api_key,
14
+ http: RestClient,
15
+ base_url: "https://bintray.com/api/v1"
16
+ )
17
+ @username = username
18
+ @api_key = api_key
19
+ @base_url = base_url
20
+ @http = http # allow injecting mocks for testing
21
+ end
22
+
23
+ # POST /packages/:subject/:repo/:package/versions
24
+ #
25
+ # Redundant: Bintray seems to create nonexistant versions for you if you
26
+ # just try to upload files into it.
27
+ def create_version(repo_name, package_name, version_name)
28
+ safe_repo = URI.escape(repo_name)
29
+ safe_pkg = URI.escape(package_name)
30
+ @http.post(
31
+ rel("/packages/#{safe_username}/#{safe_repo}/#{safe_pkg}/versions"),
32
+ { name: version_name }.to_json,
33
+ api_headers
34
+ )
35
+ end
36
+
37
+ # PUT /content/:subject/:repo/:package/:version/:file_path[?publish=0/1][?override=0/1][?explode=0/1]
38
+ #
39
+ # Bintray seems to expect the byte sequence of the file to be written straight out into the
40
+ # HTTP request body, optionally via `Transfer-Encoding: chunked`. So we pass the `content` String
41
+ # straight through to RestClient
42
+ def upload_file(repo_name, package_name, version_name, filename, content, publish: 1)
43
+ safe_repo = URI.escape(repo_name)
44
+ safe_pkg = URI.escape(package_name)
45
+ safe_ver = URI.escape(version_name)
46
+ safe_filename = URI.escape(filename)
47
+ safe_publish = URI.escape(publish.to_s)
48
+ @http.put(
49
+ rel("/content/#{safe_username}/#{safe_repo}/#{safe_pkg}/#{safe_ver}/#{safe_filename}?publish=#{safe_publish}"),
50
+ content,
51
+ auth_headers
52
+ )
53
+ end
54
+
55
+ # GET /packages/:subject/:repo/:package/versions/:version/files[?include_unpublished=0/1]
56
+ def get_all_files_in_version(repo_name, package_name, version_name)
57
+ safe_repo = URI.escape(repo_name)
58
+ safe_pkg = URI.escape(package_name)
59
+ safe_ver = URI.escape(version_name)
60
+ @http.get(
61
+ rel("/packages/#{safe_username}/#{safe_repo}/#{safe_pkg}/versions/#{safe_ver}/files"),
62
+ auth_headers)
63
+ end
64
+
65
+ def safe_username
66
+ URI.escape(@username)
67
+ end
68
+
69
+ # Expand relative path
70
+ def rel(slash_subpath)
71
+ @base_url + slash_subpath
72
+ end
73
+
74
+ def api_headers
75
+ { "Content-Type" => "application/json" }.update auth_headers
76
+ end
77
+
78
+ def auth_headers
79
+ # As per RFC 7617
80
+ cred = Base64.strict_encode64("#{@username}:#{@api_key}")
81
+ { Authorization: "Basic #{cred}" }
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,74 @@
1
+
2
+
3
+ module HomebrewAutomation
4
+
5
+ class Bottle
6
+
7
+ def initialize(
8
+ tap_url,
9
+ formula_name,
10
+ os_name,
11
+ filename: nil,
12
+ content: nil)
13
+ @tap_url = tap_url
14
+ @formula_name = formula_name
15
+ @os_name = os_name
16
+ @filename = filename
17
+ @minus_minus = nil # https://github.com/Homebrew/brew/pull/4612
18
+ @content = content
19
+ end
20
+
21
+ # Takes ages to run, just like if done manually
22
+ def build
23
+ die unless system 'brew', 'tap', local_tap_name, @tap_url
24
+ die unless system 'brew', 'install', '--verbose', '--build-bottle', @formula_name
25
+ die unless system 'brew', 'bottle', '--verbose', '--json', @formula_name
26
+ end
27
+
28
+ # Read and analyse metadata JSON file
29
+ def locate_tarball
30
+ json_filename = Dir['*.bottle.json'].first
31
+ unless json_filename
32
+ build
33
+ return locate_tarball
34
+ end
35
+ json = JSON.parse(File.read(json_filename))
36
+ focus = json || die
37
+ focus = focus[json.keys.first] || die
38
+ focus = focus['bottle'] || die
39
+ focus = focus['tags'] || die
40
+ focus = focus[@os_name] || die
41
+ @minus_minus, @filename = focus['local_filename'], focus['filename']
42
+ end
43
+
44
+ def minus_minus
45
+ @minus_minus || locate_tarball.first
46
+ end
47
+
48
+ def filename
49
+ @filename || locate_tarball.last
50
+ end
51
+
52
+ def load_tarball_from_disk
53
+ File.rename minus_minus, filename
54
+ @content = File.read filename
55
+ end
56
+
57
+ def content
58
+ @content || load_tarball_from_disk
59
+ end
60
+
61
+ private
62
+
63
+ # A name for the temporary tap; doesn't really matter what this is.
64
+ def local_tap_name
65
+ 'easoncxz/local-tap'
66
+ end
67
+
68
+ def die
69
+ raise StandardError.new
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,51 @@
1
+
2
+ require_relative './mac-os.rb'
3
+ require_relative './formula.rb'
4
+
5
+ module HomebrewAutomation
6
+
7
+ # Some functions for figuring out, from files on Bintray, what values to use in bottle DSL.
8
+ class BottleGatherer
9
+
10
+ # @param json [Hash] List of files from Bintray
11
+ def initialize(json)
12
+ @json = json
13
+ @bottles = nil
14
+ end
15
+
16
+ # () -> Hash String String
17
+ #
18
+ # Returns a hash with keys being OS names (in Homebrew-form) and values being SHA256 checksums
19
+ def bottles
20
+ return @bottles if @bottles
21
+ pairs = @json.map do |f|
22
+ os = parse_for_os(f['name'])
23
+ checksum = f['sha256']
24
+ [os, checksum]
25
+ end
26
+ @bottles = Hash[pairs]
27
+ end
28
+
29
+ # Formula -> Formula
30
+ #
31
+ # Put all bottles gathered here into the given formula, then return the result
32
+ def put_bottles_into(formula)
33
+ bottles.reduce(formula) do |formula, (os, checksum)|
34
+ formula.put_bottle(os, checksum)
35
+ end
36
+ end
37
+
38
+ #private
39
+
40
+ # String -> String
41
+ #
42
+ # filename -> OS name
43
+ def parse_for_os(bottle_filename)
44
+ File.extname(
45
+ File.basename(bottle_filename, '.bottle.tar.gz')).
46
+ sub(/^\./, '')
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,217 @@
1
+
2
+ require 'parser/current'
3
+ require 'unparser'
4
+
5
+ Parser::Builders::Default.emit_lambda = true
6
+ Parser::Builders::Default.emit_procarg0 = true
7
+
8
+ module HomebrewAutomation
9
+
10
+ # An internal representation of some Formula.rb Ruby source file, containing
11
+ # the definition of a Homebrew Bottle. See Homebrew docs for concepts:
12
+ # https://docs.brew.sh/Bottles.html
13
+ #
14
+ # Instance methods produce new instances where applicable, leaving all
15
+ # instances free from mutation.
16
+ class Formula
17
+
18
+ # A constructor method that parses the string form of a Homebrew Formula
19
+ # source file into an internal representation
20
+ #
21
+ # @return [Formula]
22
+ def self.parse_string s
23
+ Formula.new (Parser::CurrentRuby.parse s)
24
+ end
25
+
26
+ # Take an post-parsing abstract syntax tree representation of a Homebrew Formula.
27
+ # This is mostly not intended for common use-cases.
28
+ #
29
+ # @param ast [Parser::AST::Node]
30
+ def initialize ast
31
+ @ast = ast
32
+ end
33
+
34
+ # Produce Homebrew Formula source code as a string, suitable for saving as
35
+ # a Ruby source file.
36
+ #
37
+ # @return [String]
38
+ def to_s
39
+ Unparser.unparse @ast
40
+ end
41
+
42
+ # Update a field in the Formula
43
+ #
44
+ # @param field [String] Name of the Formula field, e.g. `url`
45
+ # @param value [String] Value of the Formula field, e.g. `https://github.com/easoncxz/homebrew-automation`
46
+ # @return [Formula] a new instance of Formula with the changes applied
47
+ def update_field field, value
48
+ Formula.new update(
49
+ @ast,
50
+ [ by_type('begin'),
51
+ by_both(
52
+ by_type('send'),
53
+ by_msg(field)),
54
+ by_type('str')],
55
+ -> (n) { n.updated(nil, [value]) })
56
+ end
57
+
58
+ # Update two fields together
59
+ #
60
+ # @param url [String] URL of source tarball
61
+ # @param sha256 [String] SHA256 sum of source tarball
62
+ def put_sdist url, sha256
63
+ update_field("url", url).
64
+ update_field("sha256", sha256)
65
+ end
66
+
67
+ # Insert or replace the Homebrew Bottle for a given OS
68
+ #
69
+ # @param os [String] Operating system name, e.g. "yosemite", as per Homebrew's conventions
70
+ # @param sha256 [String] Checksum of the binary "Bottle" tarball
71
+ # @return [Formula] a new instance of Formula with the changes applied
72
+ def put_bottle os, sha256
73
+ Formula.new update(
74
+ @ast,
75
+ bot_begin_path,
76
+ put_bottle_version(os, sha256))
77
+ end
78
+
79
+ def == o
80
+ self.class == o.class && @ast == o.ast
81
+ end
82
+
83
+ def hash
84
+ @ast.hash
85
+ end
86
+
87
+ alias :eql? :==
88
+
89
+ protected
90
+ attr_reader :ast
91
+
92
+ private
93
+
94
+ # Path to the :begin node
95
+ # bot_begin_path :: [Choice]
96
+ # type Choice = Proc (Node -> Bool)
97
+ def bot_begin_path
98
+ [ by_type('begin'),
99
+ by_both(
100
+ by_type('block'),
101
+ by_child(
102
+ by_both(
103
+ by_type('send'),
104
+ by_msg('bottle')))),
105
+ by_type('begin')]
106
+ end
107
+
108
+ # Tricky: this is an insert-or-update
109
+ # put_bottle_version :: String -> String -> Proc (Node -> Node)
110
+ def put_bottle_version os, sha256
111
+ -> (bot_begin) {
112
+ bot_begin.updated(
113
+ nil, # keep the node type unchanged
114
+ bot_begin.children.reject(
115
+ # Get rid of any existing matching ones
116
+ &by_both(
117
+ by_msg('sha256'),
118
+ by_os(os))
119
+ # Then add the one we want
120
+ ).push(new_sha256(sha256, os)))
121
+ }
122
+ end
123
+
124
+ # Build a new AST Node
125
+ # String -> String -> Node
126
+ def new_sha256 sha256, os
127
+ # Unparser doesn't like Sexp, so let's bring
128
+ # own own bit of "source code" inline.
129
+ sha256_send = Parser::CurrentRuby.parse(
130
+ 'sha256 "checksum-here" => :some_os')
131
+ with_sha256 = update(
132
+ sha256_send,
133
+ [ by_type('hash'),
134
+ by_type('pair'),
135
+ by_type('str') ],
136
+ -> (n) { n.updated(nil, [sha256]) })
137
+ with_sha256_and_os = update(
138
+ with_sha256,
139
+ [ by_type('hash'),
140
+ by_type('pair'),
141
+ by_type('sym') ],
142
+ -> (n) { n.updated(nil, [os.to_sym]) })
143
+ with_sha256_and_os
144
+ end
145
+
146
+ # update :: Node -> [Choice] -> Proc (Node -> Node) -> Node
147
+ def update node, path, fn
148
+ if path.length == 0 then
149
+ fn.(node)
150
+ else
151
+ choose, *rest = path
152
+ node.updated(
153
+ nil, # Don't change node type
154
+ node.children.map do |c|
155
+ choose.(c) ? update(c, rest, fn) : c
156
+ end)
157
+ end
158
+ end
159
+
160
+ # zoom_in :: Node -> [Choice] -> Node
161
+ def zoom_in node, path
162
+ if path.length == 0 then
163
+ node
164
+ else
165
+ choose, *rest = path
166
+ chosen = node.children.select(&choose).first
167
+ zoom_in chosen, rest
168
+ end
169
+ end
170
+
171
+ # by_both
172
+ # :: Proc (Node -> Bool)
173
+ # -> Proc (Node -> Bool)
174
+ # -> Proc (Node -> Bool)
175
+ def by_both p, q
176
+ -> (n) { p.(n) && q.(n) }
177
+ end
178
+
179
+ # by_msg :: String -> Proc (Node -> Bool)
180
+ def by_msg msg
181
+ -> (n) { n.children[1] == msg.to_sym }
182
+ end
183
+
184
+ # by_type :: String -> Proc (Node -> Bool)
185
+ def by_type type
186
+ -> (n) {
187
+ n &&
188
+ n.is_a?(AST::Node) &&
189
+ n.type == type.to_sym
190
+ }
191
+ end
192
+
193
+ # Matches if one of the node's children matches the given p
194
+ # by_child :: Proc (Node -> Bool) -> Proc (Node -> Bool)
195
+ def by_child p
196
+ -> (n) {
197
+ n &&
198
+ n.is_a?(AST::Node) &&
199
+ n.children.select(&p).size > 0
200
+ }
201
+ end
202
+
203
+ # Matches if this :send node expresses the give sha256 sum
204
+ # by_os :: String -> Proc (Node -> Bool)
205
+ def by_os os
206
+ -> (n) {
207
+ zoom_in(n, [
208
+ by_type('hash'),
209
+ by_type('pair'),
210
+ by_type('sym')])
211
+ .children[0] == os.to_sym
212
+ }
213
+ end
214
+
215
+ end
216
+
217
+ end
@@ -0,0 +1,29 @@
1
+
2
+ module HomebrewAutomation
3
+
4
+ class MacOS
5
+
6
+ # () -> String
7
+ #
8
+ # Returns a representation of the macOS version name in a format recognised by Homebrew,
9
+ # in particular the Formula/Bottle DSL.
10
+ def self.identify_version
11
+ version = `sw_vers -productVersion`
12
+ mac_to_homebrew.
13
+ select { |pattern, _| pattern === version }.
14
+ map { |_, description| description }.
15
+ first
16
+ end
17
+
18
+ def self.mac_to_homebrew
19
+ {
20
+ /^10.10/ => 'yosemite',
21
+ /^10.11/ => 'el_capitan',
22
+ /^10.12/ => 'sierra',
23
+ /^10.13/ => 'high_sierra'
24
+ }
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,52 @@
1
+
2
+ require 'http'
3
+
4
+ module HomebrewAutomation
5
+
6
+ # A representation of a source distribution tarball file
7
+ class SourceDist
8
+
9
+ # @param tag [String] a Git tag, e.g. "v0.1.1.14"
10
+ def initialize user, repo, tag
11
+ @user = user
12
+ @repo = repo
13
+ @tag = tag
14
+ end
15
+
16
+ attr_reader :user, :repo, :tag
17
+
18
+ # Calculate and return the file's checksum. Lazy and memoized.
19
+ #
20
+ # @return [String] hex-encoded string representation of the checksum
21
+ def sha256
22
+ @sha256 ||= Digest::SHA256.hexdigest contents
23
+ end
24
+
25
+ # Fetch the file contents over HTTP. Lazy and memoized.
26
+ #
27
+ # @param fake [String] fake file contents (for testing)
28
+ # @return [String] contents of the file
29
+ def contents fake: nil
30
+ @contents = @contents || fake ||
31
+ begin
32
+ resp = HTTP.follow.get url
33
+ case resp.code
34
+ when 200
35
+ resp.body.to_s
36
+ else
37
+ puts resp
38
+ raise StandardError.new resp.code
39
+ end
40
+ end
41
+ end
42
+
43
+ # Pure
44
+ #
45
+ # @return [String]
46
+ def url
47
+ "https://github.com/#{@user}/#{@repo}/archive/#{@tag}.tar.gz"
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,85 @@
1
+
2
+ require_relative "formula.rb"
3
+
4
+ module HomebrewAutomation
5
+
6
+ class Tap
7
+
8
+ # Get a token from: https://github.com/settings/tokens
9
+ #
10
+ # @param keep_submodule [Boolean] When done, don't delete the tap Git repo
11
+ def initialize(user, repo, token, keep_submodule: false)
12
+ @repo = repo
13
+ @url = "https://#{token}@github.com/#{user}/#{repo}.git"
14
+ @keep_submodule = keep_submodule
15
+ end
16
+
17
+ attr_reader :user, :repo, :token
18
+
19
+ # forall a. Block (() -> a) -> a
20
+ #
21
+ # Do something in a fresh clone, then clean-up.
22
+ def with_git_clone(&block)
23
+ begin
24
+ git_clone
25
+ Dir.chdir @repo, &block
26
+ ensure
27
+ remove_git_submodule unless @keep_submodule
28
+ end
29
+ end
30
+
31
+ # (String, Block (Formula -> Formula)) -> nil
32
+ #
33
+ # Overwrite the given formula
34
+ def on_formula(formula, &block)
35
+ name = "#{formula}.rb"
36
+ block ||= ->(n) { n }
37
+ Dir.chdir 'Formula' do
38
+ File.open name, 'r' do |old_file|
39
+ File.open "#{name}.new", 'w' do |new_file|
40
+ new_file.write(
41
+ block.
42
+ call(Formula.parse_string(old_file.read)).
43
+ to_s)
44
+ end
45
+ end
46
+ File.rename "#{name}.new", name
47
+ end
48
+ end
49
+
50
+ def git_config
51
+ name = ENV['TRAVIS_GIT_USER_NAME']
52
+ email = ENV['TRAVIS_GIT_USER_EMAIL']
53
+ if name && email
54
+ system 'git', 'config', '--global', 'user.name', name
55
+ system 'git', 'config', '--global', 'user.email', email
56
+ end
57
+ end
58
+
59
+ def git_commit_am(msg)
60
+ die unless system "git", "commit", "-am", msg
61
+ end
62
+
63
+ def git_push
64
+ die unless system "git", "push"
65
+ end
66
+
67
+
68
+ #private
69
+
70
+
71
+ def git_clone
72
+ die unless system "git", "clone", @url
73
+ end
74
+
75
+ def remove_git_submodule
76
+ die unless system "rm", "-rf", @repo
77
+ end
78
+
79
+ def die
80
+ raise StandardError.new
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -1,4 +1,4 @@
1
1
 
2
2
  module HomebrewAutomation
3
- VERSION = '0.0.4'
3
+ VERSION = '0.0.8'
4
4
  end
@@ -0,0 +1,91 @@
1
+
2
+ require_relative './mac-os.rb'
3
+ require_relative './bottle_gatherer.rb'
4
+
5
+ module HomebrewAutomation
6
+
7
+ # Imperative glue code
8
+ #
9
+ # Probably each method suits to become a CLI command.
10
+ class Workflow
11
+
12
+ # @param tap [HomebrewAutomation::Tap]
13
+ # @param bintray [HomebrewAutomation::Bintray]
14
+ def initialize(
15
+ tap,
16
+ bintray,
17
+ bintray_bottle_repo: 'homebrew-bottles')
18
+ @tap = tap
19
+ @bintray = bintray
20
+ @bintray_bottle_repo = bintray_bottle_repo
21
+ end
22
+
23
+ # Build a bottle from the given source tarball reference
24
+ #
25
+ # @param source_dist [HomebrewAutomation::SourceDist] Source tarball
26
+ # @param formula_name [String] Formula name as appears in the Tap, which should be the same as the Bintray "Package" name
27
+ # @param version_name [String] Bintray package "Version" name; defaults to stripping leading `v` from the Git tag.
28
+ # @return [Bottle]
29
+ def build_and_upload_bottle(source_dist, formula_name: nil, version_name: nil)
30
+ formula_name ||= source_dist.repo
31
+ version_name ||= source_dist.tag.sub(/^v/, '')
32
+ os_name = MacOS.identify_version
33
+ @tap.with_git_clone do
34
+ @tap.on_formula(formula_name) do |formula|
35
+ formula.put_sdist(source_dist.url, source_dist.sha256)
36
+ end
37
+ @tap.git_commit_am "Throwaway commit; just for building bottles"
38
+
39
+ local_tap_url = File.realpath('.')
40
+ bottle = Bottle.new(local_tap_url, formula_name, os_name)
41
+ bottle.build
42
+
43
+ @bintray.create_version(
44
+ @bintray_bottle_repo,
45
+ formula_name,
46
+ version_name)
47
+ @bintray.upload_file(
48
+ @bintray_bottle_repo,
49
+ formula_name,
50
+ version_name,
51
+ bottle.filename,
52
+ bottle.content)
53
+
54
+ bottle
55
+ end
56
+ end
57
+
58
+ # Look around on Bintray to see what bottles we've previously built, then
59
+ # push new commits into the Tap repository to register the new bottles.
60
+ #
61
+ # @param formula_name [String]
62
+ # @param version_name [String] Bintray "Version" name, not a Git tag
63
+ # @return [Formula]
64
+ def gather_and_publish_bottles(formula_name, version_name)
65
+ @tap.with_git_clone do
66
+ resp = @bintray.get_all_files_in_version(
67
+ @bintray_bottle_repo,
68
+ formula_name,
69
+ version_name)
70
+ unless (200..207) === resp.code
71
+ puts resp
72
+ raise StandardError.new(resp)
73
+ end
74
+
75
+ json = JSON.parse(resp.body)
76
+ gatherer = BottleGatherer.new(json)
77
+
78
+ @tap.on_formula(formula_name) do |formula|
79
+ gatherer.put_bottles_into(formula)
80
+ end
81
+
82
+ @tap.git_config
83
+ @tap.git_commit_am "Add bottles for #{formula_name}@#{version_name}"
84
+ @tap.git_push
85
+ end
86
+ end
87
+
88
+
89
+ end
90
+
91
+ end
@@ -1,172 +1,14 @@
1
1
 
2
- require 'parser/current'
3
- require 'unparser'
4
-
5
- Parser::Builders::Default.emit_lambda = true
6
- Parser::Builders::Default.emit_procarg0 = true
7
-
2
+ # Helps you manipulate Homebrew Formula files, Bottles etc.
8
3
  module HomebrewAutomation
9
-
10
- class Formula
11
-
12
- # Formula::parse_string :: String -> Formula
13
- def self.parse_string s
14
- Formula.new (Parser::CurrentRuby.parse s)
15
- end
16
-
17
- # Formula::new :: Parser::AST::Node -> Formula
18
- def initialize ast
19
- @ast = ast
20
- end
21
-
22
- def to_s
23
- Unparser.unparse @ast
24
- end
25
-
26
- # update_field :: String -> String -> Formula
27
- def update_field field, value
28
- Formula.new update(
29
- @ast,
30
- [ by_type('begin'),
31
- by_both(
32
- by_type('send'),
33
- by_msg(field)),
34
- by_type('str')],
35
- -> (n) { n.updated(nil, [value]) })
36
- end
37
-
38
- # Insert or replace the bottle for a given OS
39
- # put_bottle :: String -> String -> Node -> Node
40
- def put_bottle os, sha256
41
- Formula.new update(
42
- @ast,
43
- bot_begin_path,
44
- put_bottle_version(os, sha256))
45
- end
46
-
47
- private
48
-
49
- # Path to the :begin node
50
- # bot_begin_path :: [Choice]
51
- # type Choice = Proc (Node -> Bool)
52
- def bot_begin_path
53
- [ by_type('begin'),
54
- by_both(
55
- by_type('block'),
56
- by_child(
57
- by_both(
58
- by_type('send'),
59
- by_msg('bottle')))),
60
- by_type('begin')]
61
- end
62
-
63
- # Tricky: this is an insert-or-update
64
- # put_bottle_version :: String -> String -> Proc (Node -> Node)
65
- def put_bottle_version os, sha256
66
- -> (bot_begin) {
67
- bot_begin.updated(
68
- nil, # keep the node type the unchanged
69
- bot_begin.children.reject(
70
- # Get rid of any existing matching ones
71
- &by_both(
72
- by_msg('sha256'),
73
- by_os(os))
74
- # Then add the one we want
75
- ).push(new_sha256(sha256, os)))
76
- }
77
- end
78
-
79
- # Build a new AST Node
80
- # String -> String -> Node
81
- def new_sha256 sha256, os
82
- # Unparser doesn't like Sexp, so let's bring
83
- # own own bit of "source code" inline.
84
- sha256_send = Parser::CurrentRuby.parse(
85
- 'sha256 "checksum-here" => :some_os')
86
- with_sha256 = update(
87
- sha256_send,
88
- [ by_type('hash'),
89
- by_type('pair'),
90
- by_type('str') ],
91
- -> (n) { n.updated(nil, [sha256]) })
92
- with_sha256_and_os = update(
93
- with_sha256,
94
- [ by_type('hash'),
95
- by_type('pair'),
96
- by_type('sym') ],
97
- -> (n) { n.updated(nil, [os.to_sym]) })
98
- with_sha256_and_os
99
- end
100
-
101
- # update :: Node -> [Choice] -> Proc (Node -> Node) -> Node
102
- def update node, path, fn
103
- if path.length == 0 then
104
- fn.(node)
105
- else
106
- choose, *rest = path
107
- node.updated(
108
- nil, # Don't change node type
109
- node.children.map do |c|
110
- choose.(c) ? update(c, rest, fn) : c
111
- end)
112
- end
113
- end
114
-
115
- # zoom_in :: Node -> [Choice] -> Node
116
- def zoom_in node, path
117
- if path.length == 0 then
118
- node
119
- else
120
- choose, *rest = path
121
- chosen = node.children.select(&choose).first
122
- zoom_in chosen, rest
123
- end
124
- end
125
-
126
- # by_both
127
- # :: Proc (Node -> Bool)
128
- # -> Proc (Node -> Bool)
129
- # -> Proc (Node -> Bool)
130
- def by_both p, q
131
- -> (n) { p.(n) && q.(n) }
132
- end
133
-
134
- # by_msg :: String -> Proc (Node -> Bool)
135
- def by_msg msg
136
- -> (n) { n.children[1] == msg.to_sym }
137
- end
138
-
139
- # by_type :: String -> Proc (Node -> Bool)
140
- def by_type type
141
- -> (n) {
142
- n &&
143
- n.is_a?(AST::Node) &&
144
- n.type == type.to_sym
145
- }
146
- end
147
-
148
- # Matches if one of the node's children matches the given p
149
- # by_child :: Proc (Node -> Bool) -> Proc (Node -> Bool)
150
- def by_child p
151
- -> (n) {
152
- n &&
153
- n.is_a?(AST::Node) &&
154
- n.children.select(&p).size > 0
155
- }
156
- end
157
-
158
- # Matches if this :send node expresses the give sha256 sum
159
- # by_os :: String -> Proc (Node -> Bool)
160
- def by_os os
161
- -> (n) {
162
- zoom_in(n, [
163
- by_type('hash'),
164
- by_type('pair'),
165
- by_type('sym')])
166
- .children[0] == os.to_sym
167
- }
168
- end
169
-
170
- end
171
-
172
4
  end
5
+
6
+ require_relative 'homebrew_automation/bintray.rb'
7
+ require_relative 'homebrew_automation/bottle.rb'
8
+ require_relative 'homebrew_automation/bottle_gatherer.rb'
9
+ require_relative 'homebrew_automation/formula.rb'
10
+ require_relative 'homebrew_automation/mac-os.rb'
11
+ require_relative 'homebrew_automation/source_dist.rb'
12
+ require_relative 'homebrew_automation/tap.rb'
13
+ require_relative 'homebrew_automation/version.rb'
14
+ require_relative 'homebrew_automation/workflow.rb'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: homebrew_automation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - easoncxz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-30 00:00:00.000000000 Z
11
+ date: 2018-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '12.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: thor
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,20 @@ dependencies:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0.20'
69
+ - !ruby/object:Gem::Dependency
70
+ name: http
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: parser
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -80,10 +108,21 @@ dependencies:
80
108
  - - "~>"
81
109
  - !ruby/object:Gem::Version
82
110
  version: '0.2'
83
- description: |2
84
- If you're thinking of manipulating Homebrew Formula files
85
- e.g. during continuous integration, this is for you.
86
- See the GitHub project page for usage details.
111
+ - !ruby/object:Gem::Dependency
112
+ name: rest-client
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ description: Build Bottles and update Formulae. Please see README on Github for details.
87
126
  email: me@easoncxz.com
88
127
  executables:
89
128
  - homebrew_automation.rb
@@ -92,7 +131,15 @@ extra_rdoc_files: []
92
131
  files:
93
132
  - bin/homebrew_automation.rb
94
133
  - lib/homebrew_automation.rb
134
+ - lib/homebrew_automation/bintray.rb
135
+ - lib/homebrew_automation/bottle.rb
136
+ - lib/homebrew_automation/bottle_gatherer.rb
137
+ - lib/homebrew_automation/formula.rb
138
+ - lib/homebrew_automation/mac-os.rb
139
+ - lib/homebrew_automation/source_dist.rb
140
+ - lib/homebrew_automation/tap.rb
95
141
  - lib/homebrew_automation/version.rb
142
+ - lib/homebrew_automation/workflow.rb
96
143
  homepage: https://github.com/easoncxz/homebrew-automation
97
144
  licenses:
98
145
  - GPL-3.0
@@ -113,8 +160,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
160
  version: '0'
114
161
  requirements: []
115
162
  rubyforge_project:
116
- rubygems_version: 2.7.4
163
+ rubygems_version: 2.7.7
117
164
  signing_key:
118
165
  specification_version: 4
119
- summary: Automate editing of Homebrew Formula files
166
+ summary: Build bottles and update Formulae
120
167
  test_files: []