boringbuilder 0.1.0.alpha.1 → 0.1.0.alpha.3
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/CHANGELOG.md +10 -0
- data/lib/boringbuilder/builder.rb +1 -1
- data/lib/boringbuilder/cli.rb +12 -10
- data/lib/boringbuilder/exporter.rb +43 -12
- data/lib/boringbuilder/mise.rb +1 -1
- data/lib/boringbuilder/pipeline.rb +5 -2
- data/lib/boringbuilder/project.rb +2 -1
- data/lib/boringbuilder/ruby_build.rb +71 -34
- data/lib/boringbuilder/version.rb +1 -1
- data/lib/boringbuilder.rb +2 -1
- metadata +8 -12
- data/lib/boringbuilder/build_progress.rb +0 -90
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 823ab9ebc22d049c27671d07ea1be44315dbdf3f63cdd29c46cd209e6e5fc279
|
|
4
|
+
data.tar.gz: 58c38219ef59ff3bf7515d10140be7500784bf276f5b0ea71edd965d7ae10b86
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7812ac305fa0015e0007049f6272a81fcf237db37304522323bad5a7a9947ac2f6e77d9e9788cf57490c31d680c17bda5aa3cc025cc19a4b310f9711b080d907
|
|
7
|
+
data.tar.gz: 67a48f5ce9298763f588c2e979a3bcca6b1a38a9c8f9350597f82876c69168c644b480affc62728d27ad1c8d114071267fbc7592a5703446441dc385b3459357
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0.alpha.3 - 2026-08-27
|
|
4
|
+
|
|
5
|
+
- Remove Bundler cache metadata before compiling assets so cold and restored Rails builds stay identical.
|
|
6
|
+
|
|
7
|
+
## 0.1.0.alpha.2 - 2026-08-27
|
|
8
|
+
|
|
9
|
+
- Stream clean, Docker-style build steps and command output through Dagger Ruby.
|
|
10
|
+
- Run Bundler with the available CPUs unless `BUNDLE_JOBS` is explicitly configured.
|
|
11
|
+
- Keep dependency cache metadata out of Rails artifacts.
|
|
12
|
+
|
|
3
13
|
## 0.1.0.alpha.1 - 2026-08-27
|
|
4
14
|
|
|
5
15
|
- Reintroduce BoringBuilder as a Ruby gem backed by Dagger Ruby.
|
|
@@ -6,7 +6,7 @@ module BoringBuilder
|
|
|
6
6
|
class Builder
|
|
7
7
|
attr_reader :configuration, :progress
|
|
8
8
|
|
|
9
|
-
def initialize(configuration, progress:
|
|
9
|
+
def initialize(configuration, progress: DaggerRuby::Progress.silent)
|
|
10
10
|
@configuration = configuration
|
|
11
11
|
@progress = progress
|
|
12
12
|
end
|
data/lib/boringbuilder/cli.rb
CHANGED
|
@@ -59,7 +59,7 @@ module BoringBuilder
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def build
|
|
62
|
-
options = {}
|
|
62
|
+
options = { progress: "pretty" }
|
|
63
63
|
config_file = :auto
|
|
64
64
|
dry_run = false
|
|
65
65
|
json = false
|
|
@@ -71,6 +71,8 @@ module BoringBuilder
|
|
|
71
71
|
return 0 if help_requested
|
|
72
72
|
raise OptionParser::InvalidArgument, "expected at most one PROJECT directory" if @argv.length > 1
|
|
73
73
|
|
|
74
|
+
options[:progress] = nil if json
|
|
75
|
+
|
|
74
76
|
configuration = BoringBuilder.configuration(root: @argv.first || Dir.pwd, config: config_file, **options)
|
|
75
77
|
project = Project.new(configuration).validate!
|
|
76
78
|
if dry_run
|
|
@@ -78,8 +80,9 @@ module BoringBuilder
|
|
|
78
80
|
return 0
|
|
79
81
|
end
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
progress = build_progress(configuration, json: json)
|
|
84
|
+
print_build_start(project, json: json, progress: progress)
|
|
85
|
+
result = @builder_class.new(configuration, progress: progress).build
|
|
83
86
|
json ? @out.puts(JSON.generate(result.to_h)) : print_result(result)
|
|
84
87
|
0
|
|
85
88
|
end
|
|
@@ -109,7 +112,8 @@ module BoringBuilder
|
|
|
109
112
|
parser.on("--platform PLATFORM", "Target platform, for example linux/amd64") do |value|
|
|
110
113
|
options[:platform] = value
|
|
111
114
|
end
|
|
112
|
-
parser.on("--progress MODE", %w[auto plain tty
|
|
115
|
+
parser.on("--progress MODE", %w[pretty auto plain tty dots logs],
|
|
116
|
+
"Build progress: pretty (default), auto, plain, tty, dots, or logs") do |value|
|
|
113
117
|
options[:progress] = value
|
|
114
118
|
end
|
|
115
119
|
end
|
|
@@ -209,7 +213,7 @@ module BoringBuilder
|
|
|
209
213
|
@out.puts "Artifact: #{result.artifact_id} (#{result.artifact_name})" if result.artifact_published?
|
|
210
214
|
end
|
|
211
215
|
|
|
212
|
-
def print_build_start(project, json:)
|
|
216
|
+
def print_build_start(project, json:, progress:)
|
|
213
217
|
return if json || !dagger_session?
|
|
214
218
|
|
|
215
219
|
cache = BoringCache.new(project, nil, environment: @environment).mode
|
|
@@ -217,8 +221,7 @@ module BoringBuilder
|
|
|
217
221
|
details = [pipeline, "#{project.configuration.runtime} runtime", "#{cache} cache",
|
|
218
222
|
"#{project.configuration.format.to_s.tr('_', '.')} output"]
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
@out.puts " #{details.join(' · ')}"
|
|
224
|
+
progress.message("Building #{project.app_name} with Dagger\n #{details.join(' · ')}")
|
|
222
225
|
end
|
|
223
226
|
|
|
224
227
|
def dagger_session?
|
|
@@ -226,9 +229,8 @@ module BoringBuilder
|
|
|
226
229
|
end
|
|
227
230
|
|
|
228
231
|
def build_progress(configuration, json:)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
BuildProgress.new(out: @out, enabled: enabled, animated: animated)
|
|
232
|
+
config = DaggerRuby::Config.new(progress: configuration.progress, verify_version: false)
|
|
233
|
+
config.progress_reporter(out: @out, enabled: !json && dagger_session?, environment: @environment)
|
|
232
234
|
end
|
|
233
235
|
end
|
|
234
236
|
end
|
|
@@ -10,7 +10,7 @@ module BoringBuilder
|
|
|
10
10
|
attr_reader :project, :client, :container, :runtime, :exporters, :progress
|
|
11
11
|
|
|
12
12
|
def initialize(project, client, container, runtime:, command_runner: nil, exporters: nil,
|
|
13
|
-
progress:
|
|
13
|
+
progress: DaggerRuby::Progress.silent)
|
|
14
14
|
@project = project
|
|
15
15
|
@client = client
|
|
16
16
|
@container = container
|
|
@@ -56,33 +56,36 @@ module BoringBuilder
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def export_step_name(adapter)
|
|
59
|
-
return "Publish artifact to BoringCache" if adapter.name == :boringcache
|
|
59
|
+
return "[export] Publish artifact to BoringCache" if adapter.name == :boringcache
|
|
60
60
|
|
|
61
|
-
"
|
|
61
|
+
"[export] Write #{configuration.format.to_s.tr('_', '.')} artifact"
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def build_asset
|
|
65
65
|
filename = project.output_path.basename.to_s
|
|
66
66
|
case configuration.format
|
|
67
67
|
when :directory
|
|
68
|
-
Exporters::Asset.new(kind: :directory, source:
|
|
68
|
+
Exporters::Asset.new(kind: :directory, source: prepared_artifact_directory, filename: filename)
|
|
69
69
|
when :tar
|
|
70
70
|
Exporters::Asset.new(kind: :file, source: archive_file("artifact.tar", compression: nil), filename: filename)
|
|
71
71
|
when :tar_zst
|
|
72
72
|
Exporters::Asset.new(kind: :file, source: archive_file("artifact.tar.zst", compression: :zstd),
|
|
73
73
|
filename: filename)
|
|
74
74
|
when :oci, :docker
|
|
75
|
-
|
|
75
|
+
source = progress.step("[export] Create #{configuration.format.to_s.upcase} image archive") do
|
|
76
|
+
container.as_tarball(media_types: media_types).sync
|
|
77
|
+
end
|
|
78
|
+
Exporters::Asset.new(kind: :file, source: source, filename: filename)
|
|
76
79
|
end
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
def export_reference
|
|
80
83
|
if configuration.publish
|
|
81
|
-
progress.step("Publish container image") do
|
|
84
|
+
progress.step("[export] Publish container image") do
|
|
82
85
|
container.publish(configuration.publish, media_types: media_types)
|
|
83
86
|
end
|
|
84
87
|
elsif configuration.load
|
|
85
|
-
progress.step("Load container image") { load_image }
|
|
88
|
+
progress.step("[export] Load container image") { load_image }
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
|
|
@@ -123,20 +126,48 @@ module BoringBuilder
|
|
|
123
126
|
@artifact_directory ||= project.artifact.export_directory(client, container)
|
|
124
127
|
end
|
|
125
128
|
|
|
129
|
+
def prepared_artifact_directory
|
|
130
|
+
@prepared_artifact_directory ||= progress.step("[export] Assemble artifact filesystem") do
|
|
131
|
+
artifact_directory.sync
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
126
135
|
def archive_file(filename, compression:)
|
|
127
|
-
packer = client.container.from(PACKER_IMAGE).
|
|
128
|
-
packer = packer
|
|
136
|
+
packer = progress.step("[export] FROM #{PACKER_IMAGE}") { client.container.from(PACKER_IMAGE).sync }
|
|
137
|
+
packer = run_archive_step(packer, %w[apk add --no-cache tar zstd], "[export] RUN apk add tar zstd")
|
|
138
|
+
packer = packer.with_mounted_directory("/artifact", prepared_artifact_directory)
|
|
129
139
|
tar_path = compression == :zstd ? "/artifact.tar" : "/#{filename}"
|
|
130
|
-
packer =
|
|
140
|
+
packer = run_archive_step(
|
|
141
|
+
packer,
|
|
131
142
|
[
|
|
132
143
|
"tar", "--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner",
|
|
133
144
|
"--pax-option=delete=atime,delete=ctime", "-C", "/artifact", "-cf", tar_path, "."
|
|
134
|
-
]
|
|
145
|
+
],
|
|
146
|
+
"[export] RUN tar -cf #{tar_path} ."
|
|
135
147
|
)
|
|
136
|
-
|
|
148
|
+
if compression == :zstd
|
|
149
|
+
packer = run_archive_step(
|
|
150
|
+
packer,
|
|
151
|
+
["zstd", "-q", "-T0", tar_path, "-o", "/#{filename}"],
|
|
152
|
+
"[export] RUN zstd -T0 #{tar_path}"
|
|
153
|
+
)
|
|
154
|
+
end
|
|
137
155
|
packer.file("/#{filename}")
|
|
138
156
|
end
|
|
139
157
|
|
|
158
|
+
def run_archive_step(container, command, name)
|
|
159
|
+
executed = container.with_exec(command)
|
|
160
|
+
progress.step(name) do
|
|
161
|
+
output = command_output(executed)
|
|
162
|
+
progress.write(output) unless progress.streaming?
|
|
163
|
+
executed.sync
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def command_output(container)
|
|
168
|
+
[container.stdout, container.stderr].map(&:strip).reject(&:empty?).join("\n")
|
|
169
|
+
end
|
|
170
|
+
|
|
140
171
|
def media_types
|
|
141
172
|
configuration.format == :docker ? :DockerMediaTypes : :OCIMediaTypes
|
|
142
173
|
end
|
data/lib/boringbuilder/mise.rb
CHANGED
|
@@ -4,7 +4,7 @@ module BoringBuilder
|
|
|
4
4
|
class Pipeline
|
|
5
5
|
attr_reader :project, :client, :progress
|
|
6
6
|
|
|
7
|
-
def initialize(project, client, environment: ENV, progress:
|
|
7
|
+
def initialize(project, client, environment: ENV, progress: DaggerRuby::Progress.silent)
|
|
8
8
|
@project = project
|
|
9
9
|
@client = client
|
|
10
10
|
@environment = environment
|
|
@@ -124,7 +124,10 @@ module BoringBuilder
|
|
|
124
124
|
return container if container.equal?(@last_synced_container)
|
|
125
125
|
|
|
126
126
|
@last_synced_container = progress.step(name) do
|
|
127
|
-
|
|
127
|
+
if output
|
|
128
|
+
result = output.call
|
|
129
|
+
progress.write(result) unless progress.streaming?
|
|
130
|
+
end
|
|
128
131
|
container.sync
|
|
129
132
|
end
|
|
130
133
|
end
|
|
@@ -5,6 +5,7 @@ module BoringBuilder
|
|
|
5
5
|
DEFAULT_EXCLUDES = %w[
|
|
6
6
|
.env
|
|
7
7
|
.env.*
|
|
8
|
+
.bundle/cache
|
|
8
9
|
.git
|
|
9
10
|
coverage
|
|
10
11
|
dist
|
|
@@ -29,7 +30,7 @@ module BoringBuilder
|
|
|
29
30
|
self
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
def container(client, progress:
|
|
33
|
+
def container(client, progress: DaggerRuby::Progress.silent)
|
|
33
34
|
return Pipeline.new(self, client, progress: progress).build(&configuration.pipeline) if custom_pipeline?
|
|
34
35
|
|
|
35
36
|
builder = rails? ? RailsBuild : RubyBuild
|
|
@@ -20,23 +20,29 @@ module BoringBuilder
|
|
|
20
20
|
"BUNDLE_CLEAN" => "true",
|
|
21
21
|
"BUNDLE_DEPLOYMENT" => "1",
|
|
22
22
|
"BUNDLE_IGNORE_CONFIG" => "true",
|
|
23
|
-
"BUNDLE_JOBS" => "1",
|
|
24
23
|
"BUNDLE_PATH" => "/usr/local/bundle",
|
|
25
24
|
"BUNDLE_WITHOUT" => "development:test"
|
|
26
25
|
}.freeze
|
|
27
|
-
BUNDLE_INSTALL_COMMAND = [
|
|
26
|
+
BUNDLE_INSTALL_COMMAND = [
|
|
27
|
+
"sh", "-c",
|
|
28
|
+
'export BUNDLE_JOBS="${BUNDLE_JOBS:-$(nproc)}"; bundle install && bundle clean --force'
|
|
29
|
+
].freeze
|
|
30
|
+
APT_INSTALL_COMMAND = [
|
|
31
|
+
"sh", "-c",
|
|
32
|
+
"apt-get update && apt-get install -y --no-install-recommends \"$@\" && rm -rf /var/lib/apt/lists/*",
|
|
33
|
+
"apt-get"
|
|
34
|
+
].freeze
|
|
28
35
|
BUNDLE_FILES = %w[Gemfile Gemfile.lock .ruby-version].freeze
|
|
29
36
|
BUNDLE_CONFIG = <<~YAML
|
|
30
37
|
---
|
|
31
38
|
BUNDLE_PATH: "vendor/bundle"
|
|
32
39
|
BUNDLE_WITHOUT: "development:test"
|
|
33
|
-
BUNDLE_JOBS: "1"
|
|
34
40
|
BUNDLE_CLEAN: "true"
|
|
35
41
|
YAML
|
|
36
42
|
|
|
37
43
|
attr_reader :project, :client, :progress
|
|
38
44
|
|
|
39
|
-
def initialize(project, client, progress:
|
|
45
|
+
def initialize(project, client, progress: DaggerRuby::Progress.silent)
|
|
40
46
|
@project = project
|
|
41
47
|
@client = client
|
|
42
48
|
@progress = progress
|
|
@@ -44,15 +50,27 @@ module BoringBuilder
|
|
|
44
50
|
|
|
45
51
|
def container
|
|
46
52
|
builder = with_environment(build_image_container, build_environment)
|
|
47
|
-
builder = install_packages(builder, (BUILD_PACKAGES + configuration.build_packages).uniq)
|
|
48
|
-
builder = create_application_user(builder)
|
|
53
|
+
builder = install_packages(builder, (BUILD_PACKAGES + configuration.build_packages).uniq, stage: :build)
|
|
54
|
+
builder = create_application_user(builder, stage: :build)
|
|
49
55
|
builder = install_toolchain(builder)
|
|
50
56
|
builder = install_gems(builder)
|
|
51
|
-
builder =
|
|
52
|
-
|
|
57
|
+
builder = pipeline.step("[build] COPY application source", builder) do |container|
|
|
58
|
+
container.with_directory(application_path, project.source(client), owner: user_owner)
|
|
59
|
+
end
|
|
60
|
+
builder = pipeline.exec(
|
|
61
|
+
builder,
|
|
62
|
+
%w[bundle check],
|
|
63
|
+
name: "[build] RUN bundle check",
|
|
64
|
+
workdir: application_path
|
|
65
|
+
)
|
|
66
|
+
builder = pipeline.exec(
|
|
67
|
+
builder,
|
|
68
|
+
%w[rm -rf .bundle/cache],
|
|
69
|
+
name: "[build] RUN rm -rf .bundle/cache",
|
|
70
|
+
workdir: application_path
|
|
71
|
+
)
|
|
53
72
|
builder = precompile(builder)
|
|
54
|
-
builder =
|
|
55
|
-
builder = progress.step("Build #{project.framework.to_s.capitalize} application") { builder.sync }
|
|
73
|
+
builder = pipeline.step("[build] Write Bundler configuration", builder) { write_bundle_config(_1) }
|
|
56
74
|
|
|
57
75
|
runtime_container(builder)
|
|
58
76
|
end
|
|
@@ -77,12 +95,12 @@ module BoringBuilder
|
|
|
77
95
|
|
|
78
96
|
def build_image_container
|
|
79
97
|
address = configuration.base_image || DEFAULT_BUILD_IMAGE
|
|
80
|
-
client.container(image_options).from(address)
|
|
98
|
+
progress.step("[build] FROM #{address}") { client.container(image_options).from(address).sync }
|
|
81
99
|
end
|
|
82
100
|
|
|
83
101
|
def runtime_image_container
|
|
84
102
|
address = configuration.base_image || DEFAULT_RUNTIME_IMAGE
|
|
85
|
-
client.container(image_options).from(address)
|
|
103
|
+
progress.step("[runtime] FROM #{address}") { client.container(image_options).from(address).sync }
|
|
86
104
|
end
|
|
87
105
|
|
|
88
106
|
def build_environment
|
|
@@ -119,7 +137,7 @@ module BoringBuilder
|
|
|
119
137
|
cache: "bundle",
|
|
120
138
|
at: "/usr/local/bundle/cache",
|
|
121
139
|
entry: "bundler",
|
|
122
|
-
name: "
|
|
140
|
+
name: "[build] RUN bundle install"
|
|
123
141
|
)
|
|
124
142
|
container.with_exec(["find", "/usr/local/bundle", "-path", "*/cache/*.gem", "-type", "f", "-delete"])
|
|
125
143
|
.with_exec([
|
|
@@ -130,6 +148,7 @@ module BoringBuilder
|
|
|
130
148
|
|
|
131
149
|
def write_bundle_config(container)
|
|
132
150
|
container.with_workdir(application_path)
|
|
151
|
+
.with_exec(%w[rm -rf .bundle/cache])
|
|
133
152
|
.with_exec(%w[mkdir -p .bundle])
|
|
134
153
|
.with_new_file("#{application_path}/.bundle/config", BUNDLE_CONFIG)
|
|
135
154
|
end
|
|
@@ -140,41 +159,59 @@ module BoringBuilder
|
|
|
140
159
|
|
|
141
160
|
def runtime_container(builder)
|
|
142
161
|
container = with_environment(runtime_image_container, Mise::ENVIRONMENT.merge(runtime_environment))
|
|
143
|
-
container = install_packages(container, (RUNTIME_PACKAGES + configuration.runtime_packages).uniq
|
|
144
|
-
|
|
145
|
-
container = container
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
162
|
+
container = install_packages(container, (RUNTIME_PACKAGES + configuration.runtime_packages).uniq,
|
|
163
|
+
stage: :runtime)
|
|
164
|
+
container = create_application_user(container, stage: :runtime)
|
|
165
|
+
container = pipeline.step("[runtime] COPY toolchain, gems, and application", container) do |runtime|
|
|
166
|
+
runtime.with_directory("/mise/installs", builder.directory("/mise/installs"))
|
|
167
|
+
.with_directory("/usr/local/bundle", builder.directory("/usr/local/bundle"), owner: user_owner)
|
|
168
|
+
.with_directory(application_path, builder.directory(application_path), owner: user_owner)
|
|
169
|
+
end
|
|
170
|
+
container = pipeline.exec(
|
|
171
|
+
container,
|
|
172
|
+
%w[mise reshim],
|
|
173
|
+
name: "[runtime] RUN mise reshim",
|
|
174
|
+
workdir: application_path
|
|
175
|
+
)
|
|
151
176
|
container = runtime_metadata(container).with_user(project.application_user)
|
|
152
|
-
progress.step("
|
|
177
|
+
progress.step("[runtime] Configure image") { container.sync }
|
|
153
178
|
end
|
|
154
179
|
|
|
155
|
-
def install_packages(container, packages)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
180
|
+
def install_packages(container, packages, stage:)
|
|
181
|
+
pipeline.exec(
|
|
182
|
+
container,
|
|
183
|
+
[*APT_INSTALL_COMMAND, *packages],
|
|
184
|
+
name: "[#{stage}] RUN apt-get update && apt-get install packages"
|
|
185
|
+
)
|
|
160
186
|
end
|
|
161
187
|
|
|
162
|
-
def create_application_user(container)
|
|
188
|
+
def create_application_user(container, stage:)
|
|
163
189
|
name = project.application_user
|
|
164
|
-
container
|
|
165
|
-
.with_exec(["groupadd", "--system", "--gid", "1000", name])
|
|
166
|
-
|
|
190
|
+
pipeline.step("[#{stage}] Create application user", container) do |step|
|
|
191
|
+
step.with_exec(["groupadd", "--system", "--gid", "1000", name])
|
|
192
|
+
.with_exec(["useradd", name, "--uid", "1000", "--gid", "1000", "--create-home", "--shell", "/bin/bash"])
|
|
193
|
+
end
|
|
167
194
|
end
|
|
168
195
|
|
|
169
196
|
def precompile(container)
|
|
170
|
-
|
|
197
|
+
if project.rails? && project.assets?
|
|
198
|
+
container = pipeline.exec(
|
|
199
|
+
container,
|
|
200
|
+
%w[bin/rails assets:precompile],
|
|
201
|
+
name: "[build] RUN bin/rails assets:precompile"
|
|
202
|
+
)
|
|
203
|
+
end
|
|
171
204
|
if project.bootsnap?
|
|
172
205
|
paths = project.rails? ? %w[app/ lib/] : %w[app/ lib/ config/]
|
|
173
|
-
container =
|
|
206
|
+
container = pipeline.exec(
|
|
207
|
+
container,
|
|
208
|
+
["bundle", "exec", "bootsnap", "precompile", "--gemfile", *paths],
|
|
209
|
+
name: "[build] RUN bundle exec bootsnap precompile"
|
|
210
|
+
)
|
|
174
211
|
end
|
|
175
212
|
return container unless project.rails?
|
|
176
213
|
|
|
177
|
-
|
|
214
|
+
pipeline.step("[build] Normalize Rails output", container) { normalize_rails_output(_1) }
|
|
178
215
|
end
|
|
179
216
|
|
|
180
217
|
def normalize_rails_output(container)
|
data/lib/boringbuilder.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "dagger_ruby"
|
|
4
|
+
|
|
3
5
|
require_relative "boringbuilder/version"
|
|
4
6
|
require_relative "boringbuilder/errors"
|
|
5
7
|
require_relative "boringbuilder/artifact"
|
|
6
8
|
require_relative "boringbuilder/configuration"
|
|
7
9
|
require_relative "boringbuilder/config_file"
|
|
8
10
|
require_relative "boringbuilder/initializer"
|
|
9
|
-
require_relative "boringbuilder/build_progress"
|
|
10
11
|
require_relative "boringbuilder/boring_cache"
|
|
11
12
|
require_relative "boringbuilder/mise"
|
|
12
13
|
require_relative "boringbuilder/pipeline"
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: boringbuilder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.0.alpha.
|
|
4
|
+
version: 0.1.0.alpha.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- BoringCache
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: dagger_ruby
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0.
|
|
18
|
+
version: '0.11'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0.
|
|
25
|
+
version: '0.11'
|
|
27
26
|
description: BoringBuilder turns application source into filesystem artifacts and
|
|
28
27
|
container images. Rails is first class, while Mise-powered Dagger Ruby recipes support
|
|
29
28
|
any workload.
|
|
@@ -48,7 +47,6 @@ files:
|
|
|
48
47
|
- lib/boringbuilder.rb
|
|
49
48
|
- lib/boringbuilder/artifact.rb
|
|
50
49
|
- lib/boringbuilder/boring_cache.rb
|
|
51
|
-
- lib/boringbuilder/build_progress.rb
|
|
52
50
|
- lib/boringbuilder/builder.rb
|
|
53
51
|
- lib/boringbuilder/cli.rb
|
|
54
52
|
- lib/boringbuilder/config_file.rb
|
|
@@ -80,13 +78,12 @@ licenses:
|
|
|
80
78
|
metadata:
|
|
81
79
|
allowed_push_host: https://rubygems.org
|
|
82
80
|
homepage_uri: https://github.com/boringcache/builder
|
|
83
|
-
source_code_uri: https://github.com/boringcache/builder/tree/v0.1.0.alpha.
|
|
81
|
+
source_code_uri: https://github.com/boringcache/builder/tree/v0.1.0.alpha.3
|
|
84
82
|
documentation_uri: https://github.com/boringcache/builder#readme
|
|
85
83
|
changelog_uri: https://github.com/boringcache/builder/blob/main/CHANGELOG.md
|
|
86
84
|
bug_tracker_uri: https://github.com/boringcache/builder/issues
|
|
87
85
|
security_policy_uri: https://github.com/boringcache/builder/security/policy
|
|
88
86
|
rubygems_mfa_required: 'true'
|
|
89
|
-
post_install_message:
|
|
90
87
|
rdoc_options: []
|
|
91
88
|
require_paths:
|
|
92
89
|
- lib
|
|
@@ -97,12 +94,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
97
94
|
version: 3.2.0
|
|
98
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
96
|
requirements:
|
|
100
|
-
- - "
|
|
97
|
+
- - ">="
|
|
101
98
|
- !ruby/object:Gem::Version
|
|
102
|
-
version:
|
|
99
|
+
version: '0'
|
|
103
100
|
requirements: []
|
|
104
|
-
rubygems_version:
|
|
105
|
-
signing_key:
|
|
101
|
+
rubygems_version: 4.0.16
|
|
106
102
|
specification_version: 4
|
|
107
103
|
summary: Build deployment artifacts and images with Dagger
|
|
108
104
|
test_files: []
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module BoringBuilder
|
|
4
|
-
class BuildProgress
|
|
5
|
-
SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
6
|
-
|
|
7
|
-
def self.silent
|
|
8
|
-
@silent ||= new(enabled: false)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def initialize(out: $stdout, enabled: true, animated: false)
|
|
12
|
-
@out = out
|
|
13
|
-
@enabled = enabled
|
|
14
|
-
@animated = animated
|
|
15
|
-
@step = 0
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def step(name)
|
|
19
|
-
return yield unless @enabled
|
|
20
|
-
|
|
21
|
-
number = next_step
|
|
22
|
-
started_at = monotonic_time
|
|
23
|
-
@out.puts "##{number} #{name}"
|
|
24
|
-
@active_spinner = start_spinner(number, started_at)
|
|
25
|
-
result = yield
|
|
26
|
-
stop_active_spinner
|
|
27
|
-
@out.puts "##{number} DONE #{elapsed_since(started_at)}"
|
|
28
|
-
result
|
|
29
|
-
rescue StandardError
|
|
30
|
-
stop_active_spinner
|
|
31
|
-
@out.puts "##{number} ERROR #{elapsed_since(started_at)}" if number
|
|
32
|
-
raise
|
|
33
|
-
ensure
|
|
34
|
-
stop_active_spinner
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def write(output = nil)
|
|
38
|
-
return unless @enabled
|
|
39
|
-
|
|
40
|
-
output = yield if block_given?
|
|
41
|
-
stop_active_spinner
|
|
42
|
-
text = output.to_s
|
|
43
|
-
text.each_line { |line| @out.print " #{line}" }
|
|
44
|
-
@out.puts unless text.empty? || text.end_with?("\n")
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def next_step
|
|
50
|
-
@step += 1
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def start_spinner(number, started_at)
|
|
54
|
-
return unless @animated
|
|
55
|
-
|
|
56
|
-
Thread.new do
|
|
57
|
-
frame = 0
|
|
58
|
-
loop do
|
|
59
|
-
@out.print "\r##{number} #{SPINNER_FRAMES.fetch(frame % SPINNER_FRAMES.length)} #{elapsed_since(started_at)}"
|
|
60
|
-
@out.flush
|
|
61
|
-
frame += 1
|
|
62
|
-
sleep 0.1
|
|
63
|
-
end
|
|
64
|
-
rescue IOError
|
|
65
|
-
nil
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def stop_spinner(thread)
|
|
70
|
-
return unless thread&.alive?
|
|
71
|
-
|
|
72
|
-
thread.kill
|
|
73
|
-
thread.join
|
|
74
|
-
@out.print "\r\e[2K"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def stop_active_spinner
|
|
78
|
-
stop_spinner(@active_spinner)
|
|
79
|
-
@active_spinner = nil
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def monotonic_time
|
|
83
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def elapsed_since(started_at)
|
|
87
|
-
format("%.1fs", monotonic_time - started_at)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|