How to embed a ruby gem into a C# project and require it from an embedded IronRuby script?
Asked Answered
N

3

16

I have a C# project in which I have embedded an IronRuby program. The project (including my ruby script) is compiled to an .exe file in Visual Studio 2010 for distribution. I'm using a pattern similar to this for bootstrapping the IronRuby script: http://pastebin.com/NKes1cyc (and Jimmy Schementi goes into more detail here: http://blog.jimmy.schementi.com/2009/12/ironruby-rubyconf-2009-part-35.html).

My problem: I would like to embed a gem (json_pure) in my C# assembly and call it from the ruby script.

Some resources I've found:

  1. In JRuby, you can easily package a gem into a jar file and then simply require the jar file at runtime - http://blog.nicksieger.com/articles/2009/01/10/jruby-1-1-6-gems-in-a-jar

  2. The irpack tool (at http://github.com/kumaryu/irpack) is capable of compiling Ruby into an .exe (I think it dynamically creates and compiles a C# project) while embedding the ruby standard library. But it looks like it is only embedding the pre-built IronRuby .dlls, not ruby .rb files. The approach this tool uses would work if I could figure out how to compile Ruby source files into a .dll.

How do I embed an IronRuby gem into a C# assembly (or compile an IronRuby gem to a .dll)?

EDIT:

Page 472 of IronRuby In Action ("Using External Libraries") explains how to require standard ruby libraries from within an embedded ruby file. It involves adding the folder(s) to the runtime search paths collection, as follows (edited for brevity and clarity):

ScriptEngine engine = IronRuby.Ruby.CreateEngine();
var searchPaths = engine.GetSearchPaths().Concat(new[] 
{
    "C:\\Program Files (x86)\\IronRuby 1.1\\Lib\\ruby\\1.9.1",
    "C:\\Program Files (x86)\\IronRuby 1.1\\Lib\\ironruby"
});
engine.SetSearchPaths(searchPaths)

This approach assumes the host machine has IronRuby installed, but I want to embed ruby files (from a gem) into the assembly so it can be run without IronRuby pre-installed.

EDIT 2 (further research):

Reading through the source code of the irpack tool referenced above, I notice that Kumaryu is embedding resources into the final assembly via the System.IO.Packaging.Package class, and then passing the package into the call to msbuild (see https://github.com/kumaryu/irpack/blob/master/lib/irpack/packager.rb). Perhaps some further research into packaging files into an executable would lead to a solution. The problem I see is that ruby files in a gem require other ruby files... can a ruby file in an assembly package require other ruby files in the same package?

EDIT 3:

I haven't gotten any further to an answer yet, but I'd be interested to hear anyone's feedback, even if it's just a link or a suggestion about where to look. I'm new to msbuild, and the documentation is pretty hefty. An initial web search for "msbuild embed zip package" didn't reveal anything relevant.

Neoclassic answered 17/2, 2012 at 18:49 Comment(1)
See blog.nicksieger.com/articles/2009/01/10/… - I would like some help building something that is this easy to use for packaging gems.Neoclassic
C
3

Require is a method that can be overridden like any other ruby method. I think that is how the original ruby gems package worked, gems would simply override require.

If you can package the gem into your .exe somehow, you can override require to load from the assembly.

Conch answered 26/2, 2012 at 2:21 Comment(5)
Some Ruby gems consist of multiple directories of Ruby files. I'd rather have a packaging mechanism that would leave that structure intact (otherwise, I'd have to edit each .rb file in the gem; this wouldn't do for large gems like active_record or json_pure).Neoclassic
It would be nicer to package them automatically, but its not hard to write a script that will package all the .rb files into a .cs file as a Map<Path,Contents>Conch
Great idea! ... however, it would be more difficult if not impossible to compress the Ruby files if they exist as string literals. AFAIK, MSBuild allows you to compress embedded resources. With larger (and multiple) gems this might become important.Neoclassic
I'm not sure what compression is done on compiled .cs files. At worse your string literals could be base64 encodings of a .zipped .rb file.Conch
Accepting this answer because it's the only one so far that gives a complete (albeit difficult) solution.Neoclassic
B
3

try this https://github.com/rifraf/IronRubyAppPackager#readme (or wait for me to report back on whether it works with the library I am trying to embed)

Reporting back:

The process I have gone to is...

  1. fork and submodule all the projects (IronRubyEmbeddedApps, IronRubyAppPackager, Serfs and Vendorize) from https://github.com/rifraf, and added them all into a VS solution. I updated all the projects to .NET4 and made them reference one another, rather than the included .net2 assemblies

  2. executed Vendorize to rip all the dependencies/gems D:\projects\SomeLibrary\lib_vendor_ by running:

    D:\projects\SomeLibrary\lib>ruby -I..\..\Vendorize\lib -rvendorize some_lib.rb

  3. Generated a c# project at D:\projects\SomeLibrary\lib\_IRPackager_\some_lib.csproj by running:

    D:\projects\SomeLibrary\lib>ruby -I..\..\IronRubyAppPackager\lib\IRPackager.rb some_lib.rb

  4. Added the some_lib.csproj to my solution, upgraded it to .net4 and fixed references

  5. At this point, you can run the project in .NET, standalone, by running
var Ruby = new EmbeddedRuby();
Ruby.Decoder = new Program.GZipDecoder();
Ruby.Mount("App");
Ruby.Run("gocardless.rb");

And it succeeded! Brilliant I thought!

Actually at this point I discovered that it had failed in step 2 to Vendorize every single required library. Vendorize works by intercepting require calls and then saving the called rb files off to the _Vendor_ - if require is not executed (so run all tests and hope test coverage is 100%) it won't be picked up.

For some reason, it wasn't picking up one of the required files, even though there was a require. In the end, I manually created the content for the vendor directory by copying my library in, and by using bundle install --deployment command. Repeat step 3 and 4 and I'm done... right?

Nope, I get some error about no such file to load -- json/pure when I run the generated project - I think this is something to do a combination of ironruby not having a native json implementation, me doing the manual vendorizing and me having to bodge things at several points today.

To conclude - this method very very nearly works, will work if your library and it dependencies work with ironruby properly, and you can get vendorize to work properly.

I'm giving up and porting the library to c# the old fashioned way.

edit: as mentioned below, rifraf is helping me and looking into updating the tools I was using. watch t(his) space!

Bouchard answered 28/2, 2012 at 15:31 Comment(5)
Thanks for the link! That looks promising.Neoclassic
I've had trouble with json_pure in the past as well. I know there's a solution (I solved it before). I'll get back to you if I'm able to recall... (perhaps try igem install json_pure?)Neoclassic
I've just tried repeating my instructions and now the libraries own require relative files aren't being vendorized correctly... :(Bouchard
+350 - this is the closest any answer came to a solution to the problem. Just a couple of problems left to wrap it up. Thanks for your help!Neoclassic
ta! but it's worth getting in touch with rifraf if you are going down this route - he'sBouchard
H
3

I am working with https://stackoverflow.com/users/2086/mcintyre321 to get his library wrapped up into C#/IronRuby using my packaging code ( https://github.com/rifraf/IronRubyAppPackager#readme ).

There are some fixes needed in the vendorize step that are related to the later versions of Ruby having RubyGems embedded rather than loaded, but progress is being made.

His code requires 'json', and I have a trick to force it to use the Ruby implementation rather than the C-linkage .so files. This should mean that it will work with IronRuby ok. Will report back, or feel free to contact me with the source that you want to run.

Housetop answered 1/3, 2012 at 7:39 Comment(1)
Waiting for you to report back - the bounty expires soon :)Neoclassic

© 2022 - 2024 — McMap. All rights reserved.