KitaitiMakoto commited on
Commit
470918e
·
unverified ·
1 Parent(s): 9401dde

ruby: use CMake in build process (#3043)

Browse files

* 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

.github/workflows/bindings-ruby.yml CHANGED
@@ -1,55 +1,11 @@
1
  name: Bindings Tests (Ruby)
 
2
  on:
3
  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
28
  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
53
 
54
  jobs:
55
  ubuntu-22:
@@ -60,6 +16,6 @@ jobs:
60
  steps:
61
  - uses: ruby/setup-ruby@v1
62
  with:
63
- ruby-version: '3.1'
64
  - uses: actions/checkout@v4
65
  - run: rake test
 
1
  name: Bindings Tests (Ruby)
2
+
3
  on:
4
  push:
5
+ branches:
6
+ - master
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  pull_request:
8
+ types: [opened, synchronize, reopened]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  jobs:
11
  ubuntu-22:
 
16
  steps:
17
  - uses: ruby/setup-ruby@v1
18
  with:
19
+ ruby-version: '3.2'
20
  - uses: actions/checkout@v4
21
  - run: rake test
bindings/ruby/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
  LICENSE
2
  pkg/
3
  lib/whisper.*
 
 
 
 
1
  LICENSE
2
  pkg/
3
  lib/whisper.*
4
+ ext/sources/*
5
+ !ext/sources/CMakeGraphVizOptions.cmake
6
+ ext/mkmf.log
bindings/ruby/Rakefile CHANGED
@@ -3,11 +3,15 @@ require "bundler/gem_tasks"
3
  require "rake/testtask"
4
  require_relative "extsources"
5
 
 
 
6
  SOURCES = FileList[]
7
 
8
  EXTSOURCES.each do |src|
9
  basename = src.pathmap("%f")
10
- dest = basename == "LICENSE" ? basename : src.pathmap("%{../..,ext}p")
 
 
11
  dir = dest.pathmap("%d")
12
  file src
13
  directory dir
@@ -18,7 +22,6 @@ EXTSOURCES.each do |src|
18
  end
19
 
20
  CLEAN.include SOURCES
21
- CLEAN.include FileList["ext/**/*.o", "ext/**/*.metal", "ext/**/*.tmp", "ext/whisper.{so,bundle,dll}"]
22
 
23
  SRC = FileList["ext/*.{c,cpp,h}"]
24
 
@@ -36,6 +39,20 @@ file "ext/Makefile" => SRC + ["ext/extconf.rb"] + SOURCES do |t|
36
  ruby "extconf.rb"
37
  end
38
  end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  file SO_FILE => "ext/Makefile" do |t|
41
  chdir "ext" do
 
3
  require "rake/testtask"
4
  require_relative "extsources"
5
 
6
+ SOURCES_DIR = "ext/sources"
7
+
8
  SOURCES = FileList[]
9
 
10
  EXTSOURCES.each do |src|
11
  basename = src.pathmap("%f")
12
+ dest = basename == "LICENSE" ? basename
13
+ : src.pathmap("%{\\.\\./\\.\\.,#{SOURCES_DIR}}p")
14
+ .pathmap("%{\\.\\./javascript,#{SOURCES_DIR}/bindings/javascript}p")
15
  dir = dest.pathmap("%d")
16
  file src
17
  directory dir
 
22
  end
23
 
24
  CLEAN.include SOURCES
 
25
 
26
  SRC = FileList["ext/*.{c,cpp,h}"]
27
 
 
39
  ruby "extconf.rb"
40
  end
41
  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
56
 
57
  file SO_FILE => "ext/Makefile" do |t|
58
  chdir "ext" do
bindings/ruby/ext/extconf.rb CHANGED
@@ -1,212 +1,61 @@
1
- require 'mkmf'
 
2
 
3
- # need to use c++ compiler flags
4
- $CXXFLAGS << ' -std=c++17'
5
 
6
- $LDFLAGS << ' -lstdc++'
7
 
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
17
 
18
- if ENV['WHISPER_METAL']
19
- $GGML_METAL ||= true
20
- $DEPRECATE_WARNING ||= true
21
- end
22
 
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
 
30
  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'
105
  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'
155
  end
156
  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
212
  end
 
1
+ require "mkmf"
2
+ require "tsort"
3
 
4
+ # TODO: options such as CoreML
 
5
 
6
+ cmake = find_executable("cmake") || abort
7
 
8
+ have_library("gomp") rescue nil
 
 
 
 
 
 
 
 
9
 
10
+ prefix = File.join("build", "whisper.cpp.dot")
11
+ system cmake, "-S", "sources", "-B", "build", "--graphviz", prefix, "-D", "BUILD_SHARED_LIBS=OFF", exception: true
 
 
12
 
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)
21
  end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  end
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  end
38
  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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  end
bindings/ruby/ext/sources/CMakeGraphVizOptions.cmake ADDED
@@ -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)
bindings/ruby/extsources.rb CHANGED
@@ -1,6 +1,34 @@
1
- require "yaml"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- sources = `git ls-files -z ../..`.split("\x0")
4
- paths = YAML.load_file("../../.github/workflows/bindings-ruby.yml")[true]["push"]["paths"]
5
- paths.delete "bindings/ruby/**"
6
- EXTSOURCES = (Dir.glob(paths, base: "../..").collect {|path| "../../#{path}"} << "../../LICENSE") & sources
 
 
 
 
 
 
 
1
+ ignored_dirs = %w[
2
+ .devops
3
+ examples/wchess/wchess.wasm
4
+ examples/whisper.android
5
+ examples/whisper.android.java
6
+ examples/whisper.objc
7
+ examples/whisper.swiftui
8
+ grammars
9
+ models
10
+ samples
11
+ scripts
12
+ ]
13
+ ignored_files = %w[
14
+ AUTHORS
15
+ Makefile
16
+ README.md
17
+ README_sycl.md
18
+ .gitignore
19
+ .gitmodules
20
+ whisper.nvim
21
+ twitch.sh
22
+ yt-wsp.sh
23
+ ]
24
 
25
+ EXTSOURCES =
26
+ `git ls-files -z ../..`.split("\x0")
27
+ .select {|file|
28
+ basename = File.basename(file)
29
+
30
+ ignored_dirs.all? {|dir| !file.start_with?("../../#{dir}")} &&
31
+ !ignored_files.include?(basename) &&
32
+ (file.start_with?("../..") || file.start_with?("../javascript")) &&
33
+ (!file.start_with?("../../.github/") || basename == "bindings-ruby.yml")
34
+ }
bindings/ruby/tests/test_package.rb CHANGED
@@ -25,6 +25,8 @@ class TestPackage < TestBase
25
  Dir.mktmpdir do |dir|
26
  system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{filename.shellescape}", exception: true
27
  assert_path_exist File.join(dir, "gems/whispercpp-#{version}/lib", basename)
 
 
28
  end
29
  end
30
  end
 
25
  Dir.mktmpdir do |dir|
26
  system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{filename.shellescape}", exception: true
27
  assert_path_exist File.join(dir, "gems/whispercpp-#{version}/lib", basename)
28
+ assert_path_exist File.join(dir, "gems/whispercpp-#{version}/LICENSE")
29
+ assert_path_not_exist File.join(dir, "gems/whispercpp-#{version}/ext/build")
30
  end
31
  end
32
  end
bindings/ruby/whispercpp.gemspec CHANGED
@@ -15,7 +15,8 @@ Gem::Specification.new do |s|
15
  if s.extra_rdoc_files.include?(basename)
16
  basename
17
  else
18
- file.sub("../..", "ext")
 
19
  end
20
  }
21
 
 
15
  if s.extra_rdoc_files.include?(basename)
16
  basename
17
  else
18
+ file.sub("../..", "ext/sources")
19
+ .sub("../javascript", "ext/sources/bindings/javascript")
20
  end
21
  }
22