gembrew 0.1.0 → 0.1.2
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 +4 -4
- data/README.md +80 -16
- data/bin/gembrew +2 -0
- data/lib/gembrew/adder.rb +39 -0
- data/lib/gembrew/cli.rb +2 -0
- data/lib/gembrew/commands/add.rb +20 -0
- data/lib/gembrew/commands/build.rb +15 -4
- data/lib/gembrew/commands/check.rb +16 -5
- data/lib/gembrew/commands/init.rb +6 -4
- data/lib/gembrew/commands/shell.rb +4 -2
- data/lib/gembrew/config.rb +111 -8
- data/lib/gembrew/docker_check.rb +24 -24
- data/lib/gembrew/docker_runner.rb +67 -0
- data/lib/gembrew/docker_shell.rb +6 -24
- data/lib/gembrew/generator.rb +18 -7
- data/lib/gembrew/github_archive_store.rb +51 -0
- data/lib/gembrew/initializer.rb +27 -23
- data/lib/gembrew/reporter.rb +1 -0
- data/lib/gembrew/resolver.rb +62 -8
- data/lib/gembrew/templates/README.md.erb +55 -0
- data/lib/gembrew/templates/formula.rb.erb +24 -6
- data/lib/gembrew/templates/gembrew.yml.erb +17 -3
- data/lib/gembrew/templates/test.yml +65 -0
- data/lib/gembrew/version.rb +1 -1
- data/lib/gembrew.rb +3 -0
- metadata +7 -2
- data/lib/gembrew/templates/compose.yaml.erb +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fe9b9cc5e9535cca862d2adf23aa7887bf0608fa14104d8aa7c3db046358ac61
|
|
4
|
+
data.tar.gz: 7242264cbf2fa6c438445dd180ec73fec06c37513a5d67889ac47dddb748985b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 412fd3b8bc7d1d92ae3358902eacb1e89d6d5a1c2eee5fbb21512b6ea549d1035e438d45855ee8091eda01b9f92f22603c8a089324e82485bafcb124afe4723c
|
|
7
|
+
data.tar.gz: 5fdb7789ec71ce6838cdc82973765441ada2a2c100b9b73ac121c1d3e8aaa77885477d61b578336e6a92b78fc72e6a3acb0bac3cea9be8a2937a8c7710302919
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Gembrew
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
Generate conventional Homebrew formulae for published Ruby command-line gems.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
@@ -18,38 +20,65 @@ gembrew build
|
|
|
18
20
|
gembrew check
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
`init`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
`init` creates a tap repository containing `README.md`, `gembrew/`, `Formula/`,
|
|
24
|
+
and a GitHub Actions workflow that styles, audits, installs, tests, and checks
|
|
25
|
+
the linkage of every formula on Linux and macOS. It accepts an empty directory
|
|
26
|
+
or an existing tap containing `Formula/`.
|
|
27
|
+
Supplying a gem name also creates its first configuration. The gem name is
|
|
28
|
+
optional, so `gembrew init` can prepare an empty tap or add Gembrew to an
|
|
29
|
+
existing one. Existing README and workflow files are never overwritten.
|
|
27
30
|
|
|
28
31
|
For example, `gembrew init example` generates:
|
|
29
32
|
|
|
33
|
+
```text
|
|
34
|
+
README.md
|
|
35
|
+
.github/
|
|
36
|
+
workflows/
|
|
37
|
+
test.yml
|
|
38
|
+
gembrew/
|
|
39
|
+
example.yml
|
|
40
|
+
Formula/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`gembrew/example.yml` contains:
|
|
44
|
+
|
|
30
45
|
```yaml
|
|
31
46
|
gem: example
|
|
32
|
-
output: Formula/example.rb
|
|
33
47
|
|
|
34
48
|
test: |-
|
|
35
49
|
system bin/"example", "--version"
|
|
36
50
|
```
|
|
37
51
|
|
|
38
|
-
Edit
|
|
52
|
+
Edit the configuration as needed, then generate `Formula/example.rb`:
|
|
39
53
|
|
|
40
54
|
```shell
|
|
41
55
|
gembrew build
|
|
42
56
|
```
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
Each YAML file under `gembrew/` describes one formula. Add more published gems
|
|
59
|
+
with:
|
|
60
|
+
|
|
61
|
+
```shell
|
|
62
|
+
gembrew add another-gem
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Plain `build` generates every configured formula. Pass a configuration name to
|
|
66
|
+
generate just one:
|
|
67
|
+
|
|
68
|
+
```shell
|
|
69
|
+
gembrew build example
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The required settings are `gem`, `version`, `source`, and exactly one of `test`
|
|
73
|
+
or `test_from_file`.
|
|
74
|
+
The output defaults to `Formula/NAME.rb`, where `NAME` is the configuration
|
|
75
|
+
filename. Relative paths are resolved from the tap root.
|
|
46
76
|
|
|
47
77
|
```yaml
|
|
48
78
|
gem: example
|
|
49
|
-
output: Formula/example.rb
|
|
50
|
-
|
|
51
|
-
# Optional published gem version. The latest stable version is used by default.
|
|
52
79
|
version: "1.2.3"
|
|
80
|
+
source:
|
|
81
|
+
type: rubygems
|
|
53
82
|
|
|
54
83
|
# Optional gem metadata overrides:
|
|
55
84
|
desc: Example command-line application
|
|
@@ -57,6 +86,14 @@ homepage: https://example.com
|
|
|
57
86
|
license: MIT
|
|
58
87
|
executable: example
|
|
59
88
|
|
|
89
|
+
# Optional additional Homebrew formula dependencies:
|
|
90
|
+
dependencies:
|
|
91
|
+
- bash
|
|
92
|
+
- libffi :system_on_macos
|
|
93
|
+
|
|
94
|
+
# Optional output override. The default for gembrew/example.yml is shown here.
|
|
95
|
+
output: Formula/example.rb
|
|
96
|
+
|
|
60
97
|
test: |-
|
|
61
98
|
system bin/"example", "--version"
|
|
62
99
|
|
|
@@ -64,6 +101,23 @@ test: |-
|
|
|
64
101
|
# test_from_file: support/test.rb
|
|
65
102
|
```
|
|
66
103
|
|
|
104
|
+
Tag a dependency with `:system_on_macos` when macOS provides it and Homebrew
|
|
105
|
+
should install its formula only on other platforms.
|
|
106
|
+
|
|
107
|
+
To build the root gem from a GitHub tag while continuing to fetch its
|
|
108
|
+
dependencies from RubyGems, use:
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
source:
|
|
112
|
+
type: github
|
|
113
|
+
repo: owner/repository
|
|
114
|
+
# tag: v1.2.3
|
|
115
|
+
# gemspec: example.gemspec
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The tag defaults to `vVERSION`; the gemspec path defaults to
|
|
119
|
+
`GEMNAME.gemspec`.
|
|
120
|
+
|
|
67
121
|
Gembrew resolves the generic Ruby dependency graph and generates a Homebrew
|
|
68
122
|
`resource` for every runtime dependency. Original `.gem` archives are reused
|
|
69
123
|
from RubyGems' local cache when available. Downloaded archives are retained in
|
|
@@ -83,6 +137,14 @@ brew style example
|
|
|
83
137
|
brew audit --new --online example
|
|
84
138
|
brew install --build-from-source example
|
|
85
139
|
brew test example
|
|
140
|
+
brew linkage --test example
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
To test a published tap without mounting the local repository, open a pristine
|
|
144
|
+
Homebrew shell:
|
|
145
|
+
|
|
146
|
+
```shell
|
|
147
|
+
gembrew shell --pristine
|
|
86
148
|
```
|
|
87
149
|
|
|
88
150
|
Run the complete workflow non-interactively in one clean container:
|
|
@@ -91,9 +153,11 @@ Run the complete workflow non-interactively in one clean container:
|
|
|
91
153
|
gembrew check
|
|
92
154
|
```
|
|
93
155
|
|
|
94
|
-
This rebuilds
|
|
95
|
-
installation,
|
|
96
|
-
|
|
156
|
+
This rebuilds every configured formula, then runs Homebrew style, online audit,
|
|
157
|
+
source installation, the formula test, and linkage validation in disposable
|
|
158
|
+
Homebrew containers. Use `gembrew check example` to check only one formula.
|
|
159
|
+
Gembrew invokes Docker directly; the tap does not need generated container
|
|
160
|
+
support files.
|
|
97
161
|
|
|
98
162
|
## Contributing / Support
|
|
99
163
|
|
data/bin/gembrew
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'gembrew/config'
|
|
4
|
+
require 'gembrew/error'
|
|
5
|
+
|
|
6
|
+
module Gembrew
|
|
7
|
+
class Adder
|
|
8
|
+
attr_reader :gem_name, :project_path
|
|
9
|
+
|
|
10
|
+
def initialize(gem_name, project_path: Pathname.pwd)
|
|
11
|
+
@gem_name = gem_name
|
|
12
|
+
@project_path = Pathname(project_path).expand_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
Config.validate_name! gem_name
|
|
17
|
+
unless formula_path.directory?
|
|
18
|
+
raise Error, "Directory is not a Homebrew tap: #{project_path} (Formula/ does not exist)"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config_directory.mkpath
|
|
22
|
+
raise Error, "Gem configuration already exists: #{config_path}" if config_path.exist?
|
|
23
|
+
|
|
24
|
+
config_path.write render
|
|
25
|
+
config_path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def formula_path = project_path/'Formula'
|
|
31
|
+
def config_directory = project_path/'gembrew'
|
|
32
|
+
def config_path = config_directory/"#{gem_name}.yml"
|
|
33
|
+
|
|
34
|
+
def render
|
|
35
|
+
template = Pathname(__dir__)/'templates/gembrew.yml.erb'
|
|
36
|
+
ERB.new(template.read, trim_mode: '-').result(binding)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/gembrew/cli.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'mister_bin'
|
|
2
2
|
require 'gembrew/commands/build'
|
|
3
|
+
require 'gembrew/commands/add'
|
|
3
4
|
require 'gembrew/commands/check'
|
|
4
5
|
require 'gembrew/commands/init'
|
|
5
6
|
require 'gembrew/commands/shell'
|
|
@@ -13,6 +14,7 @@ module Gembrew
|
|
|
13
14
|
footer: 'Run m`gembrew COMMAND --help` for more information'
|
|
14
15
|
|
|
15
16
|
runner.route 'init', to: Commands::Init
|
|
17
|
+
runner.route 'add', to: Commands::Add
|
|
16
18
|
runner.route 'build', to: Commands::Build
|
|
17
19
|
runner.route 'check', to: Commands::Check
|
|
18
20
|
runner.route 'shell', to: Commands::Shell
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'mister_bin'
|
|
2
|
+
require 'gembrew/adder'
|
|
3
|
+
|
|
4
|
+
module Gembrew
|
|
5
|
+
module Commands
|
|
6
|
+
class Add < MisterBin::Command
|
|
7
|
+
help 'Add a Ruby gem to this tap repository'
|
|
8
|
+
|
|
9
|
+
usage 'gembrew add GEM'
|
|
10
|
+
usage 'gembrew add (-h|--help)'
|
|
11
|
+
|
|
12
|
+
param 'GEM', 'Published Ruby gem name'
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
path = Adder.new(args['GEM']).call
|
|
16
|
+
say "Added m`#{path}`"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -4,14 +4,25 @@ require 'gembrew/generator'
|
|
|
4
4
|
module Gembrew
|
|
5
5
|
module Commands
|
|
6
6
|
class Build < MisterBin::Command
|
|
7
|
-
help 'Generate
|
|
7
|
+
help 'Generate Homebrew formulae from gembrew/*.yml'
|
|
8
8
|
|
|
9
|
-
usage 'gembrew build'
|
|
9
|
+
usage 'gembrew build [GEM]'
|
|
10
10
|
usage 'gembrew build (-h|--help)'
|
|
11
11
|
|
|
12
|
+
param 'GEM', 'Optional gem configuration name'
|
|
13
|
+
|
|
12
14
|
def run
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
configs.each do |config|
|
|
16
|
+
say "bb`==> gembrew: building #{config.name}`"
|
|
17
|
+
output_path = Generator.new(config: config).call
|
|
18
|
+
say "Generated m`#{output_path}`"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def configs
|
|
25
|
+
args['GEM'] ? [Config.find(args['GEM'])] : Config.all
|
|
15
26
|
end
|
|
16
27
|
end
|
|
17
28
|
end
|
|
@@ -5,17 +5,28 @@ require 'gembrew/generator'
|
|
|
5
5
|
module Gembrew
|
|
6
6
|
module Commands
|
|
7
7
|
class Check < MisterBin::Command
|
|
8
|
-
help 'Build and test
|
|
8
|
+
help 'Build and test formulae in clean Homebrew environments'
|
|
9
9
|
|
|
10
|
-
usage 'gembrew check'
|
|
10
|
+
usage 'gembrew check [GEM]'
|
|
11
11
|
usage 'gembrew check (-h|--help)'
|
|
12
12
|
|
|
13
|
+
param 'GEM', 'Optional gem configuration name'
|
|
14
|
+
|
|
13
15
|
def run
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
configs.each do |config|
|
|
17
|
+
say "bb`==> gembrew: building #{config.name}`"
|
|
18
|
+
output_path = Generator.new(config: config).call
|
|
19
|
+
say "Generated m`#{output_path}`"
|
|
20
|
+
DockerCheck.new.call output_path.basename('.rb').to_s
|
|
21
|
+
end
|
|
17
22
|
say 'g`All checks passed`'
|
|
18
23
|
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def configs
|
|
28
|
+
args['GEM'] ? [Config.find(args['GEM'])] : Config.all
|
|
29
|
+
end
|
|
19
30
|
end
|
|
20
31
|
end
|
|
21
32
|
end
|
|
@@ -4,16 +4,18 @@ require 'gembrew/initializer'
|
|
|
4
4
|
module Gembrew
|
|
5
5
|
module Commands
|
|
6
6
|
class Init < MisterBin::Command
|
|
7
|
-
help 'Initialize a Homebrew tap repository
|
|
7
|
+
help 'Initialize a Homebrew tap repository'
|
|
8
8
|
|
|
9
|
-
usage 'gembrew init GEM'
|
|
9
|
+
usage 'gembrew init [GEM]'
|
|
10
10
|
usage 'gembrew init (-h|--help)'
|
|
11
11
|
|
|
12
|
-
param 'GEM', '
|
|
12
|
+
param 'GEM', 'Optional published Ruby gem name'
|
|
13
13
|
|
|
14
14
|
def run
|
|
15
15
|
Initializer.new(args['GEM']).call
|
|
16
|
-
|
|
16
|
+
message = "Initialized m`#{Dir.pwd}`"
|
|
17
|
+
message += " for bb`#{args['GEM']}`" if args['GEM']
|
|
18
|
+
say message
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|
|
@@ -6,11 +6,13 @@ module Gembrew
|
|
|
6
6
|
class Shell < MisterBin::Command
|
|
7
7
|
help 'Open a Homebrew shell for this tap repository'
|
|
8
8
|
|
|
9
|
-
usage 'gembrew shell'
|
|
9
|
+
usage 'gembrew shell [--pristine]'
|
|
10
10
|
usage 'gembrew shell (-h|--help)'
|
|
11
11
|
|
|
12
|
+
option '-p --pristine', 'Open without mounting the local tap'
|
|
13
|
+
|
|
12
14
|
def run
|
|
13
|
-
DockerShell.new.call
|
|
15
|
+
DockerShell.new.call pristine: args['--pristine']
|
|
14
16
|
end
|
|
15
17
|
end
|
|
16
18
|
end
|
data/lib/gembrew/config.rb
CHANGED
|
@@ -4,32 +4,68 @@ require 'gembrew/error'
|
|
|
4
4
|
|
|
5
5
|
module Gembrew
|
|
6
6
|
class Config
|
|
7
|
-
|
|
7
|
+
Dependency = Data.define(:name, :tags) do
|
|
8
|
+
def system_on_macos? = tags.include? :system_on_macos
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
DEPENDENCY_TAGS = %w[:system_on_macos].freeze
|
|
12
|
+
DIRECTORY = 'gembrew'
|
|
8
13
|
ALLOWED_KEYS = %w[
|
|
9
|
-
gem version output desc homepage license executable test test_from_file
|
|
14
|
+
gem version source output desc homepage license executable dependencies test test_from_file
|
|
10
15
|
].freeze
|
|
11
16
|
|
|
12
|
-
attr_reader :path
|
|
17
|
+
attr_reader :path, :project_path
|
|
18
|
+
|
|
19
|
+
def self.all(project_path = Pathname.pwd)
|
|
20
|
+
project_path = Pathname(project_path).expand_path
|
|
21
|
+
paths = (project_path/DIRECTORY).glob('*.yml').sort
|
|
22
|
+
raise Error, "No gem configurations found in #{project_path/DIRECTORY}" if paths.empty?
|
|
23
|
+
|
|
24
|
+
paths.map { |path| new path, project_path: project_path }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.find(name, project_path = Pathname.pwd)
|
|
28
|
+
validate_name! name
|
|
29
|
+
project_path = Pathname(project_path).expand_path
|
|
30
|
+
new project_path/DIRECTORY/"#{name}.yml", project_path: project_path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.validate_name!(name)
|
|
34
|
+
return name if name&.match?(/\A[a-zA-Z0-9._-]+\z/)
|
|
35
|
+
|
|
36
|
+
raise Error, "Invalid gem name: #{name.inspect}"
|
|
37
|
+
end
|
|
13
38
|
|
|
14
|
-
def initialize(project_path
|
|
15
|
-
@path = Pathname(
|
|
39
|
+
def initialize(path, project_path: nil)
|
|
40
|
+
@path = Pathname(path).expand_path
|
|
41
|
+
@project_path = project_path ? Pathname(project_path).expand_path : @path.dirname.parent
|
|
16
42
|
@data = load_data
|
|
17
43
|
validate
|
|
18
44
|
end
|
|
19
45
|
|
|
46
|
+
def name = path.basename('.yml').to_s
|
|
20
47
|
def gem_name = data.fetch('gem').to_s
|
|
21
48
|
def version = data['version']&.to_s
|
|
22
|
-
def
|
|
49
|
+
def source = data.fetch('source')
|
|
50
|
+
def source_type = source.fetch('type')
|
|
51
|
+
def source_repo = source['repo']
|
|
52
|
+
def source_tag = source['tag'] || "v#{version}"
|
|
53
|
+
def source_gemspec = source['gemspec'] || "#{gem_name}.gemspec"
|
|
54
|
+
def output_path = (project_path/(data['output'] || "Formula/#{name}.rb")).expand_path
|
|
23
55
|
def description = data['desc']
|
|
24
56
|
def homepage = data['homepage']
|
|
25
57
|
def license = data['license']
|
|
26
58
|
def executable = data['executable']
|
|
27
59
|
|
|
60
|
+
def dependencies
|
|
61
|
+
@dependencies ||= data.fetch('dependencies', []).map { |value| parse_dependency value }
|
|
62
|
+
end
|
|
63
|
+
|
|
28
64
|
def test_body
|
|
29
65
|
@test_body ||= if data.has_key?('test')
|
|
30
66
|
data.fetch('test').to_s
|
|
31
67
|
else
|
|
32
|
-
test_path =
|
|
68
|
+
test_path = project_path/data.fetch('test_from_file')
|
|
33
69
|
raise Error, "Test file does not exist: #{test_path}" unless test_path.file?
|
|
34
70
|
|
|
35
71
|
test_path.read
|
|
@@ -55,7 +91,10 @@ module Gembrew
|
|
|
55
91
|
raise Error, "Unknown configuration keys: #{unknown_keys.join(', ')}" if unknown_keys.any?
|
|
56
92
|
|
|
57
93
|
require_value 'gem'
|
|
58
|
-
require_value '
|
|
94
|
+
require_value 'version'
|
|
95
|
+
require_value 'source'
|
|
96
|
+
validate_source
|
|
97
|
+
validate_dependencies
|
|
59
98
|
|
|
60
99
|
return unless data.has_key?('test') == data.has_key?('test_from_file')
|
|
61
100
|
|
|
@@ -66,5 +105,69 @@ module Gembrew
|
|
|
66
105
|
value = data[key]
|
|
67
106
|
raise Error, "#{key} is required" if value.nil? || value.to_s.empty?
|
|
68
107
|
end
|
|
108
|
+
|
|
109
|
+
def validate_source
|
|
110
|
+
raise Error, 'source must be a mapping' unless source.is_a?(Hash)
|
|
111
|
+
|
|
112
|
+
validate_source_keys
|
|
113
|
+
case source['type']&.to_s
|
|
114
|
+
when 'rubygems' then validate_rubygems_source
|
|
115
|
+
when 'github' then validate_github_source
|
|
116
|
+
else raise Error, 'source.type must be github or rubygems'
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def validate_source_keys
|
|
121
|
+
unknown_keys = source.keys - %w[type repo tag gemspec]
|
|
122
|
+
raise Error, "Unknown source keys: #{unknown_keys.join(', ')}" if unknown_keys.any?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def validate_rubygems_source
|
|
126
|
+
extra_keys = source.keys - ['type']
|
|
127
|
+
raise Error, "source.#{extra_keys.first} is only valid for GitHub sources" if extra_keys.any?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def validate_github_source
|
|
131
|
+
repo = source['repo']&.to_s
|
|
132
|
+
raise Error, 'source.repo is required for GitHub sources' if repo.nil? || repo.empty?
|
|
133
|
+
raise Error, 'source.repo must be in owner/repository form' unless repo.match?(%r{\A[^/\s]+/[^/\s]+\z})
|
|
134
|
+
|
|
135
|
+
validate_relative_source_path 'gemspec' if source.has_key?('gemspec')
|
|
136
|
+
raise Error, 'source.tag must not be empty' if source.has_key?('tag') && source['tag'].to_s.empty?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def validate_relative_source_path(key)
|
|
140
|
+
value = source[key].to_s
|
|
141
|
+
path = Pathname(value)
|
|
142
|
+
valid = !value.empty? && !path.absolute? && path.each_filename.none?('..')
|
|
143
|
+
raise Error, "source.#{key} must be a relative path within the archive" unless valid
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def validate_dependencies
|
|
147
|
+
return unless data.has_key?('dependencies')
|
|
148
|
+
|
|
149
|
+
raise Error, 'dependencies must be a sequence' unless data['dependencies'].is_a?(Array)
|
|
150
|
+
|
|
151
|
+
dependencies
|
|
152
|
+
names = dependencies.map(&:name)
|
|
153
|
+
duplicate = names.find { |name| names.count(name) > 1 }
|
|
154
|
+
raise Error, "dependency #{duplicate.inspect} is specified more than once" if duplicate
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def parse_dependency(value)
|
|
158
|
+
raise Error, 'each dependency must be a name followed by optional tags' unless value.is_a?(String)
|
|
159
|
+
|
|
160
|
+
values = value.split
|
|
161
|
+
name = values.shift
|
|
162
|
+
unless name && !name.start_with?(':')
|
|
163
|
+
raise Error, 'dependency name must be a non-empty string'
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
invalid_tag = values.find { |tag| !tag.is_a?(String) || !DEPENDENCY_TAGS.include?(tag) }
|
|
167
|
+
raise Error, "unknown dependency tag: #{invalid_tag.inspect}" if invalid_tag
|
|
168
|
+
raise Error, "dependency #{name.inspect} has duplicate tags" unless values.uniq.length == values.length
|
|
169
|
+
|
|
170
|
+
Dependency.new(name:, tags: values.map { |tag| tag.delete_prefix(':').to_sym }.freeze)
|
|
171
|
+
end
|
|
69
172
|
end
|
|
70
173
|
end
|
data/lib/gembrew/docker_check.rb
CHANGED
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
require 'gembrew/docker_runner'
|
|
2
3
|
require 'gembrew/error'
|
|
3
4
|
|
|
4
5
|
module Gembrew
|
|
5
6
|
class DockerCheck
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
attr_reader :project_path
|
|
9
|
-
|
|
10
|
-
def initialize(project_path: Pathname.pwd, command_runner: nil)
|
|
11
|
-
@project_path = Pathname(project_path).expand_path
|
|
12
|
-
@command_runner = command_runner || method(:run_command)
|
|
7
|
+
def initialize(runner: DockerRunner.new)
|
|
8
|
+
@runner = runner
|
|
13
9
|
end
|
|
14
10
|
|
|
15
|
-
def call
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
success = command_runner.call(
|
|
19
|
-
'docker', 'compose', '-f', COMPOSE_PATH.to_s,
|
|
20
|
-
'run', '--rm', '--no-TTY', 'check',
|
|
21
|
-
chdir: project_path
|
|
22
|
-
)
|
|
11
|
+
def call(formula)
|
|
12
|
+
success = runner.call 'bash', '-euc', script(formula)
|
|
23
13
|
raise Error, 'Homebrew checks failed' unless success
|
|
24
14
|
|
|
25
15
|
true
|
|
@@ -27,14 +17,24 @@ module Gembrew
|
|
|
27
17
|
|
|
28
18
|
private
|
|
29
19
|
|
|
30
|
-
attr_reader :
|
|
31
|
-
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
20
|
+
attr_reader :runner
|
|
21
|
+
|
|
22
|
+
def script(formula)
|
|
23
|
+
formula = Shellwords.escape formula
|
|
24
|
+
<<~BASH
|
|
25
|
+
caption() { printf '\\n\\033[1;34m==> gembrew: %s\\033[0m\\n' "$1"; }
|
|
26
|
+
formula=#{formula}
|
|
27
|
+
caption "styling $formula"
|
|
28
|
+
brew style "$formula"
|
|
29
|
+
caption "auditing $formula"
|
|
30
|
+
brew audit --new --online "$formula"
|
|
31
|
+
caption "installing $formula"
|
|
32
|
+
brew install --build-from-source "$formula"
|
|
33
|
+
caption "testing $formula"
|
|
34
|
+
brew test "$formula"
|
|
35
|
+
caption "checking linkage for $formula"
|
|
36
|
+
brew linkage --test "$formula"
|
|
37
|
+
BASH
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module Gembrew
|
|
4
|
+
class DockerRunner
|
|
5
|
+
IMAGE = 'homebrew/brew:main'
|
|
6
|
+
TAP_PATH = '/home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/gembrew/homebrew-tap'
|
|
7
|
+
|
|
8
|
+
attr_reader :project_path
|
|
9
|
+
|
|
10
|
+
def initialize(project_path: Pathname.pwd, command_runner: nil)
|
|
11
|
+
@project_path = Pathname(project_path).expand_path
|
|
12
|
+
@command_runner = command_runner || method(:run_command)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(*command, interactive: false, pristine: false)
|
|
16
|
+
docker_command = ['docker', 'run', '--rm', '--init', '--name', container_name]
|
|
17
|
+
docker_command << '-it' if interactive
|
|
18
|
+
docker_command.concat environment(interactive: interactive, pristine: pristine)
|
|
19
|
+
unless pristine
|
|
20
|
+
docker_command.push '--workdir', '/work'
|
|
21
|
+
docker_command.push '--volume', "#{project_path}:/work"
|
|
22
|
+
docker_command.push '--volume', "#{project_path}:#{TAP_PATH}"
|
|
23
|
+
end
|
|
24
|
+
docker_command << IMAGE
|
|
25
|
+
docker_command.concat command
|
|
26
|
+
command_runner.call(*docker_command, chdir: project_path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :command_runner
|
|
32
|
+
|
|
33
|
+
def environment(interactive:, pristine:)
|
|
34
|
+
result = %w[
|
|
35
|
+
--env HOMEBREW_NO_AUTO_UPDATE=1
|
|
36
|
+
--env HOMEBREW_NO_INSTALL_CLEANUP=1
|
|
37
|
+
--env HOMEBREW_NO_INSTALL_FROM_API=1
|
|
38
|
+
]
|
|
39
|
+
prompt = pristine ? 'homebrew' : 'gembrew'
|
|
40
|
+
result.push '--env', "PS1=\\n\\n#{prompt} $ " if interactive
|
|
41
|
+
result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def run_command(*command, chdir:)
|
|
45
|
+
success = system(*command, chdir: chdir.to_s)
|
|
46
|
+
cleanup_container command unless success
|
|
47
|
+
success
|
|
48
|
+
rescue Interrupt
|
|
49
|
+
previous_handler = Signal.trap 'INT', 'IGNORE'
|
|
50
|
+
begin
|
|
51
|
+
cleanup_container command
|
|
52
|
+
ensure
|
|
53
|
+
Signal.trap 'INT', previous_handler
|
|
54
|
+
end
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def container_name
|
|
59
|
+
"gembrew-#{Process.pid}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def cleanup_container(command)
|
|
63
|
+
name = command.fetch(command.index('--name') + 1)
|
|
64
|
+
system 'docker', 'rm', '--force', name, out: File::NULL, err: File::NULL
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/gembrew/docker_shell.rb
CHANGED
|
@@ -1,24 +1,14 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'gembrew/docker_runner'
|
|
2
2
|
require 'gembrew/error'
|
|
3
3
|
|
|
4
4
|
module Gembrew
|
|
5
5
|
class DockerShell
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
attr_reader :project_path
|
|
9
|
-
|
|
10
|
-
def initialize(project_path: Pathname.pwd, command_runner: nil)
|
|
11
|
-
@project_path = Pathname(project_path).expand_path
|
|
12
|
-
@command_runner = command_runner || method(:run_command)
|
|
6
|
+
def initialize(runner: DockerRunner.new)
|
|
7
|
+
@runner = runner
|
|
13
8
|
end
|
|
14
9
|
|
|
15
|
-
def call
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
success = command_runner.call(
|
|
19
|
-
'docker', 'compose', '-f', COMPOSE_PATH.to_s, 'run', '--rm', 'brew',
|
|
20
|
-
chdir: project_path
|
|
21
|
-
)
|
|
10
|
+
def call(pristine: false)
|
|
11
|
+
success = runner.call 'bash', '--norc', interactive: true, pristine: pristine
|
|
22
12
|
raise Error, 'Homebrew shell exited with an error' unless success
|
|
23
13
|
|
|
24
14
|
true
|
|
@@ -26,14 +16,6 @@ module Gembrew
|
|
|
26
16
|
|
|
27
17
|
private
|
|
28
18
|
|
|
29
|
-
attr_reader :
|
|
30
|
-
|
|
31
|
-
def compose_path
|
|
32
|
-
project_path/COMPOSE_PATH
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def run_command(*command, chdir:)
|
|
36
|
-
system(*command, chdir: chdir.to_s)
|
|
37
|
-
end
|
|
19
|
+
attr_reader :runner
|
|
38
20
|
end
|
|
39
21
|
end
|
data/lib/gembrew/generator.rb
CHANGED
|
@@ -4,14 +4,14 @@ require 'gembrew/resolver'
|
|
|
4
4
|
|
|
5
5
|
module Gembrew
|
|
6
6
|
class Generator
|
|
7
|
-
def initialize(config
|
|
7
|
+
def initialize(config:, resolver: Resolver.new, renderer: Renderer.new)
|
|
8
8
|
@config = config
|
|
9
9
|
@resolver = resolver
|
|
10
10
|
@renderer = renderer
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def call
|
|
14
|
-
resolution = resolver.resolve(config.gem_name, config.version)
|
|
14
|
+
resolution = resolver.resolve(config.gem_name, config.version, source: config.source)
|
|
15
15
|
output_path = config.output_path
|
|
16
16
|
output_path.dirname.mkpath
|
|
17
17
|
output_path.write renderer.render(formula_data(resolution))
|
|
@@ -35,14 +35,25 @@ module Gembrew
|
|
|
35
35
|
|
|
36
36
|
def metadata(spec)
|
|
37
37
|
{
|
|
38
|
-
description:
|
|
39
|
-
homepage:
|
|
40
|
-
license:
|
|
41
|
-
executable:
|
|
42
|
-
|
|
38
|
+
description: config.description || spec.summary,
|
|
39
|
+
homepage: config.homepage || inferred_homepage(spec),
|
|
40
|
+
license: config.license || spec.license || spec.licenses.first,
|
|
41
|
+
executable: config.executable || spec.executables.first || spec.name,
|
|
42
|
+
source_type: config.source_type,
|
|
43
|
+
source_repo: config.source_repo,
|
|
44
|
+
source_tag: config.source_tag,
|
|
45
|
+
source_gemspec: config.source_gemspec,
|
|
46
|
+
dependencies: formula_dependencies,
|
|
47
|
+
test_body: config.test_body,
|
|
43
48
|
}
|
|
44
49
|
end
|
|
45
50
|
|
|
51
|
+
def formula_dependencies
|
|
52
|
+
([Config::Dependency.new(name: 'ruby', tags: [])] + config.dependencies)
|
|
53
|
+
.uniq(&:name)
|
|
54
|
+
.sort_by(&:name)
|
|
55
|
+
end
|
|
56
|
+
|
|
46
57
|
def formula_name(gem_name)
|
|
47
58
|
gem_name.split(/[-_]/).map(&:capitalize).join
|
|
48
59
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'open3'
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'gembrew/error'
|
|
5
|
+
require 'gembrew/reporter'
|
|
6
|
+
|
|
7
|
+
module Gembrew
|
|
8
|
+
class GithubArchiveStore
|
|
9
|
+
def initialize(cache_path:, reporter: Reporter.new, command_runner: nil)
|
|
10
|
+
@cache_path = Pathname(cache_path)
|
|
11
|
+
@reporter = reporter
|
|
12
|
+
@command_runner = command_runner || method(:run_command)
|
|
13
|
+
@cache_path.mkpath
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fetch(repository, tag)
|
|
17
|
+
name = repository.tr '/', '-'
|
|
18
|
+
path = @cache_path/"#{name}-#{tag.tr '/', '-'}.tar.gz"
|
|
19
|
+
if path.file? && !path.empty?
|
|
20
|
+
reporter.archive :github_cache, repository, tag
|
|
21
|
+
return path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
reporter.archive :downloading, repository, tag
|
|
25
|
+
url = "https://github.com/#{repository}/archive/refs/tags/#{tag}.tar.gz"
|
|
26
|
+
command_runner.call 'curl', '--fail', '--location', '--silent', '--show-error',
|
|
27
|
+
'--output', path.to_s, url, chdir: @cache_path
|
|
28
|
+
raise Error, "GitHub archive is empty: #{url}" unless path.file? && !path.empty?
|
|
29
|
+
|
|
30
|
+
path
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def extract(archive, destination)
|
|
34
|
+
destination = Pathname(destination)
|
|
35
|
+
destination.mkpath
|
|
36
|
+
command_runner.call 'tar', '-xzf', archive.to_s, '-C', destination.to_s, chdir: destination
|
|
37
|
+
destination
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :reporter, :command_runner
|
|
43
|
+
|
|
44
|
+
def run_command(*command, chdir:)
|
|
45
|
+
result, status = Open3.capture2e(*command, chdir: chdir.to_s)
|
|
46
|
+
raise Error, result unless status.success?
|
|
47
|
+
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/gembrew/initializer.rb
CHANGED
|
@@ -1,63 +1,67 @@
|
|
|
1
1
|
require 'erb'
|
|
2
2
|
require 'fileutils'
|
|
3
3
|
require 'pathname'
|
|
4
|
+
require 'gembrew/adder'
|
|
4
5
|
require 'gembrew/error'
|
|
5
6
|
|
|
6
7
|
module Gembrew
|
|
7
8
|
class Initializer
|
|
8
|
-
CONFIG_FILENAME = 'gembrew.yml'
|
|
9
|
-
|
|
10
9
|
attr_reader :gem_name, :project_path
|
|
11
10
|
|
|
12
|
-
def initialize(gem_name, project_path: Pathname.pwd)
|
|
11
|
+
def initialize(gem_name = nil, project_path: Pathname.pwd)
|
|
13
12
|
@gem_name = gem_name
|
|
14
13
|
@project_path = Pathname(project_path).expand_path
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def call
|
|
18
|
-
|
|
17
|
+
Config.validate_name! gem_name if gem_name
|
|
18
|
+
validate_project
|
|
19
19
|
|
|
20
20
|
formula_path.mkpath
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
config_directory.mkpath
|
|
22
|
+
readme_path.write render_readme unless readme_path.exist?
|
|
23
|
+
workflow_path.dirname.mkpath
|
|
24
|
+
workflow_path.write workflow_template.read unless workflow_path.exist?
|
|
25
|
+
Adder.new(gem_name, project_path: project_path).call if gem_name
|
|
24
26
|
|
|
25
27
|
project_path
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
private
|
|
29
31
|
|
|
30
|
-
def
|
|
31
|
-
project_path/
|
|
32
|
+
def formula_path
|
|
33
|
+
project_path/'Formula'
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
+
def config_directory
|
|
37
|
+
project_path/'gembrew'
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
def
|
|
39
|
-
project_path/'
|
|
40
|
+
def readme_path
|
|
41
|
+
project_path/'README.md'
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def
|
|
43
|
-
project_path/'
|
|
44
|
+
def workflow_path
|
|
45
|
+
project_path/'.github/workflows/test.yml'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def workflow_template
|
|
49
|
+
Pathname(__dir__)/'templates/test.yml'
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
def entries
|
|
47
53
|
@entries ||= project_path.children.reject { |path| path.basename.to_s == '.git' }
|
|
48
54
|
end
|
|
49
55
|
|
|
50
|
-
def
|
|
51
|
-
entries.
|
|
52
|
-
end
|
|
56
|
+
def validate_project
|
|
57
|
+
return if entries.empty? || formula_path.directory?
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
template = asset_path filename
|
|
56
|
-
ERB.new(template.read, trim_mode: '-').result(binding)
|
|
59
|
+
raise Error, "Directory is not a Homebrew tap: #{project_path} (Formula/ does not exist)"
|
|
57
60
|
end
|
|
58
61
|
|
|
59
|
-
def
|
|
60
|
-
Pathname(__dir__)/'templates'
|
|
62
|
+
def render_readme
|
|
63
|
+
template = Pathname(__dir__)/'templates/README.md.erb'
|
|
64
|
+
ERB.new(template.read, trim_mode: '-').result(binding)
|
|
61
65
|
end
|
|
62
66
|
end
|
|
63
67
|
end
|
data/lib/gembrew/reporter.rb
CHANGED
data/lib/gembrew/resolver.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'rubygems/package'
|
|
|
6
6
|
require 'tmpdir'
|
|
7
7
|
require 'gembrew/archive_store'
|
|
8
8
|
require 'gembrew/error'
|
|
9
|
+
require 'gembrew/github_archive_store'
|
|
9
10
|
require 'gembrew/reporter'
|
|
10
11
|
|
|
11
12
|
module Gembrew
|
|
@@ -15,21 +16,22 @@ module Gembrew
|
|
|
15
16
|
class Resolver
|
|
16
17
|
attr_reader :cache_root
|
|
17
18
|
|
|
18
|
-
def initialize(cache_root: nil, reporter: Reporter.new, command_runner: nil, archive_store: nil
|
|
19
|
+
def initialize(cache_root: nil, reporter: Reporter.new, command_runner: nil, archive_store: nil,
|
|
20
|
+
github_archive_store: nil)
|
|
19
21
|
@cache_root = Pathname(cache_root || default_cache_root)
|
|
20
22
|
@reporter = reporter
|
|
21
23
|
@command_runner = command_runner || method(:run_command)
|
|
22
24
|
@archive_store = archive_store || ArchiveStore.new(
|
|
23
25
|
cache_path: @cache_root/'gems', reporter: reporter, command_runner: @command_runner
|
|
24
26
|
)
|
|
27
|
+
@github_archive_store = github_archive_store
|
|
25
28
|
temporary_root.mkpath
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
def resolve(name, version
|
|
31
|
+
def resolve(name, version, source:)
|
|
29
32
|
Dir.mktmpdir('build-', temporary_root.to_s) do |directory|
|
|
30
33
|
temporary = Pathname(directory)
|
|
31
|
-
root_archive =
|
|
32
|
-
root_spec = Gem::Package.new(root_archive.to_s).spec
|
|
34
|
+
root_archive, root_spec = root_source name, version, source, temporary
|
|
33
35
|
dependencies = dependency_specs(lock(root_spec, temporary), root_spec)
|
|
34
36
|
|
|
35
37
|
reporter.collecting dependencies.length
|
|
@@ -45,6 +47,57 @@ module Gembrew
|
|
|
45
47
|
|
|
46
48
|
attr_reader :reporter, :command_runner, :archive_store
|
|
47
49
|
|
|
50
|
+
def github_archive_store
|
|
51
|
+
@github_archive_store ||= GithubArchiveStore.new(
|
|
52
|
+
cache_path: cache_root/'github', reporter: reporter, command_runner: command_runner
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def root_source(name, version, source_config, temporary)
|
|
57
|
+
return rubygems_root_source(name, version, temporary) if source_config.fetch('type') == 'rubygems'
|
|
58
|
+
|
|
59
|
+
github_root_source name, version, source_config, temporary
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rubygems_root_source(name, version, temporary)
|
|
63
|
+
archive = archive_store.fetch(name, version, temporary)
|
|
64
|
+
[archive, Gem::Package.new(archive.to_s).spec]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def github_root_source(name, version, source_config, temporary)
|
|
68
|
+
repository = source_config.fetch('repo')
|
|
69
|
+
tag = source_config['tag'] || "v#{version}"
|
|
70
|
+
gemspec_path = source_config['gemspec'] || "#{name}.gemspec"
|
|
71
|
+
archive = github_archive_store.fetch repository, tag
|
|
72
|
+
root = github_project_root archive, temporary
|
|
73
|
+
spec = load_github_spec root/gemspec_path, gemspec_path
|
|
74
|
+
validate_github_spec spec, name, version
|
|
75
|
+
|
|
76
|
+
[archive, spec]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def github_project_root(archive, temporary)
|
|
80
|
+
extracted = github_archive_store.extract archive, temporary/'source'
|
|
81
|
+
roots = extracted.children.select(&:directory?)
|
|
82
|
+
raise Error, 'Expected one project directory in the GitHub archive' unless roots.one?
|
|
83
|
+
|
|
84
|
+
roots.first
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def load_github_spec(gemspec, gemspec_path)
|
|
88
|
+
raise Error, "Gemspec not found in GitHub archive: #{gemspec_path}" unless gemspec.file?
|
|
89
|
+
|
|
90
|
+
spec = Dir.chdir(gemspec.dirname) { Gem::Specification.load(gemspec.basename.to_s) }
|
|
91
|
+
raise Error, "Could not load gemspec from GitHub archive: #{gemspec_path}" unless spec
|
|
92
|
+
|
|
93
|
+
spec
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate_github_spec(spec, name, version)
|
|
97
|
+
raise Error, "Gemspec name #{spec.name} does not match configured gem #{name}" unless spec.name == name
|
|
98
|
+
raise Error, "GitHub gemspec version #{spec.version} does not match #{version}" unless spec.version == Gem::Version.new(version)
|
|
99
|
+
end
|
|
100
|
+
|
|
48
101
|
def resource(spec, temporary, index, total)
|
|
49
102
|
archive = archive_store.fetch spec.name, spec.version, temporary, index: index, total: total
|
|
50
103
|
Resource.new spec.name, spec.version.to_s, Digest::SHA256.file(archive).hexdigest
|
|
@@ -62,10 +115,11 @@ module Gembrew
|
|
|
62
115
|
def lock(root_spec, temporary)
|
|
63
116
|
gemfile = temporary/'Gemfile'
|
|
64
117
|
lockfile = temporary/'Gemfile.lock'
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
gem #{
|
|
68
|
-
|
|
118
|
+
dependencies = root_spec.runtime_dependencies.map do |dependency|
|
|
119
|
+
requirements = dependency.requirement.requirements.map { |operator, version| "#{operator} #{version}" }
|
|
120
|
+
"gem #{dependency.name.inspect}, #{requirements.map(&:inspect).join(', ')}"
|
|
121
|
+
end
|
|
122
|
+
gemfile.write([[%[source "https://rubygems.org"]], dependencies, ['']].flatten.join("\n"))
|
|
69
123
|
|
|
70
124
|
reporter.resolving root_spec.name, root_spec.version
|
|
71
125
|
command_runner.call(
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Homebrew Tap
|
|
2
|
+
|
|
3
|
+
<!-- Replace OWNER/TAP with your Homebrew tap name, for example dannyben/tools. -->
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
brew tap OWNER/TAP
|
|
9
|
+
brew install <%= gem_name || 'FORMULA' %>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or install the formula directly:
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
brew install OWNER/TAP/<%= gem_name || 'FORMULA' %>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Development
|
|
19
|
+
|
|
20
|
+
Install [Gembrew](https://github.com/DannyBen/gembrew):
|
|
21
|
+
|
|
22
|
+
```shell
|
|
23
|
+
gem install gembrew
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Generate all formulae:
|
|
27
|
+
|
|
28
|
+
```shell
|
|
29
|
+
gembrew build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Generate or check one formula:
|
|
33
|
+
|
|
34
|
+
```shell
|
|
35
|
+
gembrew build <%= gem_name || 'FORMULA' %>
|
|
36
|
+
gembrew check <%= gem_name || 'FORMULA' %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Check every formula:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
gembrew check
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Add another Ruby gem:
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
gembrew add another-gem
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Open an interactive Homebrew environment:
|
|
52
|
+
|
|
53
|
+
```shell
|
|
54
|
+
gembrew shell
|
|
55
|
+
```
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
# Generated by Gembrew: https://github.com/dannyben/gembrew
|
|
2
|
-
# Manual edits are not recommended. Update gembrew.yml and regenerate with:
|
|
3
|
-
# gem install gembrew
|
|
4
|
-
# gembrew build
|
|
5
|
-
|
|
6
1
|
class <%= formula_name %> < Formula
|
|
7
2
|
desc <%= description.inspect %>
|
|
8
3
|
homepage <%= homepage.inspect %>
|
|
4
|
+
<% if source_type == 'github' -%>
|
|
5
|
+
url "https://github.com/<%= source_repo %>/archive/refs/tags/<%= source_tag %>.tar.gz"
|
|
6
|
+
<% else -%>
|
|
9
7
|
url "https://rubygems.org/downloads/<%= gem_name %>-<%= version %>.gem"
|
|
8
|
+
<% end -%>
|
|
10
9
|
sha256 "<%= sha256 %>"
|
|
11
10
|
license <%= license.inspect %>
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
<% formula_dependencies = dependencies.reject(&:system_on_macos?) -%>
|
|
13
|
+
<% macos_dependencies = dependencies.select(&:system_on_macos?) -%>
|
|
14
|
+
<% formula_dependencies.each do |dependency| -%>
|
|
15
|
+
depends_on <%= dependency.name.inspect %>
|
|
16
|
+
<% end -%>
|
|
17
|
+
<% if formula_dependencies.any? && macos_dependencies.any? -%>
|
|
18
|
+
|
|
19
|
+
<% end -%>
|
|
20
|
+
<% macos_dependencies.each do |dependency| -%>
|
|
21
|
+
uses_from_macos <%= dependency.name.inspect %>
|
|
22
|
+
<% end -%>
|
|
14
23
|
<% resources.each do |resource| -%>
|
|
15
24
|
|
|
16
25
|
resource <%= resource.name.inspect %> do
|
|
@@ -27,8 +36,17 @@ class <%= formula_name %> < Formula
|
|
|
27
36
|
"--ignore-dependencies", "--install-dir", libexec, "--no-document"
|
|
28
37
|
end
|
|
29
38
|
|
|
39
|
+
<% if source_type == 'github' -%>
|
|
40
|
+
system "gem", "build", "<%= source_gemspec %>"
|
|
41
|
+
system "gem", "install", "--ignore-dependencies", "<%= gem_name %>-#{version}.gem",
|
|
42
|
+
"--install-dir", libexec, "--no-document"
|
|
43
|
+
<% else -%>
|
|
30
44
|
system "gem", "install", cached_download,
|
|
31
45
|
"--ignore-dependencies", "--install-dir", libexec, "--no-document"
|
|
46
|
+
<% end -%>
|
|
47
|
+
|
|
48
|
+
rm libexec.glob("extensions/*/*/*/mkmf.log")
|
|
49
|
+
deuniversalize_machos if OS.mac?
|
|
32
50
|
|
|
33
51
|
(bin/<%= executable.inspect %>).write_env_script libexec/"bin/<%= executable %>", GEM_HOME: ENV.fetch("GEM_HOME")
|
|
34
52
|
end
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
gem: <%= gem_name %>
|
|
2
|
-
|
|
2
|
+
version: "" # required
|
|
3
|
+
|
|
4
|
+
# Required source. Use the published RubyGem:
|
|
5
|
+
source:
|
|
6
|
+
type: rubygems
|
|
7
|
+
|
|
8
|
+
# Or replace the source above with a GitHub tag archive:
|
|
9
|
+
# source:
|
|
10
|
+
# type: github
|
|
11
|
+
# repo: owner/repository # required for GitHub
|
|
12
|
+
# tag: v1.2.3 # optional; defaults to vVERSION
|
|
13
|
+
# gemspec: <%= gem_name %>.gemspec # optional; defaults to GEMNAME.gemspec
|
|
3
14
|
|
|
4
15
|
test: |-
|
|
5
16
|
system bin/"<%= gem_name %>", "--version"
|
|
6
17
|
|
|
7
|
-
# Optional overrides:
|
|
8
|
-
# version: "1.2.3"
|
|
18
|
+
# Optional metadata and formula overrides:
|
|
9
19
|
# desc: Example command-line application
|
|
10
20
|
# homepage: https://example.com
|
|
11
21
|
# license: MIT
|
|
12
22
|
# executable: <%= gem_name %>
|
|
23
|
+
# output: Formula/<%= gem_name %>.rb
|
|
24
|
+
# dependencies:
|
|
25
|
+
# - bash
|
|
26
|
+
# - libffi :system_on_macos
|
|
13
27
|
|
|
14
28
|
# To load the Homebrew test body from a Ruby file, replace `test` above with:
|
|
15
29
|
# test_from_file: support/test.rb
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
name: Test Formulae
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: ${{ matrix.os }}
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
os:
|
|
22
|
+
- ubuntu-latest
|
|
23
|
+
- macos-latest
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Set up Homebrew
|
|
27
|
+
id: set-up-homebrew
|
|
28
|
+
uses: Homebrew/actions/setup-homebrew@fd832223f9f99ebf0244dd20658680e5d4aca049 # 2026.08.03.2
|
|
29
|
+
|
|
30
|
+
- name: Cache Homebrew Bundler gems
|
|
31
|
+
id: cache
|
|
32
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
33
|
+
with:
|
|
34
|
+
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
|
|
35
|
+
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
|
|
36
|
+
restore-keys: ${{ runner.os }}-rubygems-
|
|
37
|
+
|
|
38
|
+
- name: Install Homebrew Bundler gems
|
|
39
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
40
|
+
run: brew install-bundler-gems
|
|
41
|
+
|
|
42
|
+
- name: Test formulae
|
|
43
|
+
shell: bash
|
|
44
|
+
env:
|
|
45
|
+
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
46
|
+
run: |
|
|
47
|
+
shopt -s nullglob
|
|
48
|
+
formulae=(Formula/*.rb)
|
|
49
|
+
|
|
50
|
+
if (( ${#formulae[@]} == 0 )); then
|
|
51
|
+
echo "No formulae found in Formula/"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
for formula in "${formulae[@]}"; do
|
|
56
|
+
formula_name="${formula##*/}"
|
|
57
|
+
formula_name="${formula_name%.rb}"
|
|
58
|
+
|
|
59
|
+
brew style "$formula"
|
|
60
|
+
brew audit --new --online "$formula_name"
|
|
61
|
+
brew install --build-from-source "$formula_name"
|
|
62
|
+
brew test "$formula_name"
|
|
63
|
+
brew linkage --test "$formula_name"
|
|
64
|
+
brew uninstall "$formula_name"
|
|
65
|
+
done
|
data/lib/gembrew/version.rb
CHANGED
data/lib/gembrew.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
require 'gembrew/archive_store'
|
|
2
|
+
require 'gembrew/adder'
|
|
2
3
|
require 'gembrew/config'
|
|
3
4
|
require 'gembrew/docker_check'
|
|
4
5
|
require 'gembrew/docker_shell'
|
|
6
|
+
require 'gembrew/docker_runner'
|
|
5
7
|
require 'gembrew/error'
|
|
6
8
|
require 'gembrew/generator'
|
|
9
|
+
require 'gembrew/github_archive_store'
|
|
7
10
|
require 'gembrew/initializer'
|
|
8
11
|
require 'gembrew/reporter'
|
|
9
12
|
require 'gembrew/renderer'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gembrew
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Danny Ben Shitrit
|
|
@@ -63,24 +63,29 @@ files:
|
|
|
63
63
|
- README.md
|
|
64
64
|
- bin/gembrew
|
|
65
65
|
- lib/gembrew.rb
|
|
66
|
+
- lib/gembrew/adder.rb
|
|
66
67
|
- lib/gembrew/archive_store.rb
|
|
67
68
|
- lib/gembrew/cli.rb
|
|
69
|
+
- lib/gembrew/commands/add.rb
|
|
68
70
|
- lib/gembrew/commands/build.rb
|
|
69
71
|
- lib/gembrew/commands/check.rb
|
|
70
72
|
- lib/gembrew/commands/init.rb
|
|
71
73
|
- lib/gembrew/commands/shell.rb
|
|
72
74
|
- lib/gembrew/config.rb
|
|
73
75
|
- lib/gembrew/docker_check.rb
|
|
76
|
+
- lib/gembrew/docker_runner.rb
|
|
74
77
|
- lib/gembrew/docker_shell.rb
|
|
75
78
|
- lib/gembrew/error.rb
|
|
76
79
|
- lib/gembrew/generator.rb
|
|
80
|
+
- lib/gembrew/github_archive_store.rb
|
|
77
81
|
- lib/gembrew/initializer.rb
|
|
78
82
|
- lib/gembrew/renderer.rb
|
|
79
83
|
- lib/gembrew/reporter.rb
|
|
80
84
|
- lib/gembrew/resolver.rb
|
|
81
|
-
- lib/gembrew/templates/
|
|
85
|
+
- lib/gembrew/templates/README.md.erb
|
|
82
86
|
- lib/gembrew/templates/formula.rb.erb
|
|
83
87
|
- lib/gembrew/templates/gembrew.yml.erb
|
|
88
|
+
- lib/gembrew/templates/test.yml
|
|
84
89
|
- lib/gembrew/version.rb
|
|
85
90
|
homepage: https://github.com/DannyBen/gembrew
|
|
86
91
|
licenses:
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
services:
|
|
2
|
-
brew: &base
|
|
3
|
-
image: homebrew/brew:main
|
|
4
|
-
working_dir: /work
|
|
5
|
-
environment:
|
|
6
|
-
HOMEBREW_NO_AUTO_UPDATE: "1"
|
|
7
|
-
HOMEBREW_NO_INSTALL_CLEANUP: "1"
|
|
8
|
-
HOMEBREW_NO_INSTALL_FROM_API: "1"
|
|
9
|
-
GEMBREW_GEM: <%= gem_name %>
|
|
10
|
-
PS1: "\n\nbrew $ "
|
|
11
|
-
volumes:
|
|
12
|
-
- ..:/work
|
|
13
|
-
- ..:/home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/gembrew/homebrew-tap
|
|
14
|
-
command: ["bash", "--norc"]
|
|
15
|
-
stdin_open: true
|
|
16
|
-
tty: true
|
|
17
|
-
|
|
18
|
-
check:
|
|
19
|
-
<<: *base
|
|
20
|
-
command:
|
|
21
|
-
- bash
|
|
22
|
-
- -euc
|
|
23
|
-
- |
|
|
24
|
-
brew style "$${GEMBREW_GEM}"
|
|
25
|
-
brew audit --new --online "$${GEMBREW_GEM}"
|
|
26
|
-
brew install --build-from-source "$${GEMBREW_GEM}"
|
|
27
|
-
brew test "$${GEMBREW_GEM}"
|
|
28
|
-
stdin_open: false
|
|
29
|
-
tty: false
|