ruboto-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Daniel Jackoway, Charles Nutter, Scott Moyer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ Ruboto Core
2
+ =============
3
+
4
+ Ruby on Android.
5
+
6
+ Installation
7
+ -------
8
+
9
+ $ gem install ruboto-core
10
+
11
+ Getting Started
12
+ ---------------
13
+
14
+ Before you use Ruboto, you should do the following things:
15
+
16
+ * Install the JDK if it's not on your system already
17
+ * Install [jruby](http://jruby.org/) if you don't already have it. JRuby has a [very easy install process](http://jruby.org/#2), or you can use [rvm](http://rvm.beginrescueend.com/)
18
+ * Install [the Android SDK](http://developer.android.com/sdk/index.html)
19
+ * Add the sdk's `tools/` directory to your `$PATH`
20
+ * Generate an [Emulator](http://developer.android.com/guide/developing/tools/emulator.html) image unless you want to develop using your phone.
21
+
22
+ General Information
23
+ ------------------
24
+
25
+ The Rakefile assumes that you are in the root directory of your app, as do all commands of the `ruboto` command line utility, other than `ruboto gen app`.
26
+
27
+ The Rakefile requires you to run it through JRuby's rake.
28
+
29
+ Command-line Tools
30
+ -------
31
+
32
+ * [Application generator](#application_generator) (like the rails application generator)
33
+ * [Class generator](#class_generator) to generate additional Activities, BroadcastReceivers, Services, etc.
34
+ * [Packaging task](#packaging_task) to generate an apk file
35
+ * [Deployment task](#deployment_task) to deploy a generated package to an emulator or connected device
36
+ * [Develop without having to compile to try every change](#update_scripts)
37
+
38
+
39
+ <a name="application_generator"></a>
40
+ ### Application generator
41
+
42
+ $ ruboto gen app --package com.yourdomain.whatever --path path/to/where/you/want/the/app --name NameOfApp --target android-version --activity MainActivityName
43
+ Currently any value but `android-8` will not work.
44
+
45
+ <a name="class_generator"></a>
46
+ ### Class generator
47
+
48
+ $ ruboto gen class ClassName --name YourObjectName
49
+ Ex:
50
+ $ ruboto gen class BroadcastReceiver --name AwesomenessReceiver
51
+
52
+ <a name="packaging_task"></a>
53
+ ### Packaging task
54
+
55
+ This will generate an apk file.
56
+
57
+ $ rake
58
+
59
+ To generate an apk and install it to a connected device (or emulator) all in one go, run
60
+
61
+ $ rake install
62
+
63
+ <a name="deployment_task"></a>
64
+ ### Deployment task
65
+
66
+ When you're ready to post your app to the Market, you need to do a few things.
67
+
68
+ First, you'll need to generate a key to sign the app with using `keytool` if you do not already have one. If you're ok with accepting some sane defaults, you can use
69
+ $ ruboto gen key --alias alias_for_your_key
70
+ with an optional flag `--keystore /path/to/keystore.keystore`, which defaults to `~/.android/production.keystore`. It will ask for a password for the keystore and one for the key itself. Make sure that you remember those two passwords, as well as the alias for the key.
71
+
72
+ Also make sure to keep your key backed up (if you lose it, you won't be able to release updates to your app that can install right over the old versions), but secure.
73
+
74
+ Once you have your key, use the `rake publish` task to generate a market-ready `.apk` file. You will need the `RUBOTO_KEYSTORE` and `RUBOTO_KEY_ALIAS` environment variables set to the path to the keystore and the alias for the key, respectively. So either run
75
+ $ RUBOTO_KEYSTORE=~/.android/production.keystore RUBOTO_KEY_ALIAS=foo rake publish
76
+ or set those environment variables in your `~/.bashrc` or similar file and just run
77
+ $ rake publish
78
+ Now get that `.apk` to the market!
79
+
80
+ <a name="update_scripts"></a>
81
+ ### Updating Your Scripts on a Device
82
+
83
+ With traditional Android development, you have to recompile your app and reinstall it on your test device/emulator every time you make a change. That's slow and annoying.
84
+
85
+ Luckily, with Ruboto, most of your changes are in the scripts, not in the compiles Java files. So if your changes are Ruby-only, you can just run
86
+
87
+ $ rake update_scripts
88
+
89
+ to have it copy the current version of your scripts to your device.
90
+
91
+ Sorry if this takes away your excuse to have sword fights:
92
+
93
+ ![XKCD Code's Compiling](http://imgs.xkcd.com/comics/compiling.png)
94
+
95
+ Caveats:
96
+
97
+ This only works if your changes are all Ruby. If you have Java changes (which would generally just mean generating new classes) or changes to the xml, you will need to recompile your script.
98
+
99
+ Also, you need root access to your device for this to work, as it needs to write to directories that are read-only otherwise. The easiest solution is to test on an emulator, but you can also root your phone.
100
+
101
+ ### Updating Ruboto's Files
102
+
103
+ Not implemented, yet.
104
+
105
+
106
+ Scripts
107
+ -------
108
+
109
+ The main thing Ruboto offers you is the ability to write Ruby scripts to define the behavior of Activites, BroadcastReceievers, and Services. (Eventually it'll be every class. It's setup such that adding in more classes should be trivial.)
110
+
111
+ Here's how it works:
112
+
113
+ First of all, your scripts are found in `assets/scripts/` and the script name is the same as the name of your class, only under_scored instead of CamelCased. Android classes have all of these methods that get called in certain situations. `Activity.onDestroy()` gets called when the activity gets killed, for example. Save weird cases (like the "launching" methods that need to setup JRuby), to script the method onFooBar, you call the Ruby method handle_foo_bar on the Android object. In your scripts, they are defined as `$class_name`. That was really abstract, so here's an example.
114
+
115
+ You generate an app with the option `--activity FooActivity`, which means that ruboto will generate a FooActivity for you. So you open `assets/scripts/foo_activity.rb` in your favorite text editor. If you want an activity that does nothing but Log when it gets launched and when it gets destroyed (in the onCreate and onPause methods). You want your script to look like this:
116
+
117
+ require 'ruboto.rb' #scripts will not work without doing this
118
+ $activity.handle_create do |bundle|
119
+ Log.v 'MYAPPNAME', 'onCreate got called!'
120
+ handle_pause do
121
+ Log.v 'MYAPPNAME', 'onPause got called!'
122
+ end
123
+ end
124
+
125
+ If you prefer, you can also do this. It's equivalent:
126
+
127
+ require 'ruboto.rb' #scripts will not work without doing this
128
+ $activity.handle_create do |bundle|
129
+ Log.v 'MYAPPNAME', 'onCreate got called!'
130
+ end
131
+ $activity.handle_pause do
132
+ Log.v 'MYAPPNAME', 'onPause got called!'
133
+ end
134
+
135
+ Each class has only one method that you can nest other calls inside of (ie. what is happening in that first example that removes the need for the second `$activity.`). For Activities and Services, it is `handle_create`, and for BroadcastReceivers, it is `handle_receive`. The general rule is that it corresponds to the first method in the class's lifecycle. But you should never really have to think about it because generating a class generates a sample script that calls that method.
136
+
137
+ The arguments passed to the block you give `handle_` methods are the same as the arguments that the java methods take. Consult the Android documentation.
138
+
139
+ Activities also have some special methods defined to make things easier. The easiest way to get an idea of what they are is looking over the [demo scripts](http://github.com/ruboto/ruboto-irb/tree/master/assets/demo-scripts/). You can also read the [ruboto.rb file](http://github.com/ruboto/ruboto-core/blob/master/assets/assets/scripts/ruboto.rb) where everything is defined.
140
+
141
+ Contributing
142
+ ------------
143
+
144
+ Want to contribute? Great! Meet us in #ruboto on irc.freenode.net, fork the project and start coding!
145
+
146
+ "But I don't understand it well enough to contribute by forking the project!" That's fine. Equally helpful:
147
+
148
+ * Use Ruboto and tell us how it could be better.
149
+ * As you gain wisdom, contribute it to [the wiki](http://github.com/ruboto/ruboto-core/wiki/)
150
+ * When you gain enough wisdom, reconsider whether you could fork the project.
151
+
152
+ Getting Help
153
+ ------------
154
+
155
+ * You'll need to be pretty familiar with the Android API. The [Developer Guide](http://developer.android.com/guide/index.html) and [Reference](http://developer.android.com/reference/packages.html) are very useful.
156
+ * There is further documentation at the [wiki](http://github.com/ruboto/ruboto-core/wiki)
157
+ * If you have bugs or feature requests, [open an issue on GitHub](http://github.com/ruboto/ruboto-core/issues)
158
+ * You can ask questions in #ruboto on irc.freenode.net and on the [mailing list](http://groups.google.com/groups/ruboto)
159
+ * There are some sample scripts (just Activities) [here](http://github.com/ruboto/ruboto-irb/tree/master/assets/demo-scripts/)
160
+
161
+ Tips & Tricks
162
+ -------------
163
+
164
+ ### Emulators
165
+
166
+ If you're doing a lot of Android development, you'll probably find yourself typing `emulator -avd name_of_emulator` a lot to open emulators. It can be convenient to alias these to shorter commands.
167
+
168
+ For example, in your `~/.bashrc`, `~/.zshrc`, or similar file, you might put
169
+ alias eclair="emulator -avd eclair"
170
+ alias froyo="emulator -avd froyo"
171
+ If you have an "eclair" emulator that runs Android 2.1 and a "froyo" one that runs Android 2.2.
172
+
173
+
174
+ Alternatives
175
+ ------------
176
+
177
+ If Ruboto's performance is a problem for you, or you want something that gives you total access to the android API (as Ruboto does not yet do), check out [Mirah](http://mirah.org/) and [Garrett](http://github.com/technomancy/Garrett).
178
+
179
+ Mirah, formerly known as Duby, is a language with Ruby-like syntax that compiles to java files. This means that it adds no big runtime dependencies and has essentially the same performance as writing Java code because it essentially generates the same Java code that you would write. This makes it extremely well-suited for mobile devices where performance is a much bigger consideration.
180
+
181
+ Garrett is a "playground for Mirah exploration on Android."
182
+
183
+
184
+ Thanks
185
+ -----
186
+
187
+ Thanks go to:
188
+
189
+ * Charles Nutter, a member of the JRuby core team, for being mentoring this RSoC project and starting the Ruboto project in the first place with an [irb](http://github.com/ruboto/ruboto-irb)
190
+ * All of Ruby Summer of Code's [sponsors](http://rubysoc.org/sponsors)
191
+ * [Engine Yard](http://engineyard.com/) in particular for sponsoring RSoC and heavily sponsoring JRuby, which is obviously critical to the project.
192
+ * All [contributors](http://github.com/ruboto/ruboto-core/contributors) and [contributors to the ruboto-irb project](http://github.com/ruboto/ruboto-irb/contributors), as much of this code was taken from ruboto-irb.
data/Rakefile ADDED
@@ -0,0 +1,144 @@
1
+ task :default => :gem
2
+
3
+ task :gem do
4
+ `gem build ruboto-core.gemspec`
5
+ end
6
+
7
+ task :release do
8
+ `gem push ruboto-core-0.0.1.gem`
9
+ end
10
+
11
+ require 'erb'
12
+
13
+ def unprefixed_class(class_name)
14
+ /\.([^\.]+)\z/.match(class_name)[1]
15
+ end
16
+
17
+ # active_support/inflector.rb
18
+ def underscore(camel_cased_word)
19
+ camel_cased_word.to_s.gsub(/::/, '/').
20
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
21
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
22
+ tr("-", "_").
23
+ downcase
24
+ end
25
+
26
+ def transform_return_type(type)
27
+ if type.include?(".")
28
+ return type
29
+ elsif type == "int"
30
+ return "Integer"
31
+ else
32
+ return type.capitalize
33
+ end
34
+ end
35
+
36
+ task :generate_java_classes do
37
+ all_callbacks = eval(IO.read("lib/java_class_gen/interfaces.txt"))
38
+
39
+ @starting_methods = {"BroadcastReceiver" => "onReceive"}
40
+ @starting_methods.default = "onCreate"
41
+
42
+ all_callbacks.each do |full_class, method_hash|
43
+ @class = unprefixed_class full_class
44
+ @callbacks = method_hash
45
+ @full_class = full_class
46
+ @first_method = @starting_methods[@class]
47
+
48
+
49
+ ##############################################################################################
50
+ #
51
+ # This code resolves any issues with the generated callbacks.
52
+ #
53
+ # 1) Remove callbacks that are hard coded in RubotoActivity.erb:
54
+ #
55
+
56
+ if @class == "Activity"
57
+ @callbacks["android.view.View$OnCreateContextMenuListener"].delete("onCreateContextMenu")
58
+ #
59
+ # 2) Remove callbacks that are causing a problem
60
+ #
61
+ @callbacks["android.app.Activity"].delete("onRetainNonConfigurationChildInstances")
62
+ #
63
+ # 3) Override the callback constant for a few key callbacks
64
+ #
65
+ @callbacks["android.app.Activity"]["onMenuItemSelected"]["constant"] = "CB_CREATE_OPTIONS_MENU"
66
+ @callbacks["android.app.Activity"]["onContextItemSelected"]["constant"] = "CB_CREATE_CONTEXT_MENU"
67
+ #
68
+ # 4) Create a unique name for callbacks that have duplicate names
69
+ #
70
+ @callbacks["android.content.DialogInterface$OnClickListener"]["onClick"]["ruby_method"] = "on_dialog_click"
71
+ @callbacks["android.content.DialogInterface$OnClickListener"]["onClick"]["constant"] = "CB_DIALOG_CLICK"
72
+ @callbacks["android.content.DialogInterface$OnKeyListener"]["onKey"]["ruby_method"] = "on_dialog_key"
73
+ @callbacks["android.content.DialogInterface$OnKeyListener"]["onKey"]["constant"] = "CB_DIALOG_KEY"
74
+ @callbacks["android.content.DialogInterface$OnMultiChoiceClickListener"]["onClick"]["ruby_method"] = "on_dialog_multi_choice_click"
75
+ @callbacks["android.content.DialogInterface$OnMultiChoiceClickListener"]["onClick"]["constant"] = "CB_DIALOG_MULTI_CHOICE_CLICK"
76
+ #
77
+ # 5) Report any duplicate name callbacks not handled
78
+ #
79
+ callbacks = {}
80
+ @callbacks.each do |interface,i_info|
81
+ i_info.each do |method,v|
82
+ if callbacks[method] and not v["ruby_method"]
83
+ puts "#{method} in #{interface} and #{callbacks[method]}"
84
+ else
85
+ callbacks[v["ruby_method"] || method] = interface
86
+ end
87
+ end
88
+ end
89
+ #
90
+ # 6) Create a few new special case callbacks
91
+ #
92
+ @callbacks["none"] = {
93
+ "onDraw" => {"args" => ["android.view.View", "android.graphics.Canvas"]},
94
+ "onSizeChanged" => {"args" => ["android.view.View", "int", "int", "int", "int"]}
95
+ }
96
+ end
97
+ #
98
+ ##############################################################################################
99
+ #
100
+ # This code takes the callbacks hash (read out of the interfaces.txt file) and prepares
101
+ # it for use in the code below.
102
+ #
103
+ @implements = []
104
+ @constants = []
105
+ @callbacks.each do |interface,i_info|
106
+ i_info.each do |method,v|
107
+ v["interface"] = interface.gsub("$", ".")
108
+ v["interface"] = "Activity" if v["interface"] == "android.app.Activity"
109
+ v["method"] = method
110
+ v["return_type"] = (v["return_type"] || "void").gsub("$", ".")
111
+ v["interface_method"] = v["interface_method"] || v["method"]
112
+ v["ruby_method"] = v["ruby_method"] || v["method"].gsub(/[A-Z]/) {|i| "_#{i.downcase}"}
113
+
114
+ @implements << v["interface"] if v["interface"] != full_class and
115
+ v["interface"] != @class and
116
+ v["interface"] != "none" and
117
+ not @implements.include?(v["interface"])
118
+
119
+ unless v["constant"]
120
+ constant = v["method"].gsub(/[A-Z]/) {|i| "_#{i}"}.upcase
121
+ constant = constant[3..-1] if constant[0..2] == "ON_"
122
+ v["constant"] = "CB_#{constant}"
123
+ end
124
+ @constants << v["constant"] unless @constants.include?(v["constant"]) || v["method"] == @first_method
125
+
126
+ v["args"] = (v["args"] || [])
127
+ v["args_with_types"], v["args_alone"] = [], []
128
+ v["args"].each_with_index {|arg_type, i| v["args_with_types"] << "#{arg_type.gsub("$", ".")} arg#{i}"; v["args_alone"] << "arg#{i}"}
129
+ v["args_with_types"] = v["args_with_types"].join(", ")
130
+ end
131
+ end
132
+ ##############################################################################################
133
+
134
+
135
+ File.open("assets/src/Inheriting#{@class}.java", "w") do |file|
136
+ file.write ERB.new(IO.read("lib/java_class_gen/InheritingClass.java.erb"), 0, "%").result
137
+ end
138
+
139
+ File.open("assets/src/org/ruboto/Ruboto#{@class}.java", "w") do |file|
140
+ file.write ERB.new(IO.read("lib/java_class_gen/RubotoClass.java.erb"), 0, "%").result
141
+ end
142
+ end
143
+ end
144
+
data/assets/Rakefile ADDED
@@ -0,0 +1,94 @@
1
+ # callback_reflection.rb creates the interfaces.txt (JRuby can't do YAML with ruby 1.8, so it's just
2
+ # and inspect on the hash) on a device. Bring it off the device and put it in the callback_gen dir.
3
+ #
4
+ # Move this into a rake task later.
5
+ #
6
+
7
+
8
+ raise "Needs JRuby 1.5" unless RUBY_PLATFORM =~ /java/
9
+ require 'ant'
10
+ require 'rake/clean'
11
+ require 'rexml/document'
12
+
13
+ generated_libs = 'generated_libs'
14
+ jars = Dir['libs/*.jar']
15
+ stdlib = jars.grep(/stdlib/).first #libs/jruby-stdlib-VERSION.jar
16
+ jruby_jar = jars.grep(/core/).first #libs/jruby-core-VERSION.jar
17
+ stdlib_precompiled = File.join(generated_libs, 'jruby-stdlib-precompiled.jar')
18
+ jruby_ruboto_jar = File.join(generated_libs, 'jruby-ruboto.jar')
19
+ ant.property :name=>'external.libs.dir', :value => generated_libs
20
+ dirs = ['tmp/ruby', 'tmp/precompiled', generated_libs]
21
+ dirs.each { |d| directory d }
22
+
23
+ CLEAN.include('tmp', 'bin', generated_libs)
24
+
25
+ ant_import
26
+
27
+ file stdlib_precompiled => :compile_stdlib
28
+ file jruby_ruboto_jar => generated_libs do
29
+ ant.zip(:destfile=>jruby_ruboto_jar) do
30
+ zipfileset(:src=>jruby_jar) do
31
+ exclude(:name=>'jni/**')
32
+ end
33
+ end
34
+ end
35
+
36
+ desc "precompile ruby stdlib"
37
+ task :compile_stdlib => [:clean, *dirs] do
38
+ ant.unzip(:src=>stdlib, :dest=>'tmp/ruby')
39
+ Dir.chdir('tmp/ruby') { sh "jrubyc . -t ../precompiled" }
40
+ ant.zip(:destfile=>stdlib_precompiled, :basedir=>'tmp/precompiled')
41
+ end
42
+
43
+ task :generate_libs => [generated_libs, jruby_ruboto_jar] do
44
+ cp stdlib, generated_libs
45
+ end
46
+
47
+ task :debug => :generate_libs
48
+ task :release => :generate_libs
49
+
50
+ task :default => :debug
51
+
52
+ task :tag => :release do
53
+ unless `git branch` =~ /^\* master$/
54
+ puts "You must be on the master branch to release!"
55
+ exit!
56
+ end
57
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
58
+ sh "git tag #{version}"
59
+ sh "git push origin master --tags"
60
+ #sh "gem push pkg/#{name}-#{version}.gem"
61
+ end
62
+
63
+ task :sign => :release do
64
+ sh "jarsigner -keystore #{ENV['RUBOTO_KEYSTORE']} -signedjar bin/#{build_project_name}.apk bin/#{build_project_name}-unsigned.apk #{ENV['RUBOTO_KEY_ALIAS']}"
65
+ end
66
+
67
+ task :align => :sign do
68
+ sh "zipalign 4 bin/#{build_project_name}.apk #{build_project_name}.apk"
69
+ end
70
+
71
+ task :publish => :align do
72
+ puts "#{build_project_name}.apk is ready for the market!"
73
+ end
74
+
75
+ task :update_scripts do
76
+ Dir['assets/scripts/*.rb'].each do |script|
77
+ `adb push #{script} /data/data/#{package}/files/scripts`
78
+ end
79
+ end
80
+
81
+ def manifest
82
+ @manifest ||= REXML::Document.new(File.read('AndroidManifest.xml'))
83
+ end
84
+
85
+ def strings(name)
86
+ @strings ||= REXML::Document.new(File.read('res/values/strings.xml'))
87
+ value = @strings.elements["//string[@name='#{name.to_s}']"] or raise "string '#{name}' not found in strings.xml"
88
+ value.text
89
+ end
90
+
91
+ def package() manifest.root.attribute('package') end
92
+ def version() strings :version_name end
93
+ def app_name() strings :app_name end
94
+ def build_project_name() @build_project_name ||= REXML::Document.new(File.read('build.xml')).elements['project'].attribute(:name).value end