rake-compiler 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ Given %r{^I've installed the Java Development Kit$} do
2
+ pending('Cannot locate suitable Java compiler (the Java Development Kit) in the PATH.') unless search_path(%w(javac javac.exe))
3
+ end
4
+
5
+ Given %r{^I've installed JRuby$} do
6
+ pending('Cannot locate a JRuby installation in the PATH.') unless search_path(%w(jruby jruby.exe jruby.bat))
7
+ end
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'cucumber'
3
2
  require 'spec'
4
3
  require 'fileutils'
@@ -75,6 +75,15 @@ end
75
75
  EOF
76
76
  end
77
77
 
78
+ def template_rake_extension_java_compile(extension_name, gem_spec = nil)
79
+ <<-EOF
80
+ require 'rake/javaextensiontask'
81
+ Rake::JavaExtensionTask.new("#{extension_name}"#{', SPEC' if gem_spec}) do |ext|
82
+ # nothing
83
+ end
84
+ EOF
85
+ end
86
+
78
87
  def template_extconf(extension_name)
79
88
  <<-EOF
80
89
  require 'mkmf'
@@ -97,6 +106,33 @@ EOF
97
106
  #include "ruby.h"
98
107
  EOF
99
108
  end
109
+
110
+ def template_source_java(extension_name)
111
+ <<-EOF
112
+ import org.jruby.Ruby;
113
+ import org.jruby.runtime.load.BasicLibraryService;
114
+
115
+ public class #{camelize(extension_name)}Service implements BasicLibraryService {
116
+ public boolean basicLoad(final Ruby runtime) throws java.io.IOException {
117
+ HelloWorldPrinter hwp = new HelloWorldPrinter();
118
+ hwp.tellTheWorld();
119
+ return true;
120
+ }
121
+
122
+ private class HelloWorldPrinter {
123
+ void tellTheWorld() throws java.io.IOException {
124
+ System.out.println("#{camelize(extension_name)}Service.java of extension #{extension_name}\\n");
125
+ }
126
+ }
127
+ }
128
+
129
+ EOF
130
+ end
131
+
132
+ def camelize(str)
133
+ str.gsub(/(^|_)(.)/) { $2.upcase }
134
+ end
135
+
100
136
  end
101
137
 
102
138
  World(FileTemplateHelpers)
@@ -4,7 +4,7 @@ module GeneratorHelpers
4
4
  FileUtils.mkdir_p "lib"
5
5
  FileUtils.mkdir_p "tasks"
6
6
  FileUtils.mkdir_p "tmp"
7
-
7
+
8
8
  # create Rakefile loader
9
9
  File.open("Rakefile", 'w') do |rakefile|
10
10
  rakefile.puts template_rakefile.strip
@@ -60,6 +60,25 @@ module GeneratorHelpers
60
60
  end
61
61
  end
62
62
 
63
+ def generate_java_compile_extension_task_for(extension_name)
64
+ # create folder structure
65
+ FileUtils.mkdir_p "ext/#{extension_name}"
66
+
67
+ return if File.exist?("tasks/#{extension_name}.rake")
68
+
69
+ # create specific extension rakefile
70
+ # Building a gem?
71
+ if File.exist?("tasks/gem.rake") then
72
+ File.open("tasks/gem.rake", 'a+') do |ext_in_gem|
73
+ ext_in_gem.puts template_rake_extension_java_compile(extension_name, true)
74
+ end
75
+ else
76
+ File.open("tasks/#{extension_name}.rake", 'w') do |ext_rake|
77
+ ext_rake.puts template_rake_extension_java_compile(extension_name)
78
+ end
79
+ end
80
+ end
81
+
63
82
  def generate_multi_cross_compile_extension_task_for(extension_name)
64
83
  # create folder structure
65
84
  FileUtils.mkdir_p "ext/#{extension_name}"
@@ -91,6 +110,14 @@ module GeneratorHelpers
91
110
  ext.puts template_extconf(extension_name)
92
111
  end
93
112
  end
113
+
114
+ def generate_java_source_code_for(extension_name)
115
+ # source .java file
116
+ File.open("ext/#{extension_name}/#{camelize(extension_name)}Service.java", 'w') do |c|
117
+ c.puts template_source_java(extension_name)
118
+ end
119
+ end
120
+
94
121
  end
95
122
 
96
123
  World(GeneratorHelpers)
@@ -5,10 +5,23 @@ module PlatformExtensionHelpers
5
5
  'bundle'
6
6
  when /mingw|mswin|linux/
7
7
  'so'
8
+ when /java/
9
+ 'jar'
8
10
  else
9
11
  RbConfig::CONFIG['DLEXT']
10
12
  end
11
13
  end
14
+
15
+ def search_path(binaries)
16
+ paths = ENV['PATH'].split(File::PATH_SEPARATOR)
17
+ binary = binaries.find do |bin_file|
18
+ paths.find do |path|
19
+ bin = File.join(path, bin_file)
20
+ File.exist?(bin) && File.executable?(bin)
21
+ end
22
+ end
23
+ binary
24
+ end
12
25
  end
13
26
 
14
27
  World(PlatformExtensionHelpers)
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/tasklib'
6
+ require 'rbconfig'
7
+ require 'yaml'
8
+ require 'pathname'
9
+
10
+ module Rake
11
+ autoload :GemPackageTask, 'rake/gempackagetask'
12
+
13
+ class BaseExtensionTask < TaskLib
14
+
15
+ attr_accessor :name
16
+ attr_accessor :gem_spec
17
+ attr_accessor :tmp_dir
18
+ attr_accessor :ext_dir
19
+ attr_accessor :lib_dir
20
+ attr_accessor :platform
21
+ attr_accessor :config_options
22
+ attr_accessor :source_pattern
23
+
24
+ def platform
25
+ @platform ||= RUBY_PLATFORM
26
+ end
27
+
28
+ def initialize(name = nil, gem_spec = nil)
29
+ init(name, gem_spec)
30
+ yield self if block_given?
31
+ define
32
+ end
33
+
34
+ def init(name = nil, gem_spec = nil)
35
+ @name = name
36
+ @gem_spec = gem_spec
37
+ @tmp_dir = 'tmp'
38
+ @ext_dir = "ext/#{@name}"
39
+ @lib_dir = 'lib'
40
+ @config_options = []
41
+ end
42
+
43
+ def define
44
+ fail "Extension name must be provided." if @name.nil?
45
+
46
+ define_compile_tasks
47
+ end
48
+
49
+ private
50
+
51
+ def define_compile_tasks
52
+ raise NotImplementedError
53
+ end
54
+
55
+ def binary(platform = nil)
56
+ ext = case platform
57
+ when /darwin/
58
+ 'bundle'
59
+ when /mingw|mswin|linux/
60
+ 'so'
61
+ when /java/
62
+ 'jar'
63
+ else
64
+ RbConfig::CONFIG['DLEXT']
65
+ end
66
+ "#{@name}.#{ext}"
67
+ end
68
+
69
+ def source_files
70
+ @source_files ||= FileList["#{@ext_dir}/#{@source_pattern}"]
71
+ end
72
+
73
+ def warn_once(message)
74
+ @@already_warned ||= false
75
+ return if @@already_warned
76
+ @@already_warned = true
77
+ warn message
78
+ end
79
+
80
+ def windows?
81
+ Rake.application.windows?
82
+ end
83
+ end
84
+ end
@@ -50,4 +50,4 @@ module Rake
50
50
  @mingw_gcc_executable
51
51
  end
52
52
  end
53
- end
53
+ end
@@ -1,65 +1,51 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'rake/baseextensiontask'
4
+
3
5
  # Define a series of tasks to aid in the compilation of C extensions for
4
6
  # gem developer/creators.
5
7
 
6
- require 'rake'
7
- require 'rake/clean'
8
- require 'rake/tasklib'
9
- require 'rbconfig'
10
- require 'yaml'
11
-
12
8
  module Rake
13
- autoload :GemPackageTask, 'rake/gempackagetask'
9
+ class ExtensionTask < BaseExtensionTask
14
10
 
15
- class ExtensionTask < TaskLib
16
- attr_accessor :name
17
- attr_accessor :gem_spec
18
11
  attr_accessor :config_script
19
- attr_accessor :tmp_dir
20
- attr_accessor :ext_dir
21
- attr_accessor :lib_dir
22
- attr_accessor :platform
23
- attr_accessor :config_options
24
- attr_accessor :source_pattern
25
12
  attr_accessor :cross_compile
26
13
  attr_accessor :cross_platform
27
14
  attr_accessor :cross_config_options
28
-
29
- def initialize(name = nil, gem_spec = nil)
30
- init(name, gem_spec)
31
- yield self if block_given?
32
- define
33
- end
15
+ attr_accessor :no_native
34
16
 
35
17
  def init(name = nil, gem_spec = nil)
36
- @name = name
37
- @gem_spec = gem_spec
18
+ super
38
19
  @config_script = 'extconf.rb'
39
- @tmp_dir = 'tmp'
40
- @ext_dir = "ext/#{@name}"
41
- @lib_dir = 'lib'
42
20
  @source_pattern = "*.c"
43
- @config_options = []
44
21
  @cross_compile = false
45
22
  @cross_config_options = []
46
- end
47
-
48
- def platform
49
- @platform ||= RUBY_PLATFORM
23
+ @cross_compiling = nil
24
+ @no_native = false
50
25
  end
51
26
 
52
27
  def cross_platform
53
28
  @cross_platform ||= 'i386-mingw32'
54
29
  end
55
30
 
31
+ def cross_compiling(&block)
32
+ @cross_compiling = block if block_given?
33
+ end
34
+
56
35
  def define
57
- fail "Extension name must be provided." if @name.nil?
36
+ if RUBY_PLATFORM == 'java' || (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby')
37
+ warn_once <<-EOF
38
+ WARNING: You're attempting to (cross-)compile C extensions from a platform
39
+ (#{RUBY_ENGINE}) that does not support native extensions or mkmf.rb.
40
+ Rerun `rake` under MRI Ruby 1.8.x/1.9.x to cross/native compile.
41
+ EOF
42
+ return
43
+ end
58
44
 
59
- define_compile_tasks
45
+ super
60
46
 
61
47
  # only gems with 'ruby' platforms are allowed to define native tasks
62
- define_native_tasks if @gem_spec && @gem_spec.platform == 'ruby'
48
+ define_native_tasks if !@no_native && (@gem_spec && @gem_spec.platform == 'ruby')
63
49
 
64
50
  # only define cross platform functionality when enabled
65
51
  return unless @cross_compile
@@ -118,8 +104,12 @@ module Rake
118
104
  cmd << '-rfake'
119
105
  end
120
106
 
107
+ # build a relative path to extconf script
108
+ abs_tmp_path = Pathname.new(Dir.pwd) + tmp_path
109
+ abs_extconf = Pathname.new(Dir.pwd) + extconf
110
+
121
111
  # now add the extconf script
122
- cmd << File.join(Dir.pwd, extconf)
112
+ cmd << abs_extconf.relative_path_from(abs_tmp_path)
123
113
 
124
114
  # rbconfig.rb will be present if we are cross compiling
125
115
  if t.prerequisites.include?("#{tmp_path}/rbconfig.rb") then
@@ -164,7 +154,7 @@ module Rake
164
154
  end
165
155
  end
166
156
 
167
- def define_native_tasks(for_platform = nil, ruby_ver = RUBY_VERSION)
157
+ def define_native_tasks(for_platform = nil, ruby_ver = RUBY_VERSION, callback = nil)
168
158
  platf = for_platform || platform
169
159
 
170
160
  # tmp_path
@@ -178,7 +168,7 @@ module Rake
178
168
  task "native:#{@gem_spec.name}:#{platf}" do |t|
179
169
  # FIXME: truly duplicate the Gem::Specification
180
170
  # workaround the lack of #dup for Gem::Specification
181
- spec = Gem::Specification.from_yaml(gem_spec.to_yaml)
171
+ spec = gem_spec.dup
182
172
 
183
173
  # adjust to specified platform
184
174
  spec.platform = Gem::Platform.new(platf)
@@ -197,6 +187,11 @@ module Rake
197
187
  # include the files in the gem specification
198
188
  spec.files += ext_files
199
189
 
190
+ # expose gem specification for customization
191
+ if callback
192
+ callback.call(spec)
193
+ end
194
+
200
195
  # Generate a package for this gem
201
196
  gem_package = Rake::GemPackageTask.new(spec) do |pkg|
202
197
  pkg.need_zip = false
@@ -213,7 +208,7 @@ module Rake
213
208
  file "#{lib_path}/#{binary(platf)}" => ["copy:#{@name}:#{platf}:#{ruby_ver}"]
214
209
  end
215
210
 
216
- # Allow segmented packaging by platfrom (open door for 'cross compile')
211
+ # Allow segmented packaging by platform (open door for 'cross compile')
217
212
  task "native:#{platf}" => ["native:#{@gem_spec.name}:#{platf}"]
218
213
 
219
214
  # Only add this extension to the compile chain if current
@@ -226,7 +221,7 @@ module Rake
226
221
 
227
222
  def define_cross_platform_tasks(for_platform)
228
223
  if ruby_vers = ENV['RUBY_CC_VERSION']
229
- ruby_vers = ENV['RUBY_CC_VERSION'].split(File::PATH_SEPARATOR)
224
+ ruby_vers = ENV['RUBY_CC_VERSION'].split(':')
230
225
  else
231
226
  ruby_vers = [RUBY_VERSION]
232
227
  end
@@ -275,7 +270,7 @@ module Rake
275
270
  # mkmf
276
271
  mkmf_file = File.expand_path(File.join(File.dirname(rbconfig_file), '..', 'mkmf.rb'))
277
272
 
278
- # define compilation tasks for cross platfrom!
273
+ # define compilation tasks for cross platform!
279
274
  define_compile_tasks(for_platform, ruby_ver)
280
275
 
281
276
  # chain fake.rb, rbconfig.rb and mkmf.rb to Makefile generation
@@ -301,7 +296,9 @@ module Rake
301
296
  end
302
297
 
303
298
  # now define native tasks for cross compiled files
304
- define_native_tasks(for_platform, ruby_ver) if @gem_spec && @gem_spec.platform == 'ruby'
299
+ if @gem_spec && @gem_spec.platform == 'ruby' then
300
+ define_native_tasks(for_platform, ruby_ver, @cross_compiling)
301
+ end
305
302
 
306
303
  # create cross task
307
304
  task 'cross' do
@@ -335,19 +332,15 @@ module Rake
335
332
  end
336
333
 
337
334
  def make
338
- RUBY_PLATFORM =~ /mswin/ ? 'nmake' : 'make'
339
- end
340
-
341
- def binary(platform = nil)
342
- ext = case platform
343
- when /darwin/
344
- 'bundle'
345
- when /mingw|mswin|linux/
346
- 'so'
347
- else
348
- RbConfig::CONFIG['DLEXT']
335
+ unless @make
336
+ @make =
337
+ if RUBY_PLATFORM =~ /mswin/ then
338
+ 'nmake'
339
+ else
340
+ ENV['MAKE'] || %w[gmake make].find { |c| system(c, '-v') }
341
+ end
349
342
  end
350
- "#{@name}.#{ext}"
343
+ @make
351
344
  end
352
345
 
353
346
  def source_files
@@ -372,5 +365,6 @@ module Rake
372
365
  end
373
366
  FAKE_RB
374
367
  end
368
+
375
369
  end
376
370
  end
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rake/baseextensiontask'
4
+
5
+ # Define a series of tasks to aid in the compilation of Java extensions for
6
+ # gem developer/creators.
7
+
8
+ module Rake
9
+ class JavaExtensionTask < BaseExtensionTask
10
+
11
+ attr_accessor :classpath
12
+ attr_accessor :debug
13
+
14
+ # Provide source compatibility with specified release
15
+ attr_accessor :source_version
16
+
17
+ # Generate class files for specific VM version
18
+ attr_accessor :target_version
19
+
20
+ def platform
21
+ @platform ||= 'java'
22
+ end
23
+
24
+ def java_compiling(&block)
25
+ @java_compiling = block if block_given?
26
+ end
27
+
28
+ def init(name = nil, gem_spec = nil)
29
+ super
30
+ @source_pattern = '**/*.java'
31
+ @classpath = nil
32
+ @java_compiling = nil
33
+ @debug = false
34
+ @source_version = '1.5'
35
+ @target_version = '1.5'
36
+ end
37
+
38
+ def define
39
+ super
40
+
41
+ define_java_platform_tasks
42
+ end
43
+
44
+ private
45
+ def define_compile_tasks(for_platform = nil, ruby_ver = RUBY_VERSION)
46
+ # platform usage
47
+ platf = for_platform || platform
48
+
49
+ # lib_path
50
+ lib_path = lib_dir
51
+
52
+ # tmp_path
53
+ tmp_path = "#{@tmp_dir}/#{platf}/#{@name}"
54
+
55
+ # cleanup and clobbering
56
+ CLEAN.include(tmp_path)
57
+ CLOBBER.include("#{lib_path}/#{binary(platf)}")
58
+ CLOBBER.include("#{@tmp_dir}")
59
+
60
+ # directories we need
61
+ directory tmp_path
62
+ directory lib_dir
63
+
64
+ # copy binary from temporary location to final lib
65
+ # tmp/extension_name/extension_name.{so,bundle} => lib/
66
+ task "copy:#{@name}:#{platf}" => [lib_path, "#{tmp_path}/#{binary(platf)}"] do
67
+ cp "#{tmp_path}/#{binary(platf)}", "#{lib_path}/#{binary(platf)}"
68
+ end
69
+
70
+ not_jruby_compile_msg = <<-EOF
71
+ WARNING: You're cross-compiling a binary extension for JRuby, but are using
72
+ another interpreter. If your Java classpath or extension dir settings are not
73
+ correctly detected, then either check the appropriate environment variables or
74
+ execute the Rake compilation task using the JRuby interpreter.
75
+ (e.g. `jruby -S rake compile:java`)
76
+ EOF
77
+ warn_once(not_jruby_compile_msg) unless defined?(JRUBY_VERSION)
78
+
79
+ file "#{tmp_path}/#{binary(platf)}" => "#{tmp_path}/.build" do
80
+
81
+ class_files = FileList["#{tmp_path}/**/*.class"].
82
+ gsub("#{tmp_path}/", '')
83
+
84
+ # avoid environment variable expansion using backslash
85
+ class_files.gsub!('$', '\$') unless windows?
86
+
87
+ args = class_files.map { |path|
88
+ ["-C #{tmp_path}", path]
89
+ }.flatten
90
+
91
+ sh "jar cf #{tmp_path}/#{binary(platf)} #{args.join(' ')}"
92
+ end
93
+
94
+ file "#{tmp_path}/.build" => [tmp_path] + source_files do
95
+ classpath_arg = java_classpath_arg(@classpath)
96
+ debug_arg = @debug ? '-g' : ''
97
+
98
+ sh "javac #{java_extdirs_arg} -target #{@target_version} -source #{@source_version} -Xlint:unchecked #{debug_arg} #{classpath_arg} -d #{tmp_path} #{source_files.join(' ')}"
99
+
100
+ # Checkpoint file
101
+ touch "#{tmp_path}/.build"
102
+ end
103
+
104
+ # compile tasks
105
+ unless Rake::Task.task_defined?('compile') then
106
+ desc "Compile all the extensions"
107
+ task "compile"
108
+ end
109
+
110
+ # compile:name
111
+ unless Rake::Task.task_defined?("compile:#{@name}") then
112
+ desc "Compile #{@name}"
113
+ task "compile:#{@name}"
114
+ end
115
+
116
+ # Allow segmented compilation by platform (open door for 'cross compile')
117
+ task "compile:#{@name}:#{platf}" => ["copy:#{@name}:#{platf}"]
118
+ task "compile:#{platf}" => ["compile:#{@name}:#{platf}"]
119
+
120
+ # Only add this extension to the compile chain if current
121
+ # platform matches the indicated one.
122
+ if platf == RUBY_PLATFORM then
123
+ # ensure file is always copied
124
+ file "#{lib_path}/#{binary(platf)}" => ["copy:#{name}:#{platf}"]
125
+
126
+ task "compile:#{@name}" => ["compile:#{@name}:#{platf}"]
127
+ task "compile" => ["compile:#{platf}"]
128
+ end
129
+ end
130
+
131
+ def define_java_platform_tasks
132
+ # lib_path
133
+ lib_path = lib_dir
134
+
135
+ if @gem_spec && !Rake::Task.task_defined?("java:#{@gem_spec.name}")
136
+ task "java:#{@gem_spec.name}" do |t|
137
+
138
+ # FIXME: truly duplicate the Gem::Specification
139
+ spec = gem_spec.dup
140
+
141
+ # adjust to specified platform
142
+ spec.platform = Gem::Platform.new('java')
143
+
144
+ # clear the extensions defined in the specs
145
+ spec.extensions.clear
146
+
147
+ # add the binaries that this task depends on
148
+ ext_files = []
149
+
150
+ # go through native prerequisites and grab the real extension files from there
151
+ t.prerequisites.each do |ext|
152
+ ext_files << ext
153
+ end
154
+
155
+ # include the files in the gem specification
156
+ spec.files += ext_files
157
+
158
+ # expose gem specification for customization
159
+ if @java_compiling
160
+ @java_compiling.call(spec)
161
+ end
162
+
163
+ # Generate a package for this gem
164
+ gem_package = Rake::GemPackageTask.new(spec) do |pkg|
165
+ pkg.need_zip = false
166
+ pkg.need_tar = false
167
+ end
168
+ end
169
+
170
+ # add binaries to the dependency chain
171
+ task "java:#{@gem_spec.name}" => ["#{lib_path}/#{binary(platform)}"]
172
+
173
+ # ensure the extension get copied
174
+ unless Rake::Task.task_defined?("#{lib_path}/#{binary(platform)}") then
175
+ file "#{lib_path}/#{binary(platform)}" => ["copy:#{name}:#{platform}"]
176
+ end
177
+
178
+ task 'java' => ["java:#{@gem_spec.name}"]
179
+ end
180
+
181
+ task 'java' do
182
+ task 'compile' => 'compile:java'
183
+ end
184
+ end
185
+
186
+ #
187
+ # Discover Java Extension Directories and build an extdirs argument
188
+ #
189
+ def java_extdirs_arg
190
+ extdirs = Java::java.lang.System.getProperty('java.ext.dirs') rescue nil
191
+ extdirs = ENV['JAVA_EXT_DIR'] unless extdirs
192
+ java_extdir = extdirs.nil? ? "" : "-extdirs \"#{extdirs}\""
193
+ end
194
+
195
+ #
196
+ # Discover the Java/JRuby classpath and build a classpath argument
197
+ #
198
+ # @params
199
+ # *args:: Additional classpath arguments to append
200
+ #
201
+ # Copied verbatim from the ActiveRecord-JDBC project. There are a small myriad
202
+ # of ways to discover the Java classpath correctly.
203
+ #
204
+ def java_classpath_arg(*args)
205
+ jruby_cpath = nil
206
+ if RUBY_PLATFORM =~ /java/
207
+ begin
208
+ cpath = Java::java.lang.System.getProperty('java.class.path').split(File::PATH_SEPARATOR)
209
+ cpath += Java::java.lang.System.getProperty('sun.boot.class.path').split(File::PATH_SEPARATOR)
210
+ jruby_cpath = cpath.compact.join(File::PATH_SEPARATOR)
211
+ rescue => e
212
+ end
213
+ end
214
+ unless jruby_cpath
215
+ jruby_cpath = ENV['JRUBY_PARENT_CLASSPATH'] || ENV['JRUBY_HOME'] &&
216
+ Dir.glob("#{File.expand_path(ENV['JRUBY_HOME'])}/lib/*.jar").
217
+ join(File::PATH_SEPARATOR)
218
+ end
219
+ raise "JRUBY_HOME or JRUBY_PARENT_CLASSPATH are not set" unless jruby_cpath
220
+ jruby_cpath += File::PATH_SEPARATOR + args.join(File::PATH_SEPARATOR) unless args.empty?
221
+ jruby_cpath ? "-cp \"#{jruby_cpath}\"" : ""
222
+ end
223
+
224
+ end
225
+ end