Skip to content

Commit e9b2330

Browse files
committed
Add support for multiple host platforms
This way we can provide images for Intel/AMD based as well as ARM based systems. Image building is now prefixed by the host platform "arm" or "x86": ``` rake build:arm:arm-linux-musl # Build image for platform arm-linux-musl on linux/arm64 rake build:arm:arm64-darwin # Build image for platform arm64-darwin on linux/arm64 rake build:arm:jruby # Build image for JRuby on linux/arm64 ``` and: ``` rake build:x86:arm-linux-musl # Build image for platform arm-linux-musl on linux/amd64 rake build:x86:arm64-darwin # Build image for platform arm64-darwin on linux/amd64 rake build:x86:jruby # Build image for JRuby on linux/amd64 ``` Only `docker buildx build` supports multiple platforms, so that it is used now, instead of the classic `docker build`. However buildx doesn't load the images into the local docker registry. This is why `rake load:images` is added. It loads all the images for the local platform and makes it available in `docker images`. The number of images doubles with this patch, so that I get errors building all in parallel. Therefore the number of parallel tasks should be limited like: ``` rake -j10 build:all ```
1 parent 3811c31 commit e9b2330

File tree

2 files changed

+91
-50
lines changed

2 files changed

+91
-50
lines changed

Rakefile

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -25,47 +25,68 @@ platforms = [
2525
["x86_64-linux-musl", "x86_64-unknown-linux-musl"],
2626
]
2727

28+
host_platforms = [
29+
# tuple is [docker platform, rake task, RUBY_PLATFORM matcher]
30+
["linux/amd64", "x86", /^x86_64|^x64|^amd64/],
31+
["linux/arm64", "arm", /^aarch64|arm64/],
32+
]
33+
local_platform = host_platforms.find { |_,_,reg| reg =~ RUBY_PLATFORM } or
34+
raise("RUBY_PLATFORM #{RUBY_PLATFORM} is not supported as host")
35+
2836
namespace :build do
2937

30-
platforms.each do |platform, target|
31-
sdf = "Dockerfile.mri.#{platform}"
38+
mkdir_p "tmp/docker"
39+
40+
host_platforms.each do |docker_platform, rake_platform|
41+
namespace rake_platform do
3242

33-
desc "Build image for platform #{platform}"
34-
task platform => sdf
35-
task sdf do
36-
image_name = RakeCompilerDock::Starter.container_image_name(platform: platform)
37-
sh(*RakeCompilerDock.docker_build_cmd(platform), "-t", image_name, "-f", "Dockerfile.mri.#{platform}", ".")
38-
if image_name.include?("linux-gnu")
39-
sh("docker", "tag", image_name, image_name.sub("linux-gnu", "linux"))
43+
platforms.each do |platform, target|
44+
sdf = "tmp/docker/Dockerfile.mri.#{platform}.#{rake_platform}"
45+
df = ERB.new(File.read("Dockerfile.mri.erb"), trim_mode: ">").result(binding)
46+
File.write(sdf, df)
47+
CLEAN.include(sdf)
4048
end
41-
end
49+
sdf = "tmp/docker/Dockerfile.jruby.#{rake_platform}"
50+
df = File.read("Dockerfile.jruby")
51+
File.write(sdf, df)
4252

43-
df = ERB.new(File.read("Dockerfile.mri.erb"), trim_mode: ">").result(binding)
44-
File.write(sdf, df)
45-
CLEAN.include(sdf)
46-
end
53+
builder = RakeCompilerDock::ParallelDockerBuild.new(platforms.map{|pl, _| "tmp/docker/Dockerfile.mri.#{pl}.#{rake_platform}" } + ["tmp/docker/Dockerfile.jruby.#{rake_platform}"], workdir: "tmp/docker", task_prefix: "common-#{rake_platform}-", platform: docker_platform)
4754

48-
desc "Build image for JRuby"
49-
task :jruby => "Dockerfile.jruby"
50-
task "Dockerfile.jruby" do
51-
image_name = RakeCompilerDock::Starter.container_image_name(rubyvm: "jruby")
52-
sh(*RakeCompilerDock.docker_build_cmd("jruby"), "-t", image_name, "-f", "Dockerfile.jruby", ".")
53-
end
55+
platforms.each do |platform, target|
56+
sdf = "tmp/docker/Dockerfile.mri.#{platform}.#{rake_platform}"
5457

55-
RakeCompilerDock::ParallelDockerBuild.new(platforms.map{|pl, _| "Dockerfile.mri.#{pl}" } + ["Dockerfile.jruby"], workdir: "tmp/docker")
58+
desc "Build image for platform #{platform} on #{docker_platform}"
59+
task platform => sdf
60+
multitask :all => platform
61+
end
62+
63+
sdf = "tmp/docker/Dockerfile.jruby.#{rake_platform}"
64+
desc "Build image for JRuby on #{docker_platform}"
65+
task :jruby => sdf
66+
multitask :all => :jruby
67+
end
68+
desc "Build all images on #{docker_platform} in parallel"
69+
task rake_platform => "#{rake_platform}:all"
70+
end
5671

57-
desc "Build images for all MRI platforms in parallel"
72+
all_mri_images = host_platforms.flat_map do |_, rake_platform|
73+
platforms.map do |platform, |
74+
"#{rake_platform}:#{platform}"
75+
end
76+
end
77+
desc "Build images for all MRI platforms and hosts in parallel"
5878
if ENV['RCD_USE_BUILDX_CACHE']
59-
task :mri => platforms.map(&:first)
79+
task :mri => all_mri_images
6080
else
61-
multitask :mri => platforms.map(&:first)
81+
multitask :mri => all_mri_images
6282
end
6383

64-
desc "Build images for all platforms in parallel"
84+
all_images = all_mri_images + host_platforms.map { |_, pl| "#{pl}:jruby" }
85+
desc "Build images for all platforms and hosts in parallel"
6586
if ENV['RCD_USE_BUILDX_CACHE']
66-
task :all => platforms.map(&:first) + ["jruby"]
87+
task :all => all_images
6788
else
68-
multitask :all => platforms.map(&:first) + ["jruby"]
89+
multitask :all => all_images
6990
end
7091
end
7192

@@ -114,19 +135,35 @@ task :update_lists do
114135
end
115136
end
116137

117-
namespace :release do
118-
desc "push all docker images"
119-
task :images do
120-
image_name = RakeCompilerDock::Starter.container_image_name(rubyvm: "jruby")
121-
sh("docker", "push", image_name)
138+
def build_images(platforms, host_platforms, output: )
139+
image_name = RakeCompilerDock::Starter.container_image_name(rubyvm: "jruby")
122140

123-
platforms.each do |platform, _|
124-
image_name = RakeCompilerDock::Starter.container_image_name(platform: platform)
125-
sh("docker", "push", image_name)
141+
plats = host_platforms.map(&:first).join(",")
142+
sdf = "tmp/docker/Dockerfile.jruby.#{host_platforms.first[1]}"
143+
RakeCompilerDock.docker_build(sdf, tag: image_name, platform: plats, output: output)
126144

127-
if image_name.include?("linux-gnu")
128-
sh("docker", "push", image_name.sub("linux-gnu", "linux"))
129-
end
145+
platforms.each do |platform, _|
146+
sdf = "tmp/docker/Dockerfile.mri.#{platform}.#{host_platforms.first[1]}"
147+
image_name = RakeCompilerDock::Starter.container_image_name(platform: platform)
148+
149+
RakeCompilerDock.docker_build(sdf, tag: image_name, platform: plats, output: output)
150+
151+
if image_name.include?("linux-gnu")
152+
RakeCompilerDock.docker_build(sdf, tag: image_name.sub("linux-gnu", "linux"), platform: plats, output: output)
130153
end
131154
end
132155
end
156+
157+
namespace :release do
158+
desc "Push all docker images on #{host_platforms.map(&:first).join(",")}"
159+
task :images => "build:all" do
160+
build_images(platforms, host_platforms, output: 'push')
161+
end
162+
end
163+
164+
namespace :load do
165+
desc "Build and load into local docker storage"
166+
task :images => "build:#{local_platform[1]}" do
167+
build_images(platforms, [local_platform], output: 'load')
168+
end
169+
end

build/parallel_docker_build.rb

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,29 @@ def docker_build_cmd(platform=nil)
1414
return nil
1515
end
1616
else
17-
ENV['RCD_DOCKER_BUILD'] || "docker build"
17+
ENV['RCD_DOCKER_BUILD'] || "docker buildx build"
1818
end
1919
Shellwords.split(cmd)
2020
end
21+
22+
# Run an intermediate dockerfile without tag
23+
#
24+
# The layers will be reused in subsequent builds, even if they run in parallel.
25+
def docker_build(filename, tag: tag, output: false, platform: )
26+
cmd = docker_build_cmd
27+
return if cmd.nil?
28+
tag_args = ["-t", tag] if tag
29+
push_args = ["--push"] if output == 'push'
30+
push_args = ["--load"] if output == 'load'
31+
Class.new.extend(FileUtils).sh(*cmd, "-f", filename, ".", "--platform", platform, *tag_args, *push_args)
32+
end
2133
end
2234

2335
# Run docker builds in parallel, but ensure that common docker layers are reused
2436
class ParallelDockerBuild
2537
include Rake::DSL
2638

27-
def initialize(dockerfiles, workdir: "tmp/docker", inputdir: ".", task_prefix: "common-")
39+
def initialize(dockerfiles, workdir: "tmp/docker", inputdir: ".", task_prefix: "common-", platform: "local")
2840
FileUtils.mkdir_p(workdir)
2941

3042
files = parse_dockerfiles(dockerfiles, inputdir)
@@ -34,6 +46,7 @@ def initialize(dockerfiles, workdir: "tmp/docker", inputdir: ".", task_prefix: "
3446
# pp vcs
3547

3648
define_common_tasks(vcs, workdir, task_prefix)
49+
@platform = platform
3750
end
3851

3952
# Read given dockerfiles from inputdir and split into a list of commands.
@@ -96,7 +109,7 @@ def define_common_tasks(vcs, workdir, task_prefix, plines=[])
96109
fn = "#{task_prefix}#{Digest::SHA1.hexdigest(files.join)}"
97110
File.write(File.join(workdir, fn), (plines + lines).join)
98111
task fn do
99-
docker_build(fn, workdir)
112+
RakeCompilerDock.docker_build(File.join(workdir, fn), platform: @platform)
100113
end
101114

102115
nfn = define_common_tasks(nvcs, workdir, task_prefix, plines + lines)
@@ -109,14 +122,5 @@ def define_common_tasks(vcs, workdir, task_prefix, plines=[])
109122
fn
110123
end
111124
end
112-
113-
# Run an intermediate dockerfile without tag
114-
#
115-
# The layers will be reused in subsequent builds, even if they run in parallel.
116-
def docker_build(filename, workdir)
117-
cmd = RakeCompilerDock.docker_build_cmd
118-
return if cmd.nil?
119-
sh(*RakeCompilerDock.docker_build_cmd, "-f", File.join(workdir, filename), ".")
120-
end
121125
end
122126
end

0 commit comments

Comments
 (0)