Skip to content

Commit 3d0ef4a

Browse files
larskanisflavorjones
authored andcommitted
Add support for multiple host achitectures
This way we can provide images for Intel/AMD based as well as ARM based systems. Image building is now prefixed by the host architecture "arm" or "x86" and the local host arch is loaded after build: ``` rake build:arm:arm-linux-musl # Build and load image for platform arm-linux-musl on linux/arm64 rake build:arm:arm64-darwin # Build and load image for platform arm64-darwin on linux/arm64 rake build:arm:jruby # Build and load image for JRuby on linux/arm64 ``` The foreign architecture is only built: ``` 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`. 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 963fbbb commit 3d0ef4a

File tree

2 files changed

+105
-52
lines changed

2 files changed

+105
-52
lines changed

Rakefile

Lines changed: 89 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@ CLEAN.include("tmp")
88

99
RakeCompilerDock::GemHelper.install_tasks
1010

11+
def build_mri_images(platforms, host_platforms, output: )
12+
plats = host_platforms.map(&:first).join(",")
13+
platforms.each do |platform, _|
14+
sdf = "tmp/docker/Dockerfile.mri.#{platform}.#{host_platforms.first[1]}"
15+
image_name = RakeCompilerDock::Starter.container_image_name(platform: platform)
16+
17+
RakeCompilerDock.docker_build(sdf, tag: image_name, platform: plats, output: output)
18+
19+
if image_name.include?("linux-gnu")
20+
RakeCompilerDock.docker_build(sdf, tag: image_name.sub("linux-gnu", "linux"), platform: plats, output: output)
21+
end
22+
end
23+
end
24+
25+
def build_jruby_images(host_platforms, output: )
26+
image_name = RakeCompilerDock::Starter.container_image_name(rubyvm: "jruby")
27+
plats = host_platforms.map(&:first).join(",")
28+
sdf = "tmp/docker/Dockerfile.jruby.#{host_platforms.first[1]}"
29+
RakeCompilerDock.docker_build(sdf, tag: image_name, platform: plats, output: output)
30+
end
31+
1132
platforms = [
1233
# tuple is [platform, target]
1334
["aarch64-linux-gnu", "aarch64-linux-gnu"],
@@ -25,47 +46,84 @@ platforms = [
2546
["x86_64-linux-musl", "x86_64-unknown-linux-musl"],
2647
]
2748

49+
host_platforms = [
50+
# tuple is [docker platform, rake task, RUBY_PLATFORM matcher]
51+
["linux/amd64", "x86", /^x86_64|^x64|^amd64/],
52+
["linux/arm64", "arm", /^aarch64|arm64/],
53+
]
54+
local_platform = host_platforms.find { |_,_,reg| reg =~ RUBY_PLATFORM } or
55+
raise("RUBY_PLATFORM #{RUBY_PLATFORM} is not supported as host")
56+
2857
namespace :build do
2958

30-
platforms.each do |platform, target|
31-
sdf = "Dockerfile.mri.#{platform}"
59+
mkdir_p "tmp/docker"
60+
61+
host_platforms.each do |docker_platform, rake_platform|
62+
namespace rake_platform do
3263

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"))
64+
platforms.each do |platform, target|
65+
sdf = "tmp/docker/Dockerfile.mri.#{platform}.#{rake_platform}"
66+
df = ERB.new(File.read("Dockerfile.mri.erb"), trim_mode: ">").result(binding)
67+
File.write(sdf, df)
68+
CLEAN.include(sdf)
69+
end
70+
sdf = "tmp/docker/Dockerfile.jruby.#{rake_platform}"
71+
df = File.read("Dockerfile.jruby")
72+
File.write(sdf, df)
73+
74+
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)
75+
76+
platforms.each do |platform, target|
77+
sdf = "tmp/docker/Dockerfile.mri.#{platform}.#{rake_platform}"
78+
79+
if docker_platform == local_platform[0]
80+
# Load image after build on local platform only
81+
desc "Build and load image for platform #{platform} on #{docker_platform}"
82+
task platform => sdf do
83+
build_mri_images([platform], [local_platform], output: 'load')
84+
end
85+
else
86+
desc "Build image for platform #{platform} on #{docker_platform}"
87+
task platform => sdf
88+
end
89+
multitask :all => platform
4090
end
41-
end
4291

43-
df = ERB.new(File.read("Dockerfile.mri.erb"), trim_mode: ">").result(binding)
44-
File.write(sdf, df)
45-
CLEAN.include(sdf)
92+
sdf = "tmp/docker/Dockerfile.jruby.#{rake_platform}"
93+
if docker_platform == local_platform[0]
94+
# Load image after build on local platform only
95+
desc "Build and load image for JRuby on #{docker_platform}"
96+
task :jruby => sdf do
97+
build_jruby_images([local_platform], output: 'load')
98+
end
99+
else
100+
desc "Build image for JRuby on #{docker_platform}"
101+
task :jruby => sdf
102+
end
103+
multitask :all => :jruby
104+
end
105+
desc "Build all images on #{docker_platform} in parallel"
106+
task rake_platform => "#{rake_platform}:all"
46107
end
47108

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", ".")
109+
all_mri_images = host_platforms.flat_map do |_, rake_platform|
110+
platforms.map do |platform, |
111+
"#{rake_platform}:#{platform}"
112+
end
53113
end
54-
55-
RakeCompilerDock::ParallelDockerBuild.new(platforms.map{|pl, _| "Dockerfile.mri.#{pl}" } + ["Dockerfile.jruby"], workdir: "tmp/docker")
56-
57-
desc "Build images for all MRI platforms in parallel"
114+
desc "Build images for all MRI platforms and hosts in parallel"
58115
if ENV['RCD_USE_BUILDX_CACHE']
59-
task :mri => platforms.map(&:first)
116+
task :mri => all_mri_images
60117
else
61-
multitask :mri => platforms.map(&:first)
118+
multitask :mri => all_mri_images
62119
end
63120

64-
desc "Build images for all platforms in parallel"
121+
all_images = all_mri_images + host_platforms.map { |_, pl| "#{pl}:jruby" }
122+
desc "Build images for all platforms and hosts in parallel"
65123
if ENV['RCD_USE_BUILDX_CACHE']
66-
task :all => platforms.map(&:first) + ["jruby"]
124+
task :all => all_images
67125
else
68-
multitask :all => platforms.map(&:first) + ["jruby"]
126+
multitask :all => all_images
69127
end
70128
end
71129

@@ -115,18 +173,9 @@ task :update_lists do
115173
end
116174

117175
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)
122-
123-
platforms.each do |platform, _|
124-
image_name = RakeCompilerDock::Starter.container_image_name(platform: platform)
125-
sh("docker", "push", image_name)
126-
127-
if image_name.include?("linux-gnu")
128-
sh("docker", "push", image_name.sub("linux-gnu", "linux"))
129-
end
130-
end
176+
desc "Push all docker images on #{host_platforms.map(&:first).join(",")}"
177+
task :images => "build:all" do
178+
build_jruby_images(host_platforms, output: 'push')
179+
build_mri_images(platforms, host_platforms, output: 'push')
131180
end
132181
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: nil, 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)