ruboto 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Gemfile.lock +9 -9
  2. data/README.md +2 -0
  3. data/Rakefile +71 -7
  4. data/assets/Rakefile +2 -407
  5. data/assets/libs/dexmaker20120305.jar +0 -0
  6. data/assets/rakelib/ruboto.rake +428 -0
  7. data/assets/samples/sample_broadcast_receiver.rb +7 -3
  8. data/assets/samples/sample_broadcast_receiver_test.rb +47 -1
  9. data/assets/src/RubotoActivity.java +6 -2
  10. data/assets/src/org/ruboto/EntryPointActivity.java +2 -2
  11. data/assets/src/org/ruboto/Script.java +91 -24
  12. data/assets/src/org/ruboto/test/ActivityTest.java +27 -24
  13. data/assets/src/org/ruboto/test/InstrumentationTestRunner.java +42 -8
  14. data/assets/src/ruboto/activity.rb +1 -1
  15. data/assets/src/ruboto/base.rb +17 -6
  16. data/assets/src/ruboto/generate.rb +458 -0
  17. data/assets/src/ruboto/legacy.rb +9 -12
  18. data/assets/src/ruboto/widget.rb +9 -1
  19. data/lib/java_class_gen/android_api.xml +1 -1
  20. data/lib/ruboto.rb +1 -2
  21. data/lib/ruboto/commands/base.rb +19 -4
  22. data/lib/ruboto/sdk_versions.rb +12 -0
  23. data/lib/ruboto/util/build.rb +10 -11
  24. data/lib/ruboto/util/update.rb +150 -51
  25. data/lib/ruboto/util/verify.rb +6 -4
  26. data/lib/ruboto/util/xml_element.rb +2 -2
  27. data/lib/ruboto/version.rb +1 -1
  28. data/test/activity/option_menu_activity.rb +5 -1
  29. data/test/activity/psych_activity.rb +11 -6
  30. data/test/activity/stack_activity_test.rb +13 -5
  31. data/test/app_test_methods.rb +4 -3
  32. data/test/broadcast_receiver_test.rb +86 -0
  33. data/test/minimal_app_test.rb +27 -19
  34. data/test/rake_test.rb +13 -2
  35. data/test/ruboto_gen_test.rb +17 -3
  36. data/test/ruboto_update_test.rb +24 -2
  37. data/test/service_test.rb +1 -1
  38. data/test/test_helper.rb +134 -62
  39. data/test/update_test_methods.rb +40 -14
  40. data/test/updated_example_test_methods.rb +41 -0
  41. metadata +12 -6
Binary file
@@ -0,0 +1,428 @@
1
+ if `ant -version` !~ /version (\d+)\.(\d+)\.(\d+)/ || $1.to_i < 1 || ($1.to_i == 1 && $2.to_i < 8)
2
+ puts "ANT version 1.8.0 or later required. Version found: #{$1}.#{$2}.#{$3}"
3
+ exit 1
4
+ end
5
+
6
+ require 'time'
7
+
8
+ def manifest() @manifest ||= REXML::Document.new(File.read(MANIFEST_FILE)) end
9
+ def package() manifest.root.attribute('package') end
10
+ def build_project_name() @build_project_name ||= REXML::Document.new(File.read('build.xml')).elements['project'].attribute(:name).value end
11
+ def scripts_path() @sdcard_path ||= "/mnt/sdcard/Android/data/#{package}/files/scripts" end
12
+ def app_files_path() @app_files_path ||= "/data/data/#{package}/files" end
13
+
14
+ require 'rake/clean'
15
+ require 'rexml/document'
16
+
17
+ PROJECT_DIR = File.expand_path('..', File.dirname(__FILE__))
18
+ UPDATE_MARKER_FILE = File.join(PROJECT_DIR, 'bin', 'LAST_UPDATE')
19
+ BUNDLE_JAR = File.expand_path 'libs/bundle.jar'
20
+ BUNDLE_PATH = File.expand_path 'bin/bundle'
21
+ MANIFEST_FILE = File.expand_path 'AndroidManifest.xml'
22
+ RUBOTO_CONFIG_FILE = File.expand_path 'ruboto.yml'
23
+ GEM_FILE = File.expand_path('Gemfile.apk')
24
+ GEM_LOCK_FILE = File.expand_path('Gemfile.apk.lock')
25
+ RELEASE_APK_FILE = File.expand_path "bin/#{build_project_name}-release.apk"
26
+ APK_FILE = File.expand_path "bin/#{build_project_name}-debug.apk"
27
+ TEST_APK_FILE = File.expand_path "test/bin/#{build_project_name}Test-debug.apk"
28
+ JRUBY_JARS = Dir[File.expand_path 'libs/jruby-*.jar']
29
+ RESOURCE_FILES = Dir[File.expand_path 'res/**/*']
30
+ JAVA_SOURCE_FILES = Dir[File.expand_path 'src/**/*.java']
31
+ RUBY_SOURCE_FILES = Dir[File.expand_path 'src/**/*.rb']
32
+ APK_DEPENDENCIES = [MANIFEST_FILE, RUBOTO_CONFIG_FILE, BUNDLE_JAR] + JRUBY_JARS + JAVA_SOURCE_FILES + RESOURCE_FILES + RUBY_SOURCE_FILES
33
+ KEYSTORE_FILE = (key_store = File.readlines('ant.properties').grep(/^key.store=/).first) ? File.expand_path(key_store.chomp.sub(/^key.store=/, '').sub('${user.home}', '~')) : "#{build_project_name}.keystore"
34
+ KEYSTORE_ALIAS = (key_alias = File.readlines('ant.properties').grep(/^key.alias=/).first) ? key_alias.chomp.sub(/^key.alias=/, '') : build_project_name
35
+
36
+ CLEAN.include('bin')
37
+
38
+ task :default => :debug
39
+
40
+ file JRUBY_JARS => RUBOTO_CONFIG_FILE do
41
+ next unless File.exists? RUBOTO_CONFIG_FILE
42
+ jruby_jars_mtime = JRUBY_JARS.map { |f| File.mtime(f) }.min
43
+ ruboto_yml_mtime = File.mtime(RUBOTO_CONFIG_FILE)
44
+ next if jruby_jars_mtime > ruboto_yml_mtime
45
+ puts '*' * 80
46
+ if JRUBY_JARS.empty?
47
+ puts ' The JRuby jars are missing.'
48
+ else
49
+ puts " The JRuby jars need reconfiguring after changes to #{RUBOTO_CONFIG_FILE}"
50
+ puts " #{RUBOTO_CONFIG_FILE}: #{ruboto_yml_mtime}"
51
+ puts " #{JRUBY_JARS.join(', ')}: #{jruby_jars_mtime}"
52
+ end
53
+ puts ' Run "ruboto update jruby" to regenerate the JRuby jars'
54
+ puts '*' * 80
55
+ end
56
+
57
+ desc 'build debug package'
58
+ task :debug => APK_FILE
59
+
60
+ namespace :debug do
61
+ desc 'build debug package if compiled files have changed'
62
+ task :quick => [MANIFEST_FILE, RUBOTO_CONFIG_FILE, BUNDLE_JAR] + JRUBY_JARS + JAVA_SOURCE_FILES + RESOURCE_FILES do |t|
63
+ build_apk(t, false)
64
+ end
65
+ end
66
+
67
+ desc "build package and install it on the emulator or device"
68
+ task :install => APK_FILE do
69
+ install_apk
70
+ end
71
+
72
+ namespace :install do
73
+ desc 'uninstall, build, and install the application'
74
+ task :clean => [:uninstall, APK_FILE, :install]
75
+
76
+ desc 'Install the application, but only if compiled files are changed.'
77
+ task :quick => 'debug:quick' do
78
+ install_apk
79
+ end
80
+ end
81
+
82
+ desc 'Build APK for release'
83
+ task :release => RELEASE_APK_FILE
84
+
85
+ file RELEASE_APK_FILE => [KEYSTORE_FILE] + APK_DEPENDENCIES do |t|
86
+ build_apk(t, true)
87
+ end
88
+
89
+ desc 'Create a keystore for signing the release APK'
90
+ file KEYSTORE_FILE do
91
+ unless File.read('ant.properties') =~ /^key.store=/
92
+ File.open('ant.properties', 'a'){|f| f << "\nkey.store=#{KEYSTORE_FILE}\n"}
93
+ end
94
+ unless File.read('ant.properties') =~ /^key.alias=/
95
+ File.open('ant.properties', 'a'){|f| f << "\nkey.alias=#{KEYSTORE_ALIAS}\n"}
96
+ end
97
+ sh "keytool -genkey -v -keystore #{KEYSTORE_FILE} -alias #{KEYSTORE_ALIAS} -keyalg RSA -keysize 2048 -validity 10000"
98
+ end
99
+
100
+ desc 'Tag this working copy with the current version'
101
+ task :tag => :release do
102
+ unless `git branch` =~ /^\* master$/
103
+ puts "You must be on the master branch to release!"
104
+ exit!
105
+ end
106
+ # sh "git commit --allow-empty -a -m 'Release #{version}'"
107
+ output = `git status --porcelain`
108
+ raise "Workspace not clean!\n#{output}" unless output.empty?
109
+ sh "git tag #{version}"
110
+ sh "git push origin master --tags"
111
+ end
112
+
113
+ task :sign => :release do
114
+ sh "jarsigner -keystore #{ENV['RUBOTO_KEYSTORE']} -signedjar bin/#{build_project_name}.apk bin/#{build_project_name}-unsigned.apk #{ENV['RUBOTO_KEY_ALIAS']}"
115
+ end
116
+
117
+ task :align => :sign do
118
+ sh "zipalign 4 bin/#{build_project_name}.apk #{build_project_name}.apk"
119
+ end
120
+
121
+ task :publish => :align do
122
+ puts "#{build_project_name}.apk is ready for the market!"
123
+ end
124
+
125
+ desc 'Start the emulator with larger disk'
126
+ task :emulator do
127
+ sh 'emulator -partition-size 1024 -avd Android_3.0'
128
+ end
129
+
130
+ task :start do
131
+ start_app
132
+ end
133
+
134
+ task :stop do
135
+ raise "Unable to stop app. Only available on emulator." unless stop_app
136
+ end
137
+
138
+ desc 'Restart the application'
139
+ task :restart => [:stop, :start]
140
+
141
+ task :uninstall do
142
+ uninstall_apk
143
+ end
144
+
145
+ file MANIFEST_FILE
146
+ file RUBOTO_CONFIG_FILE
147
+
148
+ file APK_FILE => APK_DEPENDENCIES do |t|
149
+ build_apk(t, false)
150
+ end
151
+
152
+ desc 'Copy scripts to emulator or device'
153
+ task :update_scripts => ['install:quick'] do
154
+ update_scripts
155
+ end
156
+
157
+ namespace :update_scripts do
158
+ desc 'Copy scripts to emulator and restart the app'
159
+ task :restart => APK_DEPENDENCIES do |t|
160
+ if build_apk(t, false) || !stop_app
161
+ install_apk
162
+ else
163
+ update_scripts
164
+ end
165
+ start_app
166
+ end
167
+ end
168
+
169
+ task :test => :uninstall do
170
+ Dir.chdir('test') do
171
+ puts 'Running tests'
172
+ sh "adb uninstall #{package}.tests"
173
+ sh "ant instrument install test"
174
+ end
175
+ end
176
+
177
+ namespace :test do
178
+ task :quick => :update_scripts do
179
+ Dir.chdir('test') do
180
+ puts 'Running quick tests'
181
+ sh 'ant instrument'
182
+ sh 'ant installi'
183
+ sh "ant run-tests-quick"
184
+ end
185
+ end
186
+ end
187
+
188
+ file GEM_FILE
189
+ file GEM_LOCK_FILE
190
+
191
+ desc 'Generate bundle jar from Gemfile'
192
+ task :bundle => BUNDLE_JAR
193
+
194
+ file BUNDLE_JAR => [GEM_FILE, GEM_LOCK_FILE] do
195
+ next unless File.exists? GEM_FILE
196
+ puts "Generating #{BUNDLE_JAR}"
197
+
198
+ FileUtils.mkdir_p BUNDLE_PATH
199
+ sh "bundle install --gemfile #{GEM_FILE} --path=#{BUNDLE_PATH}"
200
+ gem_path = Dir["#{BUNDLE_PATH}/*ruby/1.8/gems"][0]
201
+
202
+ if package != 'org.ruboto.core' && JRUBY_JARS.none? { |f| File.exists? f }
203
+ Dir.chdir gem_path do
204
+ Dir['{activerecord-jdbc-adapter, jruby-openssl}-*'].each do |g|
205
+ puts "Removing #{g} gem since it is included in the RubotoCore platform apk."
206
+ FileUtils.rm_rf g
207
+ end
208
+ end
209
+ else
210
+ Dir.chdir gem_path do
211
+ Dir['jruby-openssl-*/lib'].each do |g|
212
+ rel_dir = "#{g}/lib/ruby"
213
+ unless File.exists? rel_dir
214
+ puts "Relocating #{g} files to match standard load path."
215
+ dirs = Dir["#{g}/*"]
216
+ FileUtils.mkdir_p rel_dir
217
+ dirs.each do |d|
218
+ FileUtils.move d, rel_dir
219
+ end
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ # Remove duplicate files
226
+ Dir.chdir gem_path do
227
+ scanned_files = []
228
+ source_files = RUBY_SOURCE_FILES.map { |f| f.gsub("#{PROJECT_DIR}/src/", '') }
229
+ Dir["*/lib/**/*"].each do |f|
230
+ next if File.directory? f
231
+ raise "Malformed file name" unless f =~ %r{^(.*?)/lib/(.*)$}
232
+ gem_name, lib_file = $1, $2
233
+ if existing_file = scanned_files.find { |sf| sf =~ %r{(.*?)/lib/#{lib_file}} }
234
+ puts "Overwriting duplicate file #{lib_file} in gem #{$1} with file in #{gem_name}"
235
+ FileUtils.rm existing_file
236
+ scanned_files.delete existing_file
237
+ elsif source_files.include? lib_file
238
+ puts "Removing duplicate file #{lib_file} in gem #{gem_name}"
239
+ puts "Already present in project source src/#{lib_file}"
240
+ FileUtils.rm f
241
+ next
242
+ end
243
+ scanned_files << f
244
+ end
245
+ end
246
+
247
+ # Expand JARs
248
+ Dir.chdir gem_path do
249
+ Dir['*'].each do |gem_lib|
250
+ Dir.chdir "#{gem_lib}/lib" do
251
+ Dir['**/*.jar'].each do |jar|
252
+ if jar == 'arjdbc/jdbc/adapter_java.jar'
253
+ jar_load_code = <<-END_CODE
254
+ require 'jruby'
255
+ Java::arjdbc.jdbc.AdapterJavaService.new.basicLoad(JRuby.runtime)
256
+ END_CODE
257
+ elsif jar =~ /shared\/jopenssl.jar$/
258
+ jar_load_code = <<-END_CODE
259
+ require 'jruby'
260
+ puts 'Starting JRuby OpenSSL Service'
261
+ public
262
+ Java::JopensslService.new.basicLoad(JRuby.runtime)
263
+ END_CODE
264
+ else
265
+ jar_load_code = ''
266
+ end
267
+ unless jar =~ /sqlite-jdbc/
268
+ puts "Expanding #{gem_lib} #{jar} into #{BUNDLE_JAR}"
269
+ `jar xf #{jar}`
270
+ end
271
+ puts "Writing dummy JAR file #{jar + '.rb'}"
272
+ File.open(jar + '.rb', 'w') { |f| f << jar_load_code }
273
+ if jar.end_with?('.jar')
274
+ puts "Writing dummy JAR file #{jar.sub(/.jar$/, '.rb')}"
275
+ File.open(jar.sub(/.jar$/, '.rb'), 'w') { |f| f << jar_load_code }
276
+ end
277
+ FileUtils.rm_f(jar)
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+
284
+ FileUtils.rm_f BUNDLE_JAR
285
+ Dir["#{gem_path}/*"].each_with_index do |gem_dir, i|
286
+ `jar #{i == 0 ? 'c' : 'u'}f #{BUNDLE_JAR} -C #{gem_dir}/lib .`
287
+ end
288
+ FileUtils.rm_rf BUNDLE_PATH
289
+ end
290
+
291
+ # Methods
292
+
293
+ def mark_update(time = Time.now)
294
+ FileUtils.mkdir_p File.dirname(UPDATE_MARKER_FILE)
295
+ File.open(UPDATE_MARKER_FILE, 'w') { |f| f << time.iso8601 }
296
+ end
297
+
298
+ def clear_update
299
+ mark_update File.ctime APK_FILE
300
+ if device_path_exists?(scripts_path)
301
+ sh "adb shell rm -r #{scripts_path}"
302
+ puts "Deleted scripts directory #{scripts_path}"
303
+ end
304
+ end
305
+
306
+ def strings(name)
307
+ @strings ||= REXML::Document.new(File.read('res/values/strings.xml'))
308
+ value = @strings.elements["//string[@name='#{name.to_s}']"] or raise "string '#{name}' not found in strings.xml"
309
+ value.text
310
+ end
311
+
312
+ def version()
313
+ strings :version_name
314
+ end
315
+
316
+ def app_name()
317
+ strings :app_name
318
+ end
319
+
320
+ def main_activity()
321
+ manifest.root.elements['application'].elements["activity[@android:label='@string/app_name']"].attribute('android:name')
322
+ end
323
+
324
+ def device_path_exists?(path)
325
+ path_output =`adb shell ls #{path}`
326
+ result = path_output.chomp !~ /No such file or directory|opendir failed, Permission denied/
327
+ result
328
+ end
329
+
330
+ def package_installed? test = false
331
+ package_name = "#{package}#{'.tests' if test}"
332
+ ['', '-0', '-1', '-2'].each do |i|
333
+ p = "/data/app/#{package_name}#{i}.apk"
334
+ o = `adb shell ls -l #{p}`.chomp
335
+ if o =~ /^-rw-r--r-- system\s+system\s+(\d+) \d{4}-\d{2}-\d{2} \d{2}:\d{2} #{File.basename(p)}$/
336
+ apk_file = test ? TEST_APK_FILE : APK_FILE
337
+ if !File.exists?(apk_file) || $1.to_i == File.size(apk_file)
338
+ return true
339
+ else
340
+ return false
341
+ end
342
+ end
343
+ end
344
+ return nil
345
+ end
346
+
347
+ private
348
+
349
+ def replace_faulty_code(faulty_file, faulty_code)
350
+ explicit_requires = Dir["#{faulty_file.chomp('.rb')}/*.rb"].sort.map { |f| File.basename(f) }.map do |filename|
351
+ "require 'active_model/validations/#{filename}'"
352
+ end.join("\n")
353
+
354
+ old_code = File.read(faulty_file)
355
+ new_code = old_code.gsub faulty_code, explicit_requires
356
+ if new_code != old_code
357
+ puts "Replaced directory listing code in file #{faulty_file} with explicit requires."
358
+ File.open(faulty_file, 'w') { |f| f << new_code }
359
+ else
360
+ puts "Could not find expected faulty code\n\n#{faulty_code}\n\nin file #{faulty_file}\n\n#{old_code}\n\n"
361
+ end
362
+ end
363
+
364
+ def build_apk(t, release)
365
+ apk_file = release ? RELEASE_APK_FILE : APK_FILE
366
+ if File.exist?(apk_file)
367
+ changed_prereqs = t.prerequisites.select do |p|
368
+ File.file?(p) && !Dir[p].empty? && Dir[p].map { |f| File.mtime(f) }.max > File.mtime(APK_FILE)
369
+ end
370
+ return false if changed_prereqs.empty?
371
+ changed_prereqs.each { |f| puts "#{f} changed." }
372
+ puts "Forcing rebuild of #{apk_file}."
373
+ end
374
+ if release
375
+ sh 'ant release'
376
+ else
377
+ sh 'ant debug'
378
+ end
379
+ return true
380
+ end
381
+
382
+ def install_apk
383
+ case package_installed?
384
+ when true
385
+ puts "Package #{package} already installed."
386
+ return
387
+ when false
388
+ puts "Package installed, but of wrong size."
389
+ end
390
+ sh 'ant installd'
391
+ clear_update
392
+ end
393
+
394
+ def uninstall_apk
395
+ return if package_installed?.nil?
396
+ puts "Uninstalling package #{package}"
397
+ system "adb uninstall #{package}"
398
+ if $? != 0 && package_installed?
399
+ puts "Uninstall failed exit code #{$?}"
400
+ exit $?
401
+ end
402
+ end
403
+
404
+ def update_scripts
405
+ `adb shell mkdir -p #{scripts_path}` if !device_path_exists?(scripts_path)
406
+ puts "Pushing files to apk public file area."
407
+ last_update = File.exists?(UPDATE_MARKER_FILE) ? Time.parse(File.read(UPDATE_MARKER_FILE)) : Time.parse('1970-01-01T00:00:00')
408
+ # TODO(uwe): Use `adb sync src` instead?
409
+ Dir.chdir('src') do
410
+ Dir["**/*.rb"].each do |script_file|
411
+ next if File.directory? script_file
412
+ next if File.mtime(script_file) < last_update
413
+ next if script_file =~ /~$/
414
+ print "#{script_file}: "; $stdout.flush
415
+ `adb push #{script_file} #{scripts_path}/#{script_file}`
416
+ end
417
+ end
418
+ mark_update
419
+ end
420
+
421
+ def start_app
422
+ `adb shell am start -a android.intent.action.MAIN -n #{package}/.#{main_activity}`
423
+ end
424
+
425
+ def stop_app
426
+ output = `adb shell ps | grep #{package} | awk '{print $2}' | xargs adb shell kill`
427
+ return output !~ /Operation not permitted/
428
+ end
@@ -1,9 +1,13 @@
1
1
  require 'ruboto/broadcast_receiver'
2
2
 
3
- # will get called whenever the BroadcastReceiver receives an intent (whenever onReceive is called)
3
+ import android.util.Log
4
+
4
5
  RubotoBroadcastReceiver.new_with_callbacks do
6
+ # will get called whenever the BroadcastReceiver receives an intent (whenever onReceive is called)
5
7
  def on_receive(context, intent)
6
- Log.v "MYAPP", intent.getExtras.to_s
8
+ Log.v "SampleBroadcastReceiver", 'Broadcast received!'
9
+ Log.v "SampleBroadcastReceiver", intent.getExtras.to_s
10
+ context.run_on_ui_thread{$activity.title = 'Broadcast received!'}
11
+ Log.v "SampleBroadcastReceiver", 'Broadcast processed OK!'
7
12
  end
8
13
  end
9
-
@@ -1 +1,47 @@
1
- # TODO
1
+ # Change this to the activity that will send the broadcast
2
+ activity Java::THE_PACKAGE.SampleActivity
3
+
4
+ # Change this to wait for the activity to be created
5
+ setup do |activity|
6
+ start = Time.now
7
+ loop do
8
+ @text_view = activity.findViewById(42)
9
+ break if @text_view || (Time.now - start > 60)
10
+ sleep 1
11
+ end
12
+ assert @text_view
13
+ end
14
+
15
+ # Change this to trigger the sending of broadcast intents
16
+ # and assert that the receiver behaves correctly.
17
+ test('broadcast changes title', :ui => false) do |activity|
18
+ begin
19
+ @receiver = $package.SampleBroadcastReceiver.new
20
+ action = '__THE_PACKAGE__.SampleBroadcastReceiver.action'
21
+ filter = android.content.IntentFilter.new(action)
22
+ receiver_ready = false
23
+ Thread.start do
24
+ begin
25
+ android.os.Looper.prepare
26
+ activity.registerReceiver(@receiver, filter, nil, android.os.Handler.new)
27
+ receiver_ready = true
28
+ android.os.Looper.loop
29
+ rescue
30
+ puts "Exception starting receiver"
31
+ puts $!.message
32
+ puts $!.backtrace.join("\n")
33
+ end
34
+ end
35
+ sleep 0.1 until receiver_ready
36
+ intent = android.content.Intent.new
37
+ intent.set_action action
38
+ activity.send_broadcast(intent)
39
+ bc_sent_at = Time.now
40
+
41
+ message = 'Broadcast received!'
42
+ sleep 0.1 until activity.title == message || (Time.now - bc_sent_at) > 10
43
+ assert_equal message, activity.title
44
+ ensure
45
+ activity.unregister_receiver(@receiver) if @receiver
46
+ end
47
+ end