Articles Feed

Authors

Categories

Embedded Ruby Sings Sinatra

by: doug | December 30th, 2008 | 2 comments »

Last Time I got ruby up and running on my Arm based embedded development platform. Here is a quick summary of what Santa and I did over Christmas.

Sockets

Getting support for sockets built into Ruby turned out to not be hard at all. All I had to do has uncomment the extensions I wanted in ext/Setup. Here is everything I decided to turn on.
  1. etc
  2. fcntl
  3. iconv
  4. socket
  5. stringio
  6. strscan
  7. syck
  8. thread
  9. zlib
After a rebuild and reinstall, I could require and use 'socket.' Welcome, Ruby, to the outside world!

Relocating Ruby

I decided to move ruby to the 'standard' install directory (/usr/local/) instead of the root that I had done in the first iteration. This would avoid having to set the RUBYLIB environment variable and keep any mess I made out out of the main system /bin and /lib directories. First I removed the --prefix from the configure script I had written last time. This will cause Ruby to build assuming the default install location (/usr/local/).
  1. #! /bin/sh
  2. export ARM_TOOLS=/usr/local/arm/gcc-4.2.3-glibc-2.3.3/arm-unknown-linux-gnu/bin
  3. export CC=$ARM_TOOLS/arm-unknown-linux-gnu-gcc
  4. export LD=$ARM_TOOLS/arm-unknown-linux-gnu-gcc
  5. export AR=$ARM_TOOLS/arm-unknown-linux-gnu-ar
  6. export RANLIB=$ARM_TOOLS/arm-unknown-linux-gnu-ranlib
  7. export ac_cv_func_getpgrp_void=yes
  8. export ac_cv_func_setpgrp_void=yes
  9. ./configure --host=arm-unknown-linux --enable-wide-getaddrinfo
After Ruby was built, but before installing it (make install), I hacked the DESTDIR in rbconfig.rb
  1. ...
  2. TOPDIR = File.dirname(__FILE__).chomp!("/lib/ruby/1.8/arm-linux")
  3. DESTDIR = "/~/ruby/install"
  4. CONFIG = {}
  5. ...
This created the /usr/local tree inside my install directory. I created a /usr/local/ on my file system and copied that tree over. The only problem with this process is that the sh-bang line on the top of all of the ruby scrips (irb, testrb, etc.) had the wrong path in them. They had the install path on the host machine (/~/ruby/install/usr/local/bin/ruby) instead of the actual path on the target (/usr/local/bin/ruby). I changed those by hand.

Sinatra

Ahh, now the good part. Using a gem on an embedded system is an interesting problem. I first had to get rubygems installed on the network files system. I downloaded the source code and temporarily placed in on the target files system. On the target I ran the install command.
  1. ruby setup.rb --no-rdoc --no-ri
I found it more convenient to install the gems from the host system instead of the target. It would be faster and the 'gem' command had some ruby dependencies that I hadn't yet build. I downloaded Sinatra and Rack and then manually installed them into my target's file system on the host machine. Note that /arm/fs/ is the root of my networked file system. I switched off rdoc and ri to help keep the install small.
  1. sudo gem install sinatra-0.3.2.gem -i /arm/fs/usr/local/lib/ruby/gems/1.8 --no-rdoc --no-ri
  2. sudo gem install rack-0.4.0.gem -i /arm/fs/usr/local/lib/ruby/gems/1.8 --no-rdoc --no-ri
With Sinatra installed and ready to go, I wrote a quick Sinatra app. Sinatra runs on Mongrel by default, but I wanted to run on the lighter weight Webrick web server which was already in my ruby installation.
  1. require 'rubygems'
  2. require 'rack/handler/webrick'
  3. require 'sinatra'
  4. Sinatra::Application.default_options.merge!(
  5. :run => false,
  6. :env => :production,
  7. :port => 80
  8. )
  9. get '/' do
  10. "Hello Sinatra!"
  11. end
  12. Rack::Handler::WEBrick.run Sinatra.application
Drum roll please ...

Next Steps

I've been plugging away on the linux device and ruby extension side of the project. I'm still on the steep side of the learning curve, but making progress. I'm still after that elusive blinking LED!

Embedded Ruby - First Steps

by: doug | December 19th, 2008 | 3 comments »

I spent the beginning of my career working on embedded systems for some audio company. These embedded systems were my first love in programming and to me, no flashy css on a web page can beat the thrill of writing some code that flashes an LED. I've been working lately with Ruby a lot. I love it. I love that the language nearly disappears and I am able to express pure intent. Ok, so there is still syntax and such, but I can create so much more with so much less code.
I hate living a fragmented existence, so I've been working the last few days on bridging these two worlds of mine. I figured that it will also give me a chance to get more familiar with linux and the latest in the embedded world.

First up, pick a platform.

I chose this single board computer from Glomation. It has an ARM920T core and an LCD / touch screen interface which may be fun later.

Next, Tools.

I was determined that I could get a toolchain up and running on my MAC, but eventually ended up running linux in a virtual machine. Glomation had a set of tools and a pre-patched kernel already to go on their support page and our apprentice had a virtualbox linux image that he let me copy, so I was up and running quickly.
I do want to share with you a few of the resources I found in the processes.

Cross-Compiling Ruby

A few folks have had some success cross compiling ruby and Goodlad's presentation claimed that ruby is a part of the open embedded project. I built ruby 1.8.6-p111 because that what was on my linux distribution and it seems to be a well distributed release. Here is the script I used to configure ruby for cross compilation. Thanks to The Zen Machine blog for the skeleton of this script.
  1. #! /bin/sh
  2. export ARM_TOOLS=/usr/.../arm-unknown-linux-gnu/bin
  3. export CC=$ARM_TOOLS/arm-unknown-linux-gnu-gcc
  4. export LD=$ARM_TOOLS/arm-unknown-linux-gnu-gcc
  5. export AR=$ARM_TOOLS/arm-unknown-linux-gnu-ar
  6. export RANLIB=$ARM_TOOLS/arm-unknown-linux-gnu-ranlib
  7. export ac_cv_func_getpgrp_void=yes
  8. export ac_cv_func_setpgrp_void=yes
  9. ./configure --host=arm-unknown-linux --prefix=$HOME/ruby/install
ARM_TOOLS is where I installed the cross compiler. The prefix is just the place where 'make install' will drop the results of the build. The configure script generates a file in the ruby root director called fake.rb that causes some problems when I went to 'make'
  1. /ruby-1.8.6-p111/fake.rb:12: unterminated string meets end of file (SyntaxError)
I opened it up and it has a very obvious syntax error in it. I added the missing backslash on line 12 and ruby built fine from there.
  1. 12 ALT_SEPARATOR = "\";
becomes . . .
  1. 12 ALT_SEPARATOR = "\\";
'make' then 'make install' and I had my cross compiled ruby

Getting Ruby onto the Target

The last step to seeing little rubies flying on my ARM core was to get the build onto the target. The Glomation board came preloaded with a kernel and a root file system. The kernel source was provided on the support page but not the root file system. I asked their support guy and he kindly posted it for me. I was dreading having to rebuild a file system from scratch, so I was grateful for his help.
I decided that the best option would be to just have the target load of the root file system over the network on boot. I followed these instructions for mounting the image on my linux VM and these instructions for setting up an NFS Server. Mounting the file system over the network is a great option because now I can edit the file system from my Mac or linux VM and have the target see the changes without even rebooting. Perfect! The 2440 board came loaded with UBoot and all I had to do to load the file system over the network was to set the nfsroot in the bootargs from the bootloader's command line.
  1. set bootargs 'nfsroot=10.0.1.151:/arm/fs rw console=ttySAC0,115200 ip=dhcp init=/linuxrc'
Once booted, I only needed to set the RUBYLIB environment variable so that ruby knows where to find the libraries. I added this to my init script.
  1. export RUBYLIB=/lib/ruby/1.8
And there you have it. Ruby running on my ARM board:

Next Steps

Here is where I want to go from here. Thanks for reading. Let me know if you have any ideas or suggestions!

One Take on Configuring Rails Routes and asset_host

by: jim | December 2nd, 2008 | 1 comments »

We recently had an interesting requirement surface. In anticipation of the release of a number of demo environments, our customer requested that system configuration be able to be done at the server level. The goal was to avoid being forced to use source control to manage configuration files.

A little background is in order. The systems that we are working on are pretty large. There are two distinct, decoupled systems, each consisting of between 10 and 40 subsystems. Of these subsystems, a handful on each side are Rails applications. Most others are small Ruby application that talk a central Rinda server. We already have a configuration strategy in place for the two systems, which involves a central configuration file per system which populates a globally-accessible configuration hash.

The directory structure looks something like this:

/deployment /deployment/system_one /deployment/system_one/etc /deployment/system_one/etc/config.rb /deployment/system_one/rails_app_one /deployment/system_one/non_rails_subsystem_one

The systemone/etc/config.rb file creates the configuration hash and populates it with all of the appropriate configurations (in practice, config.rb actually loads the appropriate configuration file based on an environment variable, i.e. RAILSENV, but we will ignore that here for brevity and clarity):

  1. APPLICATION_CONFIGURATION ||= {}
  2. APPLICATION_CONFIGURATION[:foo] = "bar"

From the context of a Rails application, this central configuration file is loaded from railsappone/config/environment.rb:

  1. # Load the system configuration
  2. require File.expand_path("/deployment/system_one/etc/config")
  3. # Bootstrap the Rails environment, frameworks, and default configuration
  4. require File.join(File.dirname(__FILE__), 'boot')

With this configuration strategy in place already, the team was tasked with setting up multiple demo environments for the entire system, each deployed on a different server and accessed through a different external URL. We could have attempted to solve the configuration issues by just adding many separate Rails environments, say demo1 and demo2, but there were a couple problems with that. First, the configurations for the different environments would have been nearly identical. Second, we wanted to avoid the complexity of relying on source control to manage the configurations for each deployment. In most cases, relying on the Rails convention of setting up a new environment makes a lot of sense. However, in our case, the only differences between the environments would be setting the asset_host and routes. Storing this in source control means that in order to make a change to the external URL means that files under source control need to be modified, checked in and then redeployed to the affected system. It makes much more sense to have some reasonable defaults in source control and then provide a mechanism to override these configurations at the server level.

The solution to this problem ended up being quite simple. We first agreed on an acceptable location for the server configuration file:

deployment/config deployment/config/server_config.rb

In order to allow for overriding the system configuration, this code was added to /deployment/system_one/etc/config.rb:

  1. # Allow for configuration to be overridden by a config file that is not under source control
  2. server_config_file = File.expand_path("/deployment/config/server_config.rb")
  3. require server_config_file if File.exists?(server_config_file)

The server configuration in deployment/config/serverconfig.rb will be loaded (and executed), if it exists. If it doesn’t exist, the default configurations will be used, which is the desirable behavior. The first task was to configure the assethost. To start with, we add the desired assethost configuration to /deployment/config/serverconfig.rb

  1. APPLICATION_CONFIGURATION[:my_rails_app_asset_host] = "https://www.example.com/my_rails_app/demo"

For each Rails environment that we want the assethost to be configurable from the server configuration file, just add this line to the appropriate config file, e.g. deployment/systemone/railsappone/config/environments/demo.rb:

  1. config.action_controller.asset_host = APPLICATION_CONFIGURATION[:my_rails_app_asset_host]

You would want to add a separate assethost configuration for each Rails application, and each Rails application would set its own assethost to the appropriate configuration. For a simple configuration like asset_host, this works great. For routes, though, it gets a bit more complicated. We need a Mapper instance in order to build a route. For example, your routes configuration looks something like this:

  1. ActionController::Routing::Routes.draw do |map|
  2. map.connect ':controller/:action/:id'
  3. end

We initially saw two options. 1) Create a data structure representing the desired routes, store it in the configuration, then from the routes file iterate through the structure created in the configuration file, creating the appropriate routes; or 2) Store some Ruby code in the configuration so that the context (i.e. the Mapper instance) can be passed to the block at the run-time. This second option eliminates the need for a secondary data structure to represent the routes. How better to configure the routes than with the actual code used to configure them?

It turned out that this was nearly as simple as the assethost configuration. First, let’s define the routes in /deployment/config/serverconfig.rb:

  1. APPLICATION_CONFIGURATION[:my_rails_app_routes] = lambda do |map|
  2. map.connect 'my_rails_app/demo/:controller/:action/:id'
  3. map.connect 'my_rails_app/demo', :controller => "login", :action => "index"
  4. map.login 'my_rails_app/demo/login', :controller => "login", :action => "index"
  5. end

We have used the lambda method to convert a block into a Proc object. The Proc object is stored as a value in a hash which can be executed later. The rest should look familiar to anyone using Rails; it is exactly what we would have had in our routes file. Now, from /deployment/systemone/railsapp_one/config/routes.rb, we can do this:

  1. ActionController::Routing::Routes.draw do |map|
  2. APPLICATION_CONFIGURATION[:my_rails_app_routes].call(map) if APPLICATION_CONFIGURATION[:my_rails_app_routes]
  3. # Install the default route as the lowest priority.
  4. map.connect ':controller/:action/:id.:format'
  5. end

In order to invoke the Proc object stored in serverconfig.rb, we just send the call message to the Proc stored in the configuration hash, if it exists. In our implementation, we invoke the Proc configured in serverconfig.rb before all of the other (default) routes, with the assumption that the configured routes should have the highest priority. If we run into a case where this isn’t the case, we can address that problem then.

###