This article will show you how to integrate a C library in a Rubymotion OSX project. We are going to build an app that lets you render a markdown text into HTML using the C reference implementation of the CommonMark project.

You can find the full source code of the finished project over on Github

Last Updated: 18 May 2019 - corrected Brett Simmons to Brett Walker

There is also a sample project showing how to call Object-C classes from Rubymotion here (thanks Amir for pointing this out) and a related blog post from Clay Allsop.

Although I have got a few years of programming in Rubymotion, I always thought of vendoring C libraries as some kind of black magic, but it is easier than it seems.

Aside: If you would like to build an app with Markdown rendering in Rubymotion you might also want to check out Brett Walker’s @digitalmoksha gem motion-markdown-it. Brett built his fantastic app VersatilMarkdown on this technologoy.

Here is a quick overview of what we are going to do:

  1. Create the Rubymotion project
  2. Download and build cmark
  3. Vendor the cmark library in the Rubymotion project
  4. Using cmark from the app
  5. Wrap things up

Creating the Rubymotion project

Let’s get started with our new project. Open up the Terminal, and fire

$ motion create --template=osx mdmotion

You should see something like:

[!] You may want to run `motion repo` if it's been a while since you've updated templates.
    Create mdmotion
    Create mdmotion/.gitignore
    Create mdmotion/Gemfile
    Create mdmotion/README.md
    Create mdmotion/Rakefile
    Create mdmotion/app/app_delegate.rb
    Create mdmotion/app/menu.rb
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/1024x1024.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Contents.json
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png
    Create mdmotion/resources/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png
    Create mdmotion/resources/Assets.xcassets/Contents.json
    Create mdmotion/resources/Credits.rtf
    Create mdmotion/spec/main_spec.rb

To see if everything is working, cd into the project directory and build the application.

$ cd mdmotion
$ bundle
$ bundle exec rake

This should give you a blank application window, which shows that you succesfully created the basic project.

Downloading and building cmark

Next we will download the cmark project. Go over to the project site on Github and click on the “Clone or download” button and download the project as a .zip file. Of course you could also clone the repo if you prefer that.

Cmark Download

Double-click the .zip file in the Finder to unpack. This will result in a cmark-master folder. Then go back to the Terminal and cd into the cmark-master dir e.g. like so:

$ cd ~/Downloads/cmark-master

The cmark download doesn’t contain a finished build, so it is our task to create that. Therefore we follow along with the installation instructions in the cmark README and perform the following steps:

$ mkdir build  # create the build dir
$ cd build     # move to the build dir
$ cmake ..     # create the Makefile
$ make         # build cmark

The building process will take some time to complete. Please make sure that you have XCode and the XCode Command line tools installed for this step to work. If this step ran smoothly you have just successfully built cmark.

Vendoring the library in Rubymotion

Now we need to get the library into our Rubymotion project. First of all we need to copy over the built cmark library into the project. Let’s get back to the mdmotion project folder and run the following commands:

$ cd mypath/mdmotion  # please exchange mypath with the path to your project dir
$ mkdir vendor        # create a vendor dir for all third-party libraries
$ mkdir vendor/cmark  # add a cmark dir to the vendor dir
$ cp -R ~/Downloads/cmark-master/build/src/* ./vendor/cmark/  # copy the files
$ cp ~/Downloads/cmark-master/src/cmark.h ./vendor/cmark/  # copy the header file
$ ls vendor/cmark     # list the files

Of the steps above it is very important to copy over the cmark.h header file, as we will see later.

The list command should give you something like:

$ ls vendor/cmark
cmark                 cmark.3               cmark_export.h        libcmark.a
cmark-release.cmake   cmark.cmake           cmark_version.h       libcmark.dylib
cmark.1               cmark.h               libcmark.0.28.3.dylib libcmark.pc

Now that we put all the necessary stuff there we need to tell Rubymotion that it must include the libraries at runtime. Therefore we will edit the Rakefile and add the following line:

Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = 'mdmotion'
  app.deployment_target = '10.13'
  app.info_plist['CFBundleIconName'] = 'AppIcon'
  # HERE COMES THE IMPORTANT CMARK STUFF
  app.vendor_project("vendor/cmark", :static)
end

Before we try if the project builds, we will do a rake clean and then build. You could probably also leave away the cleaning, but according to my experience cleaning is a good way to avoid errors when updating vendored libraries or also CocoaPods.

$ bundle exec rake clean:all
…
$ bundle exec rake

If the app builds and you see the empty application window again it might look disappointing, but actually this is a good sign. Now we need to implement the actual application code to convert Markdown to HTML…

Using cmark from the app

The tricky part of using cmark - and any other C library - in Rubymotion is to find out how to call methods and create objects which are provided by the library. If we are looking for classes there is cool way to do that. We start the application and then print all the available classes from the repl command line.

$ bundle exec rake
… # lots of build output here
(main)> Kernel.constants
=> [:NSLeafProxy, :NSProxy, :NSServiceViewControllerUnifyingProxy, :NSVB_TargetedProxy, :NSVB_ViewServiceImplicitAnimationDecodingProxy,… # a quite lengthy list

The list which we just received shows all the constants (including classes) which are defined. As we are looking for cmark we will hit CMD+F to search for cmark and find … Nothing! Nothing? Yes, indeed the cmark library does not add any classes which are globally available. This is bad luck for us, but we still have got another trick up our sleeves.

Remember the vendor/cmark/cmark.h header file which we copied above? We will now look into this file in order to find any methods which the library exposes.

After some search you will probably find the file src/cmark.h. Well I am not a C expert and I never learned the language, but line 28 looks like a pretty straight method definition:

char *cmark_markdown_to_html(const char *text, size_t len, int options);

So this tells us, that:

  • this method returns a char datatype
  • the method name is cmark_markdown_to_html
  • first argument is the markdown text
  • second argument is the length of the text, i.e. number of characters
  • third argument provides additional options which we currently don’t know about

This is enough information for us to play around a little bit. Let’s edit the applications delegate class. First we will add a private method which returns a basic markdown string as a quick and dirty test:

  private

  def mdstring
    "# Hello world\nThis is some **fancy** markdown."
  end
end # end class AppDelegate

Next we try to parse the markdown string in a new method. We are using the cmark method we found out about above:

  def parse_markdown
    html_string = cmark_markdown_to_html(mdstring, mdstring.length, 0)
    puts "The html is: #{html_string}"
  end

So what we do is, call the cmark_markdown_to_html method with the string from the mdstring method, the length of the string and 0 for the integer option value, which we don’t know right now. Then we output the result via puts. We must also make sure to call this method by adding a method-call to the applicationDidFinishLaunching method of the AppDelegate class:

  def applicationDidFinishLaunching(notification)
    buildMenu
    buildWindow
    parse_markdown # call the parse function
  end

Now when we build and run the app the following should happen:

$ bundle execute rake
…
output: '<h1>hello world</h1>'

Hooray we just rendered html from a markdown string! Now you can go ahead and build something useful.

Wrap-Up

So let’s have a look once again on what we just did. First we created our Rubymotion project. Then we downloaded and built the cmark library. Next we had to add the library to our Rubymotion project. It was very important to also add the cmark.h header file so that the messages are also discovered by Rubymotion. Then we edited the Rakefile to include cmark. Afterward we applied a few tricks to find out about how to call the cmark library from Rubymotion code. After we had found out how to do that, we successfully rendered a Markdown string and that’s it!

If you liked the tutorial or have any questions on it contact me via mail or also join the Rubymotion slack channel here: https://motioneers.herokuapp.com. Also check out my apps Docxtor and Gapped. Both were built using Rubymotion.