KitaitiMakoto commited on
Commit
7953154
·
unverified ·
1 Parent(s): 3f352bd

ruby : handle build options on installation (#3206)

Browse files

* Don't pass empty string to cmake command

* Refactor Dependencies

* Use found cmake path for options

* Maintain extsources.rb

* List dependent files by directory separator agnostic way

* Prepend whitespace before '='

* Handle build options on install

* Remove useless test

* Retrieve gem file name and version from spec file

* Bump version to 1.3.3

* Update date

* Add install option examples

* [skip ci]Remove unused module

bindings/ruby/README.md CHANGED
@@ -24,7 +24,21 @@ or,
24
 
25
  $ gem install whispercpp -- --enable-ggml-cuda
26
 
27
- See whisper.cpp's [README](https://github.com/ggml-org/whisper.cpp/blob/master/README.md) for available options. You need convert options present the README to Ruby-style options.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  For boolean options like `GGML_CUDA`, the README says `-DGGML_CUDA=1`. You need strip `-D`, prepend `--enable-` for `1` or `ON` (`--disable-` for `0` or `OFF`) and make it kebab-case: `--enable-ggml-cuda`.
29
  For options which require arguments like `CMAKE_CUDA_ARCHITECTURES`, the README says `-DCMAKE_CUDA_ARCHITECTURES="86"`. You need strip `-D`, prepend `--`, make it kebab-case, append `=` and append argument: `--cmake-cuda-architectures="86"`.
30
 
 
24
 
25
  $ gem install whispercpp -- --enable-ggml-cuda
26
 
27
+ See whisper.cpp's [README](https://github.com/ggml-org/whisper.cpp/blob/master/README.md) for available options. You need convert options present the README to Ruby-style options, for example:
28
+
29
+ Boolean options:
30
+
31
+ * `-DGGML_BLAS=1` -> `--enable-ggml-blas`
32
+ * `-DWHISER_COREML=OFF` -> `--disable-whisper-coreml`
33
+
34
+ Argument options:
35
+
36
+ * `-DGGML_CUDA_COMPRESSION_MODE=size` -> `--ggml-cuda-compression-mode=size`
37
+
38
+ Combination:
39
+
40
+ * `-DGGML_CUDA=1 -DCMAKE_CUDA_ARCHITECTURES="86"` -> `--enable-ggml-cuda --cmake_cuda-architectures="86"`
41
+
42
  For boolean options like `GGML_CUDA`, the README says `-DGGML_CUDA=1`. You need strip `-D`, prepend `--enable-` for `1` or `ON` (`--disable-` for `0` or `OFF`) and make it kebab-case: `--enable-ggml-cuda`.
43
  For options which require arguments like `CMAKE_CUDA_ARCHITECTURES`, the README says `-DCMAKE_CUDA_ARCHITECTURES="86"`. You need strip `-D`, prepend `--`, make it kebab-case, append `=` and append argument: `--cmake-cuda-architectures="86"`.
44
 
bindings/ruby/ext/dependencies.rb CHANGED
@@ -1,16 +1,28 @@
1
  require "tsort"
2
 
3
  class Dependencies
 
 
4
  def initialize(cmake, options)
5
  @cmake = cmake
6
  @options = options
 
 
 
7
 
8
  generate_dot
9
- @libs = parse_dot
 
 
 
 
 
 
 
10
  end
11
 
12
  def to_s
13
- @libs.join(" ")
14
  end
15
 
16
  private
@@ -20,42 +32,38 @@ class Dependencies
20
  end
21
 
22
  def generate_dot
23
- system @cmake, "-S", "sources", "-B", "build", "--graphviz", dot_path, "-D", "BUILD_SHARED_LIBS=OFF", @options.to_s, exception: true
 
 
24
  end
25
 
26
  def parse_dot
27
- static_lib_shape = nil
28
- nodes = {}
29
- depends = Hash.new {|h, k| h[k] = []}
30
-
31
- class << depends
32
- include TSort
33
- alias tsort_each_node each_key
34
- def tsort_each_child(node, &block)
35
- fetch(node, []).each(&block)
36
- end
37
- end
38
-
39
  File.open(dot_path).each_line do |line|
40
  case line
41
  when /\[\s*label\s*=\s*"Static Library"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]/
42
- static_lib_shape = $~[:shape]
43
  when /\A\s*"(?<node>\w+)"\s*\[\s*label\s*=\s*"(?<label>\S+)"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]\s*;\s*\z/
44
  node = $~[:node]
45
  label = $~[:label]
46
  shape = $~[:shape]
47
- nodes[node] = [label, shape]
48
  when /\A\s*"(?<depender>\w+)"\s*->\s*"(?<dependee>\w+)"/
49
  depender = $~[:depender]
50
  dependee = $~[:dependee]
51
- depends[depender] ||= []
52
- depends[depender] << dependee
53
  end
54
  end
55
- depends.tsort.filter_map {|node|
56
- label, shape = nodes[node]
57
- shape == static_lib_shape ? label : nil
58
- }.collect {|lib| "lib#{lib}.a"}
59
- .reverse
 
 
 
 
 
 
 
60
  end
61
  end
 
1
  require "tsort"
2
 
3
  class Dependencies
4
+ include TSort
5
+
6
  def initialize(cmake, options)
7
  @cmake = cmake
8
  @options = options
9
+ @static_lib_shape = nil
10
+ @nodes = {}
11
+ @graph = Hash.new {|h, k| h[k] = []}
12
 
13
  generate_dot
14
+ parse_dot
15
+ end
16
+
17
+ def libs
18
+ tsort.filter_map {|node|
19
+ label, shape = @nodes[node]
20
+ shape == @static_lib_shape ? label : nil
21
+ }.reverse.collect {|lib| "lib#{lib}.a"}
22
  end
23
 
24
  def to_s
25
+ libs.join(" ")
26
  end
27
 
28
  private
 
32
  end
33
 
34
  def generate_dot
35
+ args = ["-S", "sources", "-B", "build", "--graphviz", dot_path, "-D", "BUILD_SHARED_LIBS=OFF"]
36
+ args << @options.to_s unless @options.to_s.empty?
37
+ system @cmake, *args, exception: true
38
  end
39
 
40
  def parse_dot
 
 
 
 
 
 
 
 
 
 
 
 
41
  File.open(dot_path).each_line do |line|
42
  case line
43
  when /\[\s*label\s*=\s*"Static Library"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]/
44
+ @static_lib_shape = $~[:shape]
45
  when /\A\s*"(?<node>\w+)"\s*\[\s*label\s*=\s*"(?<label>\S+)"\s*,\s*shape\s*=\s*(?<shape>\w+)\s*\]\s*;\s*\z/
46
  node = $~[:node]
47
  label = $~[:label]
48
  shape = $~[:shape]
49
+ @nodes[node] = [label, shape]
50
  when /\A\s*"(?<depender>\w+)"\s*->\s*"(?<dependee>\w+)"/
51
  depender = $~[:depender]
52
  dependee = $~[:dependee]
53
+ @graph[depender] << dependee
 
54
  end
55
  end
56
+ end
57
+
58
+ def tsort_each_node
59
+ @nodes.each_key do |node|
60
+ yield node
61
+ end
62
+ end
63
+
64
+ def tsort_each_child(node)
65
+ @graph[node].each do |child|
66
+ yield child
67
+ end
68
  end
69
  end
bindings/ruby/ext/extconf.rb CHANGED
@@ -3,7 +3,7 @@ require_relative "options"
3
  require_relative "dependencies"
4
 
5
  cmake = find_executable("cmake") || abort
6
- options = Options.new
7
  have_library("gomp") rescue nil
8
  libs = Dependencies.new(cmake, options)
9
 
 
3
  require_relative "dependencies"
4
 
5
  cmake = find_executable("cmake") || abort
6
+ options = Options.new(cmake)
7
  have_library("gomp") rescue nil
8
  libs = Dependencies.new(cmake, options)
9
 
bindings/ruby/ext/options.rb CHANGED
@@ -1,25 +1,11 @@
1
  class Options
2
- def initialize
 
3
  @options = {}
4
- @pending_options = []
5
- @ignored_options = []
6
 
7
  configure
8
  end
9
 
10
- def help
11
- @options
12
- .collect_concat {|name, (type, value)|
13
- option = option_name(name)
14
- if type == :bool
15
- ["--enable-#{option}", "--disable-#{option}"]
16
- else
17
- "--#{option}=#{type.upcase}"
18
- end
19
- }
20
- .join($/)
21
- end
22
-
23
  def to_s
24
  @options
25
  .reject {|name, (type, value)| value.nil?}
@@ -32,7 +18,7 @@ class Options
32
 
33
  output = nil
34
  Dir.chdir __dir__ do
35
- output = `cmake -S sources -B build -L`
36
  end
37
  started = false
38
  @cmake_options = output.lines.filter_map {|line|
@@ -47,175 +33,17 @@ class Options
47
  }
48
  end
49
 
50
- def missing_options
51
- cmake_options.collect {|name, type, value| name} -
52
- @options.keys - @pending_options - @ignored_options
53
- end
54
-
55
- def extra_options
56
- @options.keys + @pending_options + @ignored_options -
57
- cmake_options.collect {|name, type, value| name}
58
- end
59
-
60
  private
61
 
62
  def configure
63
- filepath "ACCELERATE_FRAMEWORK"
64
- ignored "BUILD_SHARED_LIBS"
65
- ignored "BUILD_TESTING"
66
- ignored "CMAKE_BUILD_TYPE"
67
- ignored "CMAKE_INSTALL_PREFIX"
68
- string "CMAKE_OSX_ARCHITECTURES"
69
- ignored "CMAKE_OSX_DEPLOYMENT_TARGET"
70
- string "CMAKE_OSX_SYSROOT"
71
- filepath "FOUNDATION_LIBRARY"
72
- bool "GGML_ACCELERATE"
73
- bool "GGML_ALL_WARNINGS_3RD_PARTY"
74
- bool "GGML_AMX_BF16"
75
- bool "GGML_AMX_INT8"
76
- bool "GGML_AMX_TILE"
77
- bool "GGML_AVX"
78
- bool "GGML_AVX2"
79
- bool "GGML_AVX512"
80
- bool "GGML_AVX512_BF16"
81
- bool "GGML_AVX512_VBMI"
82
- bool "GGML_AVX512_VNNI"
83
- bool "GGML_AVX_VNNI"
84
- ignored "GGML_BACKEND_DL"
85
- ignored "GGML_BIN_INSTALL_DIR"
86
- bool "GGML_BLAS"
87
- string "GGML_BLAS_VENDOR"
88
- bool "GGML_BMI2"
89
- ignored "GGML_BUILD_EXAMPLES"
90
- ignored "GGML_BUILD_TESTS"
91
- bool "GGML_CCACHE"
92
- filepath "GGML_CCACHE_FOUND"
93
- bool "GGML_CPU"
94
- bool "GGML_CPU_AARCH64"
95
- ignored "GGML_CPU_ALL_VARIANTS"
96
- string "GGML_CPU_ARM_ARCH"
97
- bool "GGML_CPU_HBM"
98
- bool "GGML_CPU_KLEIDIAI"
99
- string "GGML_CPU_POWERPC_CPUTYPE"
100
- bool "GGML_CUDA"
101
- string "GGML_CUDA_COMPRESSION_MODE"
102
- bool "GGML_CUDA_F16"
103
- bool "GGML_CUDA_FA"
104
- bool "GGML_CUDA_FA_ALL_QUANTS"
105
- bool "GGML_CUDA_FORCE_CUBLAS"
106
- bool "GGML_CUDA_FORCE_MMQ"
107
- ignored "GGML_CUDA_GRAPHS"
108
- bool "GGML_CUDA_NO_PEER_COPY"
109
- bool "GGML_CUDA_NO_VMM"
110
- string "GGML_CUDA_PEER_MAX_BATCH_SIZE"
111
- bool "GGML_F16C"
112
- bool "GGML_FMA"
113
- bool "GGML_GPROF"
114
- bool "GGML_HIP"
115
- bool "GGML_HIP_GRAPHS"
116
- bool "GGML_HIP_NO_VMM"
117
- bool "GGML_HIP_ROCWMMA_FATTN"
118
- ignored "GGML_INCLUDE_INSTALL_DIR"
119
- bool "GGML_KOMPUTE"
120
- bool "GGML_LASX"
121
- ignored "GGML_LIB_INSTALL_DIR"
122
- ignored "GGML_LLAMAFILE"
123
- bool "GGML_LSX"
124
- bool "GGML_LTO"
125
- bool "GGML_METAL"
126
- bool "GGML_METAL_EMBED_LIBRARY"
127
- string "GGML_METAL_MACOSX_VERSION_MIN"
128
- bool "GGML_METAL_NDEBUG"
129
- bool "GGML_METAL_SHADER_DEBUG"
130
- string "GGML_METAL_STD"
131
- bool "GGML_METAL_USE_BF16"
132
- bool "GGML_MUSA"
133
- bool "GGML_NATIVE"
134
- bool "GGML_OPENCL"
135
- bool "GGML_OPENCL_EMBED_KERNELS"
136
- bool "GGML_OPENCL_PROFILING"
137
- string "GGML_OPENCL_TARGET_VERSION"
138
- bool "GGML_OPENCL_USE_ADRENO_KERNELS"
139
- bool "GGML_OPENMP"
140
- bool "GGML_RPC"
141
- bool "GGML_RVV"
142
- bool "GGML_RV_ZFH"
143
- pending "GGML_SCCACHE_FOUND"
144
- string "GGML_SCHED_MAX_COPIES"
145
- bool "GGML_SSE42"
146
- ignored "GGML_STATIC"
147
- bool "GGML_SYCL"
148
- string "GGML_SYCL_DEVICE_ARCH"
149
- bool "GGML_SYCL_F16"
150
- bool "GGML_SYCL_GRAPH"
151
- string "GGML_SYCL_TARGET"
152
- bool "GGML_SYCL_DNN"
153
- bool "GGML_VULKAN"
154
- bool "GGML_VULKAN_CHECK_RESULTS"
155
- bool "GGML_VULKAN_DEBUG"
156
- bool "GGML_VULKAN_MEMORY_DEBUG"
157
- bool "GGML_VULKAN_PERF"
158
- ignored "GGML_VULKAN_RUN_TESTS"
159
- filepath "GGML_VULKAN_SHADERS_GEN_TOOLCHAIN"
160
- bool "GGML_VULKAN_SHADER_DEBUG_INFO"
161
- pending "GGML_VULKAN_VALIDATE"
162
- bool "GGML_VXE"
163
- bool "GGML_XTHEADVECTOR"
164
- filepath "GIT_EXE"
165
- filepath "MATH_LIBRARY"
166
- filepath "METALKIT_FRAMEWORK"
167
- filepath "METAL_FRAMEWORK"
168
- bool "WHISPER_ALL_WARNINGS"
169
- bool "WHISPER_ALL_WARNINGS_3RD_PARTY"
170
- ignored "WHISPER_BIN_INSTALL_DIR"
171
- ignored "WHISPER_BUILD_EXAMPLES"
172
- ignored "WHISPER_BUILD_SERVER"
173
- ignored"WHISPER_BUILD_TESTS"
174
- bool "WHISPER_COREML"
175
- bool "WHISPER_COREML_ALLOW_FALLBACK"
176
- ignored "WHISPER_CURL"
177
- bool "WHISPER_FATAL_WARNINGS"
178
- ignored "WHISPER_FFMPEG"
179
- ignored "WHISPER_INCLUDE_INSTALL_DIR"
180
- ignored "WHISPER_LIB_INSTALL_DIR"
181
- bool "WHISPER_OPENVINO"
182
- bool "WHISPER_SANITIZE_ADDRESS"
183
- bool "WHISPER_SANITIZE_THREAD"
184
- bool "WHISPER_SANITIZE_UNDEFINED"
185
- ignored "WHISPER_SDL2"
186
- pending "WHISPER_USE_SYSTEM_GGML"
187
  end
188
 
189
  def option_name(name)
190
  name.downcase.gsub("_", "-")
191
  end
192
-
193
- def bool(name)
194
- option = option_name(name)
195
- value = enable_config(option)
196
- @options[name] = [:bool, value]
197
- end
198
-
199
- def string(name, type=:string)
200
- option = "--#{option_name(name)}"
201
- value = arg_config(option)
202
- raise "String expected for #{option}" if value == true || value&.empty?
203
- @options[name] = [type, value]
204
- end
205
-
206
- def path(name)
207
- string(name, :path)
208
- end
209
-
210
- def filepath(name)
211
- string(name, :filepath)
212
- end
213
-
214
- def pending(name)
215
- @pending_options << name
216
- end
217
-
218
- def ignored(name)
219
- @ignored_options << name
220
- end
221
  end
 
1
  class Options
2
+ def initialize(cmake="cmake")
3
+ @cmake = cmake
4
  @options = {}
 
 
5
 
6
  configure
7
  end
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  def to_s
10
  @options
11
  .reject {|name, (type, value)| value.nil?}
 
18
 
19
  output = nil
20
  Dir.chdir __dir__ do
21
+ output = `#{@cmake.shellescape} -S sources -B build -L`
22
  end
23
  started = false
24
  @cmake_options = output.lines.filter_map {|line|
 
33
  }
34
  end
35
 
 
 
 
 
 
 
 
 
 
 
36
  private
37
 
38
  def configure
39
+ cmake_options.each do |name, type, default_value|
40
+ option = option_name(name)
41
+ value = type == "BOOL" ? enable_config(option) : arg_config("--#{option}")
42
+ @options[name] = [type, value]
43
+ end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  end
45
 
46
  def option_name(name)
47
  name.downcase.gsub("_", "-")
48
  end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  end
bindings/ruby/ext/ruby_whisper_vad_params.c CHANGED
@@ -249,7 +249,7 @@ ruby_whisper_vad_params_initialize(int argc, VALUE *argv, VALUE self)
249
  rb_get_kwargs(kw_hash, param_names, 0, NUM_PARAMS, values);
250
 
251
  for (i = 0; i < NUM_PARAMS; i++) {
252
- id= param_names[i];
253
  value = values[i];
254
  if (value == Qundef) {
255
  continue;
 
249
  rb_get_kwargs(kw_hash, param_names, 0, NUM_PARAMS, values);
250
 
251
  for (i = 0; i < NUM_PARAMS; i++) {
252
+ id = param_names[i];
253
  value = values[i];
254
  if (value == Qundef) {
255
  continue;
bindings/ruby/extsources.rb CHANGED
@@ -1,5 +1,10 @@
 
 
 
1
  ignored_dirs = %w[
2
  .devops
 
 
3
  examples/wchess/wchess.wasm
4
  examples/whisper.android
5
  examples/whisper.android.java
@@ -9,7 +14,7 @@ ignored_dirs = %w[
9
  models
10
  samples
11
  scripts
12
- ]
13
  ignored_files = %w[
14
  AUTHORS
15
  Makefile
@@ -17,18 +22,19 @@ ignored_files = %w[
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
  }
 
 
1
+ require "pathname"
2
+
3
+ root = Pathname("..")/".."
4
  ignored_dirs = %w[
5
  .devops
6
+ .github
7
+ ci
8
  examples/wchess/wchess.wasm
9
  examples/whisper.android
10
  examples/whisper.android.java
 
14
  models
15
  samples
16
  scripts
17
+ ].collect {|dir| root/dir}
18
  ignored_files = %w[
19
  AUTHORS
20
  Makefile
 
22
  README_sycl.md
23
  .gitignore
24
  .gitmodules
25
+ .dockerignore
26
  whisper.nvim
27
  twitch.sh
28
  yt-wsp.sh
29
+ close-issue.yml
30
  ]
31
 
32
  EXTSOURCES =
33
+ `git ls-files -z #{root}`.split("\x0")
34
+ .collect {|file| Pathname(file)}
35
+ .reject {|file|
36
+ ignored_dirs.any? {|dir| file.descend.any? {|desc| desc == dir}} ||
37
+ ignored_files.include?(file.basename.to_path) ||
38
+ (file.descend.to_a[1] != root && file.descend.to_a[1] != Pathname("..")/"javascript")
 
 
39
  }
40
+ .collect(&:to_path)
bindings/ruby/tests/helper.rb CHANGED
@@ -21,15 +21,4 @@ class TestBase < Test::Unit::TestCase
21
  def whisper
22
  self.class.whisper
23
  end
24
-
25
- module BuildOptions
26
- load "ext/options.rb", self
27
- Options.include self
28
-
29
- def enable_config(name)
30
- end
31
-
32
- def arg_config(name)
33
- end
34
- end
35
  end
 
21
  def whisper
22
  self.class.whisper
23
  end
 
 
 
 
 
 
 
 
 
 
 
24
  end
bindings/ruby/tests/test_package.rb CHANGED
@@ -18,12 +18,10 @@ class TestPackage < TestBase
18
  end
19
 
20
  def test_install
21
- match_data = `rake -Tbuild`.match(/(whispercpp-(.+)\.gem)/)
22
- filename = match_data[1]
23
- version = match_data[2]
24
  Dir.mktmpdir do |dir|
25
- system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{filename.shellescape}", exception: true
26
- assert_installed dir, version
27
  end
28
  end
29
 
@@ -35,12 +33,4 @@ class TestPackage < TestBase
35
  assert_path_not_exist File.join(dir, "gems/whispercpp-#{version}/ext/build")
36
  end
37
  end
38
-
39
- def test_build_options
40
- options = BuildOptions::Options.new
41
- assert_empty options.missing_options
42
- if ENV["TEST_EXTRA_OPTIONS"] == "1"
43
- assert_empty options.extra_options
44
- end
45
- end
46
  end
 
18
  end
19
 
20
  def test_install
21
+ gemspec = Gem::Specification.load("whispercpp.gemspec")
 
 
22
  Dir.mktmpdir do |dir|
23
+ system "gem", "install", "--install-dir", dir.shellescape, "--no-document", "pkg/#{gemspec.file_name.shellescape}", exception: true
24
+ assert_installed dir, gemspec.version
25
  end
26
  end
27
 
 
33
  assert_path_not_exist File.join(dir, "gems/whispercpp-#{version}/ext/build")
34
  end
35
  end
 
 
 
 
 
 
 
 
36
  end
bindings/ruby/whispercpp.gemspec CHANGED
@@ -3,8 +3,8 @@ require_relative "extsources"
3
  Gem::Specification.new do |s|
4
  s.name = "whispercpp"
5
  s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
6
- s.version = '1.3.2'
7
- s.date = '2025-05-11'
8
  s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
9
  s.email = '[email protected]'
10
  s.extra_rdoc_files = ['LICENSE', 'README.md']
 
3
  Gem::Specification.new do |s|
4
  s.name = "whispercpp"
5
  s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
6
+ s.version = '1.3.3'
7
+ s.date = '2025-05-29'
8
  s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
9
  s.email = '[email protected]'
10
  s.extra_rdoc_files = ['LICENSE', 'README.md']