ruboto 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/COPYING +19 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +21 -0
  4. data/README.md +293 -0
  5. data/Rakefile +114 -0
  6. data/assets/Rakefile +386 -0
  7. data/assets/res/drawable-hdpi/icon.png +0 -0
  8. data/assets/res/drawable-ldpi/icon.png +0 -0
  9. data/assets/res/drawable-mdpi/icon.png +0 -0
  10. data/assets/res/layout/get_ruboto_core.xml +25 -0
  11. data/assets/samples/sample_activity.rb +21 -0
  12. data/assets/samples/sample_activity_test.rb +21 -0
  13. data/assets/samples/sample_broadcast_receiver.rb +6 -0
  14. data/assets/samples/sample_broadcast_receiver_test.rb +1 -0
  15. data/assets/samples/sample_service.rb +14 -0
  16. data/assets/samples/sample_service_test.rb +1 -0
  17. data/assets/src/InheritingActivity.java +195 -0
  18. data/assets/src/InheritingBroadcastReceiver.java +27 -0
  19. data/assets/src/InheritingClass.java +19 -0
  20. data/assets/src/InheritingService.java +9 -0
  21. data/assets/src/RubotoActivity.java +111 -0
  22. data/assets/src/RubotoBroadcastReceiver.java +51 -0
  23. data/assets/src/RubotoService.java +61 -0
  24. data/assets/src/org/ruboto/RubotoDialog.java +11 -0
  25. data/assets/src/org/ruboto/Script.java +545 -0
  26. data/assets/src/org/ruboto/test/ActivityTest.java +63 -0
  27. data/assets/src/org/ruboto/test/InstrumentationTestRunner.java +124 -0
  28. data/assets/src/ruboto.rb +621 -0
  29. data/assets/test/assets/scripts/test_helper.rb +13 -0
  30. data/bin/ruboto +5 -0
  31. data/lib/java_class_gen/InheritingClass.java.erb +10 -0
  32. data/lib/java_class_gen/android_api.xml +1 -0
  33. data/lib/ruboto.rb +16 -0
  34. data/lib/ruboto/api.rb +21 -0
  35. data/lib/ruboto/commands/base.rb +392 -0
  36. data/lib/ruboto/core_ext/array.rb +6 -0
  37. data/lib/ruboto/core_ext/object.rb +10 -0
  38. data/lib/ruboto/util/asset_copier.rb +27 -0
  39. data/lib/ruboto/util/build.rb +201 -0
  40. data/lib/ruboto/util/code_formatting.rb +22 -0
  41. data/lib/ruboto/util/log_action.rb +20 -0
  42. data/lib/ruboto/util/main_fix.rb +13 -0
  43. data/lib/ruboto/util/objectspace.rb +8 -0
  44. data/lib/ruboto/util/scan_in_api.rb +40 -0
  45. data/lib/ruboto/util/update.rb +420 -0
  46. data/lib/ruboto/util/verify.rb +87 -0
  47. data/lib/ruboto/util/xml_element.rb +200 -0
  48. data/lib/ruboto/version.rb +3 -0
  49. data/test/activity/image_button_activity.rb +21 -0
  50. data/test/activity/image_button_activity_test.rb +21 -0
  51. data/test/activity/image_button_and_button_activity.rb +24 -0
  52. data/test/activity/image_button_and_button_activity_test.rb +27 -0
  53. data/test/activity/option_menu_activity.rb +21 -0
  54. data/test/activity/option_menu_activity_test.rb +20 -0
  55. data/test/activity/stack_activity.rb +21 -0
  56. data/test/activity/stack_activity_test.rb +23 -0
  57. data/test/app_test_methods.rb +41 -0
  58. data/test/minimal_app_test.rb +23 -0
  59. data/test/rake_test.rb +40 -0
  60. data/test/ruboto_gen_test.rb +32 -0
  61. data/test/ruboto_gen_with_psych_test.rb +16 -0
  62. data/test/ruboto_update_test.rb +5 -0
  63. data/test/ruboto_update_with_psych_test.rb +18 -0
  64. data/test/service_test.rb +49 -0
  65. data/test/test_helper.rb +177 -0
  66. data/test/update_test_methods.rb +33 -0
  67. metadata +157 -0
@@ -0,0 +1,386 @@
1
+ require 'time'
2
+
3
+ def manifest() @manifest ||= REXML::Document.new(File.read(MANIFEST_FILE)) end
4
+ def package() manifest.root.attribute('package') end
5
+ def build_project_name() @build_project_name ||= REXML::Document.new(File.read('build.xml')).elements['project'].attribute(:name).value end
6
+ def scripts_path() @sdcard_path ||= "/mnt/sdcard/Android/data/#{package}/files/scripts" end
7
+ def app_files_path() @app_files_path ||= "/data/data/#{package}/files" end
8
+
9
+ require 'rake/clean'
10
+ require 'rexml/document'
11
+
12
+ PROJECT_DIR = Dir.getwd
13
+ UPDATE_MARKER_FILE = File.expand_path(File.join('tmp', 'LAST_UPDATE'), File.dirname(__FILE__))
14
+ BUNDLE_JAR = File.expand_path 'libs/bundle.jar'
15
+ BUNDLE_PATH = File.expand_path 'tmp/bundle'
16
+ MANIFEST_FILE = File.expand_path 'AndroidManifest.xml'
17
+ RUBOTO_CONFIG_FILE = File.expand_path 'ruboto.yml'
18
+ GEM_FILE = File.expand_path('Gemfile.apk')
19
+ GEM_LOCK_FILE = File.expand_path('Gemfile.apk.lock')
20
+ RELEASE_APK_FILE = File.expand_path "bin/#{build_project_name}-release.apk"
21
+ APK_FILE = File.expand_path "bin/#{build_project_name}-debug.apk"
22
+ TEST_APK_FILE = File.expand_path "test/bin/#{build_project_name}Test-debug.apk"
23
+ JRUBY_JARS = Dir[File.expand_path 'libs/jruby-*.jar']
24
+ RESOURCE_FILES = Dir[File.expand_path 'res/**/*']
25
+ JAVA_SOURCE_FILES = Dir[File.expand_path 'src/**/*.java']
26
+ RUBY_SOURCE_FILES = Dir[File.expand_path 'src/**/*.rb']
27
+ APK_DEPENDENCIES = [MANIFEST_FILE, RUBOTO_CONFIG_FILE, BUNDLE_JAR] + JRUBY_JARS + JAVA_SOURCE_FILES + RESOURCE_FILES + RUBY_SOURCE_FILES
28
+
29
+ CLEAN.include('tmp', 'bin')
30
+
31
+ task :default => :debug
32
+
33
+ file JRUBY_JARS => RUBOTO_CONFIG_FILE do
34
+ next unless File.exists? RUBOTO_CONFIG_FILE
35
+ jruby_jars_mtime = JRUBY_JARS.map { |f| File.mtime(f) }.min
36
+ ruboto_yml_mtime = File.mtime(RUBOTO_CONFIG_FILE)
37
+ next if jruby_jars_mtime > ruboto_yml_mtime
38
+ puts '*' * 80
39
+ if JRUBY_JARS.empty?
40
+ puts ' The JRuby jars are missing.'
41
+ else
42
+ puts " The JRuby jars need reconfiguring after changes to #{RUBOTO_CONFIG_FILE}"
43
+ puts " #{RUBOTO_CONFIG_FILE}: #{ruboto_yml_mtime}"
44
+ puts " #{JRUBY_JARS.join(', ')}: #{jruby_jars_mtime}"
45
+ end
46
+ puts ' Run "ruboto update jruby" to regenerate the JRuby jars'
47
+ puts '*' * 80
48
+ end
49
+
50
+ desc 'build debug package'
51
+ task :debug => APK_FILE
52
+
53
+ namespace :debug do
54
+ desc 'build debug package if compiled files have changed'
55
+ task :quick => [MANIFEST_FILE, RUBOTO_CONFIG_FILE, BUNDLE_JAR] + JRUBY_JARS + JAVA_SOURCE_FILES + RESOURCE_FILES do |t|
56
+ build_apk(t, false)
57
+ end
58
+ end
59
+
60
+ desc "build package and install it on the emulator or device"
61
+ task :install => APK_FILE do
62
+ install_apk
63
+ end
64
+
65
+ namespace :install do
66
+ desc 'uninstall, build, and install the application'
67
+ task :clean => [:uninstall, APK_FILE, :install]
68
+
69
+ desc 'Install the application, but only if compiled files are changed.'
70
+ task :quick => 'debug:quick' do
71
+ install_apk
72
+ end
73
+ end
74
+
75
+ task :release => RELEASE_APK_FILE
76
+
77
+ file RELEASE_APK_FILE => APK_DEPENDENCIES do |t|
78
+ build_apk(t, true)
79
+ end
80
+
81
+ task :tag => :release do
82
+ unless `git branch` =~ /^\* master$/
83
+ puts "You must be on the master branch to release!"
84
+ exit!
85
+ end
86
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
87
+ sh "git tag #{version}"
88
+ sh "git push origin master --tags"
89
+ #sh "gem push pkg/#{name}-#{version}.gem"
90
+ end
91
+
92
+ task :sign => :release do
93
+ sh "jarsigner -keystore #{ENV['RUBOTO_KEYSTORE']} -signedjar bin/#{build_project_name}.apk bin/#{build_project_name}-unsigned.apk #{ENV['RUBOTO_KEY_ALIAS']}"
94
+ end
95
+
96
+ task :align => :sign do
97
+ sh "zipalign 4 bin/#{build_project_name}.apk #{build_project_name}.apk"
98
+ end
99
+
100
+ task :publish => :align do
101
+ puts "#{build_project_name}.apk is ready for the market!"
102
+ end
103
+
104
+ desc 'Start the emulator with larger disk'
105
+ task :emulator do
106
+ sh 'emulator -partition-size 1024 -avd Android_3.0'
107
+ end
108
+
109
+ task :start do
110
+ start_app
111
+ end
112
+
113
+ task :stop do
114
+ raise "Unable to stop app. Only available on emulator." unless stop_app
115
+ end
116
+
117
+ desc 'Restart the application'
118
+ task :restart => [:stop, :start]
119
+
120
+ task :uninstall do
121
+ uninstall_apk
122
+ end
123
+
124
+ file MANIFEST_FILE
125
+ file RUBOTO_CONFIG_FILE
126
+
127
+ file APK_FILE => APK_DEPENDENCIES do |t|
128
+ build_apk(t, false)
129
+ end
130
+
131
+ desc 'Copy scripts to emulator or device'
132
+ task :update_scripts => ['install:quick'] do
133
+ update_scripts
134
+ end
135
+
136
+ namespace :update_scripts do
137
+ desc 'Copy scripts to emulator and restart the app'
138
+ task :restart => APK_DEPENDENCIES do |t|
139
+ if stop_app
140
+ update_scripts
141
+ else
142
+ build_apk(t, false)
143
+ install_apk
144
+ end
145
+ start_app
146
+ end
147
+ end
148
+
149
+ task :test => :uninstall do
150
+ Dir.chdir('test') do
151
+ puts 'Running tests'
152
+ sh "adb uninstall #{package}.tests"
153
+ sh "ant instrument install test"
154
+ end
155
+ end
156
+
157
+ namespace :test do
158
+ task :quick => :update_scripts do
159
+ Dir.chdir('test') do
160
+ puts 'Running quick tests'
161
+ sh 'ant instrument'
162
+ sh 'ant installi'
163
+ sh "ant run-tests-quick"
164
+ end
165
+ end
166
+ end
167
+
168
+ file GEM_FILE
169
+ file GEM_LOCK_FILE
170
+
171
+ desc 'Generate bundle jar from Gemfile'
172
+ task :bundle => BUNDLE_JAR
173
+
174
+ file BUNDLE_JAR => [GEM_FILE, GEM_LOCK_FILE] do
175
+ next unless File.exists? GEM_FILE
176
+ puts "Generating #{BUNDLE_JAR}"
177
+
178
+ FileUtils.mkdir_p BUNDLE_PATH
179
+ sh "bundle install --gemfile #{GEM_FILE} --path=#{BUNDLE_PATH}"
180
+ gem_path = Dir["#{BUNDLE_PATH}/*ruby/1.8/gems"][0]
181
+
182
+ if package != 'org.ruboto.core' && JRUBY_JARS.none? { |f| File.exists? f }
183
+ Dir.chdir gem_path do
184
+ Dir['activerecord-jdbc-adapter-*'].each do |g|
185
+ puts "Removing #{g} gem since it is included in the RubotoCore platform apk."
186
+ FileUtils.rm_rf g
187
+ end
188
+ end
189
+ end
190
+
191
+ # Remove duplicate files
192
+ Dir.chdir gem_path do
193
+ scanned_files = []
194
+ source_files = RUBY_SOURCE_FILES.map { |f| f.gsub("#{PROJECT_DIR}/src/", '') }
195
+ Dir["*/lib/**/*"].each do |f|
196
+ next if File.directory? f
197
+ raise "Malformed file name" unless f =~ %r{^(.*?)/lib/(.*)$}
198
+ gem_name, lib_file = $1, $2
199
+ if existing_file = scanned_files.find { |sf| sf =~ %r{(.*?)/lib/#{lib_file}} }
200
+ puts "Overwriting duplicate file #{lib_file} in gem #{$1} with file in #{gem_name}"
201
+ FileUtils.rm existing_file
202
+ scanned_files.delete existing_file
203
+ elsif source_files.include? lib_file
204
+ puts "Removing duplicate file #{lib_file} in gem #{gem_name}"
205
+ puts "Already present in project source src/#{lib_file}"
206
+ FileUtils.rm f
207
+ next
208
+ end
209
+ scanned_files << f
210
+ end
211
+ end
212
+
213
+ # Expand JARs
214
+ Dir.chdir gem_path do
215
+ Dir['*'].each do |gem_lib|
216
+ Dir.chdir "#{gem_lib}/lib" do
217
+ Dir['**/*.jar'].each do |jar|
218
+ unless jar =~ /sqlite-jdbc/
219
+ puts "Expanding #{gem_lib} #{jar} into #{BUNDLE_JAR}"
220
+ `jar xf #{jar}`
221
+ end
222
+ if jar == 'arjdbc/jdbc/adapter_java.jar'
223
+ jar_load_code = <<-END_CODE
224
+ require 'jruby'
225
+ Java::arjdbc.jdbc.AdapterJavaService.new.basicLoad(JRuby.runtime)
226
+ END_CODE
227
+ else
228
+ jar_load_code = ''
229
+ end
230
+ puts "Writing dummy JAR file #{jar + '.rb'}"
231
+ File.open(jar + '.rb', 'w') { |f| f << jar_load_code }
232
+ if jar.end_with?('.jar')
233
+ puts "Writing dummy JAR file #{jar.sub(/.jar$/, '.rb')}"
234
+ File.open(jar.sub(/.jar$/, '.rb'), 'w') { |f| f << jar_load_code }
235
+ end
236
+ FileUtils.rm_f(jar)
237
+ end
238
+ end
239
+ end
240
+ end
241
+
242
+
243
+ FileUtils.rm_f BUNDLE_JAR
244
+ Dir["#{gem_path}/*"].each_with_index do |gem_dir, i|
245
+ `jar #{i == 0 ? 'c' : 'u'}f #{BUNDLE_JAR} -C #{gem_dir}/lib .`
246
+ end
247
+ FileUtils.rm_rf BUNDLE_PATH
248
+ end
249
+
250
+ # Methods
251
+
252
+ def mark_update(time = Time.now)
253
+ FileUtils.mkdir_p File.dirname(UPDATE_MARKER_FILE)
254
+ File.open(UPDATE_MARKER_FILE, 'w') { |f| f << time.iso8601 }
255
+ end
256
+
257
+ def clear_update
258
+ mark_update File.ctime APK_FILE
259
+ if device_path_exists?(scripts_path)
260
+ sh "adb shell rm -r #{scripts_path}"
261
+ puts "Deleted scripts directory #{scripts_path}"
262
+ end
263
+ end
264
+
265
+ def strings(name)
266
+ @strings ||= REXML::Document.new(File.read('res/values/strings.xml'))
267
+ value = @strings.elements["//string[@name='#{name.to_s}']"] or raise "string '#{name}' not found in strings.xml"
268
+ value.text
269
+ end
270
+
271
+ def version()
272
+ strings :version_name
273
+ end
274
+
275
+ def app_name()
276
+ strings :app_name
277
+ end
278
+
279
+ def main_activity()
280
+ manifest.root.elements['application'].elements["activity[@android:label='@string/app_name']"].attribute('android:name')
281
+ end
282
+
283
+ def device_path_exists?(path)
284
+ path_output =`adb shell ls #{path}`
285
+ result = path_output.chomp !~ /No such file or directory|opendir failed, Permission denied/
286
+ result
287
+ end
288
+
289
+ def package_installed? test = false
290
+ package_name = "#{package}#{'.tests' if test}"
291
+ ['', '-0', '-1', '-2'].each do |i|
292
+ p = "/data/app/#{package_name}#{i}.apk"
293
+ o = `adb shell ls -l #{p}`.chomp
294
+ if o =~ /^-rw-r--r-- system\s+system\s+(\d+) \d{4}-\d{2}-\d{2} \d{2}:\d{2} #{File.basename(p)}$/
295
+ apk_file = test ? TEST_APK_FILE : APK_FILE
296
+ if !File.exists?(apk_file) || $1.to_i == File.size(apk_file)
297
+ return true
298
+ else
299
+ return false
300
+ end
301
+ end
302
+ end
303
+ return nil
304
+ end
305
+
306
+ private
307
+
308
+ def replace_faulty_code(faulty_file, faulty_code)
309
+ explicit_requires = Dir["#{faulty_file.chomp('.rb')}/*.rb"].sort.map { |f| File.basename(f) }.map do |filename|
310
+ "require 'active_model/validations/#{filename}'"
311
+ end.join("\n")
312
+
313
+ old_code = File.read(faulty_file)
314
+ new_code = old_code.gsub faulty_code, explicit_requires
315
+ if new_code != old_code
316
+ puts "Replaced directory listing code in file #{faulty_file} with explicit requires."
317
+ File.open(faulty_file, 'w') { |f| f << new_code }
318
+ else
319
+ puts "Could not find expected faulty code\n\n#{faulty_code}\n\nin file #{faulty_file}\n\n#{old_code}\n\n"
320
+ end
321
+ end
322
+
323
+ def build_apk(t, release)
324
+ apk_file = release ? RELEASE_APK_FILE : APK_FILE
325
+ if File.exist?(apk_file)
326
+ changed_prereqs = t.prerequisites.select do |p|
327
+ File.file?(p) && !Dir[p].empty? && Dir[p].map { |f| File.mtime(f) }.max > File.mtime(APK_FILE)
328
+ end
329
+ return if changed_prereqs.empty?
330
+ changed_prereqs.each { |f| puts "#{f} changed." }
331
+ puts "Forcing rebuild of #{apk_file}."
332
+ end
333
+ if release
334
+ sh 'ant release'
335
+ else
336
+ sh 'ant debug'
337
+ end
338
+ end
339
+
340
+ def install_apk
341
+ case package_installed?
342
+ when true
343
+ puts "Package already installed."
344
+ return
345
+ when false
346
+ puts "Package installed, but of wrong size."
347
+ end
348
+ sh 'ant installd'
349
+ clear_update
350
+ end
351
+
352
+ def uninstall_apk
353
+ return if package_installed?.nil?
354
+ puts "Uninstalling package #{package}"
355
+ system "adb uninstall #{package}"
356
+ if $? != 0 && package_installed?
357
+ puts "Uninstall failed exit code #{$?}"
358
+ exit $?
359
+ end
360
+ end
361
+
362
+ def update_scripts
363
+ `adb shell mkdir -p #{scripts_path}` if !device_path_exists?(scripts_path)
364
+ puts "Pushing files to apk public file area."
365
+ last_update = File.exists?(UPDATE_MARKER_FILE) ? Time.parse(File.read(UPDATE_MARKER_FILE)) : Time.parse('1970-01-01T00:00:00')
366
+ # TODO(uwe): Use `adb sync src` instead?
367
+ Dir.chdir('src') do
368
+ Dir["**/*.rb"].each do |script_file|
369
+ next if File.directory? script_file
370
+ next if File.mtime(script_file) < last_update
371
+ next if script_file =~ /~$/
372
+ print "#{script_file}: "; $stdout.flush
373
+ `adb push #{script_file} #{scripts_path}/#{script_file}`
374
+ end
375
+ end
376
+ mark_update
377
+ end
378
+
379
+ def start_app
380
+ `adb shell am start -a android.intent.action.MAIN -n #{package}/.#{main_activity}`
381
+ end
382
+
383
+ def stop_app
384
+ output = `adb shell ps | grep #{package} | awk '{print $2}' | xargs adb shell kill`
385
+ return output !~ /Operation not permitted/
386
+ end
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <LinearLayout
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ android:layout_width="fill_parent"
5
+ android:layout_height="fill_parent"
6
+ android:orientation="vertical"
7
+ android:gravity="center_horizontal|center_vertical"
8
+ >
9
+ <!--
10
+ -->
11
+ <ImageButton
12
+ android:id="@+id/image"
13
+ android:layout_width="fill_parent"
14
+ android:layout_height="fill_parent"
15
+ android:src="@drawable/icon"
16
+ android:layout_weight="1"
17
+ android:scaleType="fitCenter"
18
+ android:onClick="getRubotoCore"
19
+ />
20
+ <TextView android:id="@+id/text"
21
+ android:layout_width="wrap_content"
22
+ android:layout_height="wrap_content"
23
+ android:text="Welcome to Ruboto! This is the first Ruboto application installed on this device. To continue, you need to install the Ruboto Core platform package. Click on the image above to go to the market and d\
24
+ ownload and install the Ruboto Core platform package. Then return back here to start the app." />
25
+ </LinearLayout>
@@ -0,0 +1,21 @@
1
+ require 'ruboto'
2
+
3
+ ruboto_import_widgets :Button, :LinearLayout, :TextView
4
+
5
+ $activity.handle_create do |bundle|
6
+ setTitle 'This is the Title'
7
+
8
+ setup_content do
9
+ linear_layout :orientation => LinearLayout::VERTICAL do
10
+ @text_view = text_view :text => 'What hath Matz wrought?', :id => 42
11
+ button :text => 'M-x butterfly', :width => :wrap_content, :id => 43
12
+ end
13
+ end
14
+
15
+ handle_click do |view|
16
+ if view.getText == 'M-x butterfly'
17
+ @text_view.setText 'What hath Matz wrought!'
18
+ toast 'Flipped a bit via butterfly'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ activity Java::THE_PACKAGE.SampleActivity
2
+
3
+ setup do |activity|
4
+ start = Time.now
5
+ loop do
6
+ @text_view = activity.findViewById(42)
7
+ break if @text_view || (Time.now - start > 60)
8
+ sleep 1
9
+ end
10
+ assert @text_view
11
+ end
12
+
13
+ test('initial setup') do |activity|
14
+ assert_equal "What hath Matz wrought?", @text_view.text
15
+ end
16
+
17
+ test('button changes text') do |activity|
18
+ button = activity.findViewById(43)
19
+ button.performClick
20
+ assert_equal "What hath Matz wrought!", @text_view.text
21
+ end