Skip to content

Commit 2a2d21c

Browse files
ruby: use CMake in build process (#3043)
* Use CMake to build shared object * Make Rakefile follow change of build process * Add test for packaging * Run CI for Ruby bindings almost always because each CMakeLists.txt might affect Ruby bindings * Enable PIC * Bump Ruby version to 3.2 on CI * Check libgomp * Check dependency of whisper.cpp accurately
1 parent 9cfcd6c commit 2a2d21c

File tree

8 files changed

+123
-259
lines changed

8 files changed

+123
-259
lines changed

.github/workflows/bindings-ruby.yml

+5-49
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,11 @@
11
name: Bindings Tests (Ruby)
2+
23
on:
34
push:
4-
paths:
5-
- bindings/ruby/**
6-
- src/**/*.c
7-
- src/**/*.cpp
8-
- src/**/*.h
9-
- src/**/*.m
10-
- src/**/*.metal
11-
- include/**/*.c
12-
- include/**/*.cpp
13-
- include/**/*.h
14-
- include/**/*.m
15-
- include/**/*.metal
16-
- ggml/**/*.c
17-
- ggml/**/*.cpp
18-
- ggml/**/*.h
19-
- ggml/**/*.m
20-
- ggml/**/*.metal
21-
- scripts/get-flags.mk
22-
- examples/common.h
23-
- examples/common.cpp
24-
- examples/common-whisper.h
25-
- examples/common-whisper.cpp
26-
- examples/stb_vorbis.c
27-
- examples/miniaudio.h
5+
branches:
6+
- master
287
pull_request:
29-
paths:
30-
- bindings/ruby/**
31-
- src/**/*.c
32-
- src/**/*.cpp
33-
- src/**/*.h
34-
- src/**/*.m
35-
- src/**/*.metal
36-
- include/**/*.c
37-
- include/**/*.cpp
38-
- include/**/*.h
39-
- include/**/*.m
40-
- include/**/*.metal
41-
- ggml/**/*.c
42-
- ggml/**/*.cpp
43-
- ggml/**/*.h
44-
- ggml/**/*.m
45-
- ggml/**/*.metal
46-
- scripts/get-flags.mk
47-
- examples/common.h
48-
- examples/common.cpp
49-
- examples/common-whisper.h
50-
- examples/common-whisper.cpp
51-
- examples/stb_vorbis.c
52-
- examples/miniaudio.h
8+
types: [opened, synchronize, reopened]
539

5410
jobs:
5511
ubuntu-22:
@@ -60,6 +16,6 @@ jobs:
6016
steps:
6117
- uses: ruby/setup-ruby@v1
6218
with:
63-
ruby-version: '3.1'
19+
ruby-version: '3.2'
6420
- uses: actions/checkout@v4
6521
- run: rake test

bindings/ruby/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
LICENSE
22
pkg/
33
lib/whisper.*
4+
ext/sources/*
5+
!ext/sources/CMakeGraphVizOptions.cmake
6+
ext/mkmf.log

bindings/ruby/Rakefile

+19-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ require "bundler/gem_tasks"
33
require "rake/testtask"
44
require_relative "extsources"
55

6+
SOURCES_DIR = "ext/sources"
7+
68
SOURCES = FileList[]
79

810
EXTSOURCES.each do |src|
911
basename = src.pathmap("%f")
10-
dest = basename == "LICENSE" ? basename : src.pathmap("%{../..,ext}p")
12+
dest = basename == "LICENSE" ? basename
13+
: src.pathmap("%{\\.\\./\\.\\.,#{SOURCES_DIR}}p")
14+
.pathmap("%{\\.\\./javascript,#{SOURCES_DIR}/bindings/javascript}p")
1115
dir = dest.pathmap("%d")
1216
file src
1317
directory dir
@@ -18,7 +22,6 @@ EXTSOURCES.each do |src|
1822
end
1923

2024
CLEAN.include SOURCES
21-
CLEAN.include FileList["ext/**/*.o", "ext/**/*.metal", "ext/**/*.tmp", "ext/whisper.{so,bundle,dll}"]
2225

2326
SRC = FileList["ext/*.{c,cpp,h}"]
2427

@@ -36,6 +39,20 @@ file "ext/Makefile" => SRC + ["ext/extconf.rb"] + SOURCES do |t|
3639
ruby "extconf.rb"
3740
end
3841
end
42+
if File.exist? "ext/Makefile"
43+
task :make_clean do
44+
cd "ext" do
45+
sh "make", "clean"
46+
end
47+
end
48+
task clean: :make_clean
49+
task :make_distclean do
50+
cd "ext" do
51+
sh "make", "distclean"
52+
end
53+
end
54+
task clobber: :make_distclean
55+
end
3956

4057
file SO_FILE => "ext/Makefile" do |t|
4158
chdir "ext" do

bindings/ruby/ext/extconf.rb

+51-202
Original file line numberDiff line numberDiff line change
@@ -1,212 +1,61 @@
1-
require 'mkmf'
1+
require "mkmf"
2+
require "tsort"
23

3-
# need to use c++ compiler flags
4-
$CXXFLAGS << ' -std=c++17'
4+
# TODO: options such as CoreML
55

6-
$LDFLAGS << ' -lstdc++'
6+
cmake = find_executable("cmake") || abort
77

8-
# Set to true when building binary gems
9-
if enable_config('static-stdlib', false)
10-
$LDFLAGS << ' -static-libgcc -static-libstdc++'
11-
end
12-
13-
if enable_config('march-tune-native', false)
14-
$CFLAGS << ' -march=native -mtune=native'
15-
$CXXFLAGS << ' -march=native -mtune=native'
16-
end
8+
have_library("gomp") rescue nil
179

18-
if ENV['WHISPER_METAL']
19-
$GGML_METAL ||= true
20-
$DEPRECATE_WARNING ||= true
21-
end
10+
prefix = File.join("build", "whisper.cpp.dot")
11+
system cmake, "-S", "sources", "-B", "build", "--graphviz", prefix, "-D", "BUILD_SHARED_LIBS=OFF", exception: true
2212

23-
$UNAME_S = `uname -s`.chomp
24-
$UNAME_P = `uname -p`.chomp
25-
$UNAME_M = `uname -m`.chomp
26-
27-
if $UNAME_S == 'Darwin'
28-
unless ENV['GGML_NO_METAL']
29-
$GGML_METAL ||= true
13+
static_lib_shape = nil
14+
nodes = {}
15+
depends = {}
16+
class << depends
17+
include TSort
18+
alias tsort_each_node each_key
19+
def tsort_each_child(node, &block)
20+
fetch(node, []).each(&block)
3021
end
31-
$GGML_NO_OPENMP ||= true
32-
end
33-
34-
if $GGML_METAL
35-
$GGML_METAL_EMBED_LIBRARY = true
36-
end
37-
38-
$MK_CPPFLAGS = '-Iggml/include -Iggml/src -Iggml/src/ggml-cpu -Iinclude -Isrc -Iexamples -DGGML_USE_CPU'
39-
$MK_CFLAGS = '-std=c11 -fPIC'
40-
$MK_CXXFLAGS = '-std=c++17 -fPIC'
41-
$MK_NVCCFLAGS = '-std=c++17'
42-
$MK_LDFLAGS = ''
43-
44-
$OBJ_GGML = []
45-
$OBJ_WHISPER = []
46-
$OBJ_COMMON = []
47-
$OBJ_SDL = []
48-
49-
$MK_CPPFLAGS << ' -D_XOPEN_SOURCE=600'
50-
51-
if $UNAME_S == 'Linux'
52-
$MK_CPPFLAGS << ' -D_GNU_SOURCE'
53-
end
54-
55-
if $UNAME_S == 'Darwin'
56-
$MK_CPPFLAGS << ' -D_DARWIN_C_SOURCE'
57-
end
58-
59-
if ENV['WHISPER_DEBUG']
60-
$MK_CFLAGS << ' -O0 -g'
61-
$MK_CXXFLAGS << ' -O0 -g'
62-
$MK_LDFLAGS << ' -g'
63-
$MK_NVCCFLAGS << ' -O0 -g'
64-
else
65-
$MK_CPPFLAGS << ' -DNDEBUG'
66-
$MK_CFLAGS << ' -O3'
67-
$MK_CXXFLAGS << ' -O3'
68-
$MK_NVCCFLAGS << ' -O3'
69-
end
70-
71-
$WARN_FLAGS =
72-
' -Wall' <<
73-
' -Wextra' <<
74-
' -Wpedantic' <<
75-
' -Wcast-qual' <<
76-
' -Wno-unused-function'
77-
78-
$MK_CFLAGS <<
79-
$WARN_FLAGS <<
80-
' -Wshadow' <<
81-
' -Wstrict-prototypes' <<
82-
' -Wpointer-arith' <<
83-
' -Wmissing-prototypes' <<
84-
' -Werror=implicit-int' <<
85-
' -Werror=implicit-function-declaration'
86-
87-
$MK_CXXFLAGS <<
88-
$WARN_FLAGS <<
89-
' -Wmissing-declarations' <<
90-
' -Wmissing-noreturn'
91-
92-
unless `#{cc_command} #{$LDFLAGS} -Wl,-v 2>&1`.chomp.include? 'dyld-1015.7'
93-
$MK_CPPFLAGS << ' -DHAVE_BUGGY_APPLE_LINKER'
94-
end
95-
96-
if %w[Linux Darwin FreeBSD NetBSD OpenBSD Haiku].include? $UNAME_S
97-
$MK_CFLAGS << ' -pthread'
98-
$MK_CXXFLAGS << ' -pthread'
99-
end
100-
101-
unless $_WIN32
102-
$DSO_EXT = '.so'
103-
else
104-
$DSO_EXT = '.dll'
10522
end
106-
107-
unless ENV['RISCV']
108-
if %w[x86_64 i686 amd64].include? $UNAME_M
109-
$HOST_CXXFLAGS ||= ''
110-
111-
$MK_CFLAGS << ' -march=native -mtune=native'
112-
$HOST_CXXFLAGS << ' -march=native -mtune=native'
113-
end
114-
else
115-
$MK_CFLAGS << ' -march=rv64gcv -mabi=lp64d'
116-
$MK_CXXFLAGS << ' -march=rv64gcv -mabi=lp64d'
117-
end
118-
119-
unless ENV['GGML_NO_ACCELERATE']
120-
if $UNAME_S == 'Darwin'
121-
$MK_CPPFLAGS << ' -DGGML_USE_ACCELERATE -DGGML_USE_BLAS -DGGML_BLAS_USE_ACCELERATE'
122-
$MK_CPPFLAGS << ' -DACCELERATE_NEW_LAPACK'
123-
$MK_CPPFLAGS << ' -DACCELERATE_LAPACK_ILP64'
124-
$MK_LDFLAGS << ' -framework Accelerate'
125-
$OBJ_GGML << 'ggml/src/ggml-blas/ggml-blas.o'
126-
end
127-
end
128-
129-
if ENV['GGML_OPENBLAS']
130-
$MK_CPPFLAGS << " -DGGML_USE_BLAS #{`pkg-config --cflags-only-I openblas`.chomp}"
131-
$MK_CFLAGS << " #{`pkg-config --cflags-only-other openblas)`.chomp}"
132-
$MK_LDFLAGS << " #{`pkg-config --libs openblas`}"
133-
$OBJ_GGML << 'ggml/src/ggml-blas/ggml-blas.o'
134-
end
135-
136-
if ENV['GGML_OPENBLAS64']
137-
$MK_CPPFLAGS << " -DGGML_USE_BLAS #{`pkg-config --cflags-only-I openblas64`.chomp}"
138-
$MK_CFLAGS << " #{`pkg-config --cflags-only-other openblas64)`.chomp}"
139-
$MK_LDFLAGS << " #{`pkg-config --libs openblas64`}"
140-
$OBJ_GGML << 'ggml/src/ggml-blas/ggml-blas.o'
141-
end
142-
143-
if $GGML_METAL
144-
$MK_CPPFLAGS << ' -DGGML_USE_METAL'
145-
$MK_LDFLAGS << ' -framework Foundation -framework Metal -framework MetalKit'
146-
$OBJ_GGML << 'ggml/src/ggml-metal/ggml-metal.o'
147-
148-
if ENV['GGML_METAL_NDEBUG']
149-
$MK_CPPFLAGS << ' -DGGML_METAL_NDEBUG'
150-
end
151-
152-
if $GGML_METAL_EMBED_LIBRARY
153-
$MK_CPPFLAGS << ' -DGGML_METAL_EMBED_LIBRARY'
154-
$OBJ_GGML << 'ggml/src/ggml-metal/ggml-metal-embed.o'
23+
File.open(File.join("build", "whisper.cpp.dot")).each_line do |line|
24+
case line
25+
when /\[\s*label\s*=\s*"Static Library"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]/
26+
static_lib_shape = $~[:shape]
27+
when /\A\s*"(?<node>\w+)"\s*\[\s*label\s*=\s*"(?<label>\S+)"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]\s*;\s*\z/
28+
node = $~[:node]
29+
label = $~[:label]
30+
shape = $~[:shape]
31+
nodes[node] = [label, shape]
32+
when /\A\s*"(?<depender>\w+)"\s*->\s*"(?<dependee>\w+)"/
33+
depender = $~[:depender]
34+
dependee = $~[:dependee]
35+
depends[depender] ||= []
36+
depends[depender] << dependee
15537
end
15638
end
157-
158-
$OBJ_GGML <<
159-
'ggml/src/ggml.o' <<
160-
'ggml/src/ggml-alloc.o' <<
161-
'ggml/src/ggml-backend.o' <<
162-
'ggml/src/ggml-backend-reg.o' <<
163-
'ggml/src/ggml-opt.o' <<
164-
'ggml/src/ggml-quants.o' <<
165-
'ggml/src/ggml-threading.o' <<
166-
'ggml/src/ggml-cpu/ggml-cpu.o' <<
167-
'ggml/src/ggml-cpu/ggml-cpu-cpp.o' <<
168-
'ggml/src/ggml-cpu/ggml-cpu-aarch64.o' <<
169-
'ggml/src/ggml-cpu/ggml-cpu-hbm.o' <<
170-
'ggml/src/ggml-cpu/ggml-cpu-quants.o' <<
171-
'ggml/src/ggml-cpu/ggml-cpu-traits.o' <<
172-
'ggml/src/ggml-cpu/unary-ops.o' <<
173-
'ggml/src/ggml-cpu/binary-ops.o' <<
174-
'ggml/src/ggml-cpu/vec.o' <<
175-
'ggml/src/ggml-cpu/ops.o'
176-
177-
$OBJ_WHISPER <<
178-
'src/whisper.o' <<
179-
'examples/common.o' <<
180-
'examples/common-whisper.o'
181-
182-
$objs = $OBJ_GGML + $OBJ_WHISPER + $OBJ_COMMON + $OBJ_SDL
183-
$objs <<
184-
"ruby_whisper.o" <<
185-
"ruby_whisper_context.o" <<
186-
"ruby_whisper_transcribe.o" <<
187-
"ruby_whisper_params.o" <<
188-
"ruby_whisper_error.o" <<
189-
"ruby_whisper_segment.o" <<
190-
"ruby_whisper_model.o"
191-
192-
$CPPFLAGS = "#{$MK_CPPFLAGS} #{$CPPFLAGS}"
193-
$CFLAGS = "#{$CPPFLAGS} #{$MK_CFLAGS} #{$GF_CFLAGS} #{$CFLAGS}"
194-
$BASE_CXXFLAGS = "#{$MK_CXXFLAGS} #{$CXXFLAGS}"
195-
$CXXFLAGS = "#{$BASE_CXXFLAGS} #{$HOST_CXXFLAGS} #{$GF_CXXFLAGS} #{$CPPFLAGS}"
196-
$NVCCFLAGS = "#{$MK_NVCCFLAGS} #{$NVCCFLAGS}"
197-
$LDFLAGS = "#{$MK_LDFLAGS} #{$LDFLAGS}"
198-
199-
create_makefile('whisper')
200-
201-
File.open 'Makefile', 'a' do |file|
202-
file.puts 'include scripts/get-flags.mk'
203-
file.puts 'include cpu.mk'
204-
205-
if $GGML_METAL
206-
file.puts 'include metal.mk'
207-
208-
if $GGML_METAL_EMBED_LIBRARY
209-
file.puts 'include metal-embed.mk'
210-
end
211-
end
39+
libs = depends.tsort.filter_map {|node|
40+
label, shape = nodes[node]
41+
shape == static_lib_shape ? label : nil
42+
}.collect {|lib| "lib#{lib}.a"}
43+
.reverse
44+
.join(" ")
45+
46+
$CFLAGS << " -std=c11 -fPIC"
47+
$CXXFLAGS << " -std=c++17 -O3 -DNDEBUG"
48+
$INCFLAGS << " -Isources/include -Isources/ggml/include -Isources/examples"
49+
$LOCAL_LIBS << " #{libs}"
50+
$cleanfiles << " build #{libs}"
51+
52+
create_makefile "whisper" do |conf|
53+
conf << <<~EOF
54+
$(TARGET_SO): #{libs}
55+
#{libs}: cmake-targets
56+
cmake-targets:
57+
#{"\t"}#{cmake} -S sources -B build -D BUILD_SHARED_LIBS=OFF -D CMAKE_ARCHIVE_OUTPUT_DIRECTORY=#{__dir__} -D CMAKE_POSITION_INDEPENDENT_CODE=ON
58+
#{"\t"}#{cmake} --build build --config Release --target common whisper
59+
#{"\t"}
60+
EOF
21261
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
set(GRAPHVIZ_EXECUTABLES FALSE)
2+
set(GRAPHVIZ_STATIC_LIBS TRUE)
3+
set(GRAPHVIZ_SHARED_LIBS FALSE)
4+
set(GRAPHVIZ_MODULE_LIBS FALSE)
5+
set(GRAPHVIZ_INTERFACE_LIBS FALSE)
6+
set(GRAPHVIZ_OBJECT_LIBS FALSE)
7+
set(GRAPHVIZ_UNKNOWN_LIBS FALSE)
8+
set(GRAPHVIZ_GENERATE_DEPENDERS FALSE)

0 commit comments

Comments
 (0)