homebrew_automation 0.0.4 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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: []