Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building CLI Apps for Everyone - RubyConf 2015

hone
November 17, 2015

Building CLI Apps for Everyone - RubyConf 2015

Video: https://www.youtube.com/watch?v=zv_bCpOCXxo

Many projects rely on command-line tools to provide an efficient and powerful interface to work.

Building tools for everyone can be difficult, because of conflicting environment or OS.

How can we build command-line apps that work for everyone and still write Ruby?

This talk will discuss how to use mruby-cli to build cross-platform apps in Ruby.

Our goal will be to build a CLI app using mruby and produce a self-contained binary that can be shipped to end users.

Since mruby is designed to be embedded and statically compiled, it's also really good at packaging ruby code.

hone

November 17, 2015
Tweet

More Decks by hone

Other Decks in Technology

Transcript

  1. hk

  2. Traveling Ruby • no need to rewrite your app •

    packages your app as a directory • runtime packaging is taken care of • can leverage rubygems ecosystem • native gems limited to precompiled exts
  3. mrblib ├── [4.0K] mrblib │ ├── [4.0K] mruby-cli │ │

    ├── [ 697] cli.rb │ │ ├── [ 424] help.rb │ │ ├── [ 403] option.rb │ │ ├── [1022] options.rb │ │ ├── [8.0K] setup.rb │ │ ├── [ 238] util.rb │ │ └── [ 206] version.rb │ └── [ 53] mruby-cli.rb
  4. MRI # hello_world.rb puts 'Hello World' $ time ruby hello.rb

    Hello World real 0m0.041s user 0m0.028s sys 0m0.008s
  5. mruby-cli # mrblib/hello.rb def __main__(argv) puts "Hello World" end $

    time mruby/build/host/bin/hello Hello World real 0m0.003s user 0m0.000s sys 0m0.000s
  6. $ docker-compose run compile Build summary: ================================================ Config Name: x86_64-pc-linux-gnu

    Output Directory: build/host Included Gems: mruby-print - standard print/puts/p mruby-sprintf - standard Kernel#sprintf method mruby-time - standard Time class mruby-io mruby-mtest hello - hello - Binaries: hello ================================================
  7. ================================================ Config Name: x86_64-apple-darwin14 Output Directory: build/x86_64-apple-darwin14 Included Gems: mruby-print

    - standard print/puts/p mruby-sprintf - standard Kernel#sprintf method mruby-time - standard Time class mruby-io mruby-mtest hello - hello - Binaries: hello ================================================
  8. ================================================ Config Name: x86_64-w64-mingw32 Output Directory: build/x86_64-w64-mingw32 Included Gems: mruby-print

    - standard print/puts/p mruby-sprintf - standard Kernel#sprintf method mruby-time - standard Time class mruby-io mruby-mtest hello - hello - Binaries: hello ================================================
  9. Hello World Example $ mruby-cli --setup hello $ cd hello

    $ docker-compose run compile $ docker-compose run shell # mruby/build/host/bin/hello
  10. Hello World Example $ mruby-cli --setup hello $ cd hello

    $ docker-compose run compile $ docker-compose run shell # mruby/build/host/bin/hello Hello World
  11. Hello World Example $ mruby/bin/mruby-cli --setup hello create .gitignore create

    mrbgem.rake create build_config.rb create Rakefile create Dockerfile create docker-compose.yml create tools/ create tools/hello/ create tools/hello/hello.c create mrblib/ create mrblib/hello.rb create mrblib/hello/ create mrblib/hello/version.rb create bintest/ create bintest/hello.rb create test/ create test/test_hello.rb
  12. mruby is the lightweight implementation of the Ruby language complying

    with part of the ISO standard. mruby can be linked and embedded within your application.
  13. Interpret (*.rb) $ mruby/bin/mruby -e "puts 'Hello World'" Hello World

    $ mruby/bin/mruby hello_world.rb Hello World
  14. Binary C Code (*.c) $ mruby/bin/mrbc -o hello_world.c - Bhello_world

    hello_world.rb $ cat hello_world.c /* dumped in little endian order. use `mrbc -E` option for big endian CPU. */ #include <stdint.h> const uint8_t #if defined __GNUC__ __attribute__((aligned(4))) #elif defined _MSC_VER __declspec(align(4)) #endif hello_world[] = { 0x45,0x54,0x49,0x52,0x30,0x30,0x30,0x33,0xcb,0xa4,0x00, 0x00,0x00,0x65,0x4d,0x41,...}; This is an FYI No need to read this C code
  15. int main(void) { mrb_state *mrb = mrb_open(); mrbc_context *c; mrb_value

    v; c = mrbc_context_new(mrb); mrb_load_irep_cxt(mrb, hello_world, c); mrbc_context_free(mrb, c); return 0; }
  16. int main(void) { mrb_state *mrb = mrb_open(); mrbc_context *c; mrb_value

    v; c = mrbc_context_new(mrb); mrb_load_irep_cxt(mrb, hello_world, c); mrbc_context_free(mrb, c); return 0; }
  17. tools/hello/hello.c #include <stdlib.h> #include <stdio.h> /* Include the mruby header

    */ #include <mruby.h> #include <mruby/array.h> int main(int argc, char *argv[]) { mrb_state *mrb = mrb_open(); mrb_value ARGV = mrb_ary_new_capa(mrb, argc); int i; int return_value; for (i = 0; i < argc; i++) { mrb_ary_push(mrb, ARGV, mrb_str_new_cstr(mrb, argv[i])); } mrb_define_global_const(mrb, "ARGV", ARGV); // call __main__(ARGV) mrb_funcall(mrb, mrb_top_self(mrb), "__main__", 1, ARGV); return_value = EXIT_SUCCESS; if (mrb->exc) { mrb_print_error(mrb); return_value = EXIT_FAILURE; } mrb_close(mrb); return return_value; } This is an FYI No need to read this C code
  18. mrblib ├── [4.0K] mrblib │ ├── [4.0K] mruby-cli │ │

    ├── [ 697] cli.rb │ │ ├── [ 424] help.rb │ │ ├── [ 403] option.rb │ │ ├── [1022] options.rb │ │ ├── [8.0K] setup.rb │ │ ├── [ 238] util.rb │ │ └── [ 206] version.rb │ └── [ 53] mruby-cli.rb
  19. Rakefile $ rake -T rake all # build all targets...

    rake clean # clean all built.. rake compile # compile all the binaries rake release # generate release tarballs rake test # run all tests rake test:bintest # run integration tests rake test:mtest # run mruby & unit tests
  20. build_config.rb MRuby::CrossBuild.new('x86_64-apple-darwin14') do |conf| toolchain :clang [conf.cc, conf.linker].each do |cc|

    cc.command = 'x86_64-apple-darwin14-clang' end conf.cxx.command = 'x86_64-apple-darwin14-clang++' conf.archiver.command = 'x86_64-apple-darwin14-ar' conf.build_target = 'x86_64-pc-linux-gnu' conf.host_target = 'x86_64-apple-darwin14' gem_config(conf) end
  21. $ docker-compose run compile Build summary: ================================================ Config Name: host

    Output Directory: build/host Binaries: mrbc Included Gems: mruby-print - standard print/puts/p mruby-sprintf - standard Kernel#sprintf method mruby-time - standard Time class mruby-io mruby-mtest hello - hello - Binaries: hello mruby-compiler - mruby compiler library mruby-bin-mrbc - mruby compiler executable ================================================
  22. mrbgem.rake MRuby::Gem::Specification.new('hello') do |spec| spec.license = 'MIT' spec.authors = ['Terence

    Lee'] spec.summary = 'hello world' spec.bins = ['hello'] spec.add_dependency 'mruby-print', :core => 'mruby- print' spec.add_dependency 'mruby-mtest', :mgem => 'mruby- mtest' spec.add_dependency 'mruby-yaml', :github => 'hone/mruby-yaml' end
  23. mrbgem.rake MRuby::Gem::Specification.new('hello') do |spec| spec.license = 'MIT' spec.authors = ['Terence

    Lee'] spec.summary = 'hello world' spec.bins = ['hello'] spec.add_dependency 'mruby-print', :core => 'mruby- print' spec.add_dependency 'mruby-mtest', :mgem => 'mruby- mtest' spec.add_dependency 'mruby-yaml', :github => 'hone/mruby-yaml' end
  24. core mrbgems ├── [4.0K] mruby-array-ext ├── [4.0K] mruby-bin-debugger ├── [4.0K]

    mruby-bin-mirb ├── [4.0K] mruby-bin-mrbc ├── [4.0K] mruby-bin-mruby ├── [4.0K] mruby-bin-mruby-config ├── [4.0K] mruby-bin-strip ├── [4.0K] mruby-compiler ├── [4.0K] mruby-enumerator
  25. mrbgem.rake MRuby::Gem::Specification.new('hello') do |spec| spec.license = 'MIT' spec.authors = ['Terence

    Lee'] spec.summary = 'hello world' spec.bins = ['hello'] spec.add_dependency 'mruby-print', :core => 'mruby- print' spec.add_dependency 'mruby-mtest', :mgem => 'mruby- mtest' spec.add_dependency 'mruby-yaml', :github => 'hone/mruby-yaml' end
  26. mrbgem.rake MRuby::Gem::Specification.new('hello') do |spec| spec.license = 'MIT' spec.authors = ['Terence

    Lee'] spec.summary = 'hello world' spec.bins = ['hello'] spec.add_dependency 'mruby-print', :core => 'mruby- print' spec.add_dependency 'mruby-mtest', :mgem => 'mruby- mtest' spec.add_dependency 'mruby-yaml', :github => 'hone/mruby-yaml' end
  27. build_config.rb def gem_config(conf) # be sure to include this gem

    conf.gem File.expand_path(File. dirname(__FILE__)) conf.gem :github => 'hone/mruby-yaml' end
  28. mtest module MRubyCLI class TestUtil < MTest::Unit::TestCase def test_camelize assert_equal

    "GoodNightMoon", Util.camelize ("good_night_moon") assert_equal "FooBarBaz", Util.camelize("foo- bar-baz") end end end MTest::Unit.new.run
  29. mtest (cont.) $ docker-compose run mtest >>> Test host <<<

    mrbtest - Embeddable Ruby Test .....S... # Running tests: .. Finished tests in 0.000305s, 6557.3770 tests/s, 26229.5082 assertions/s. 2 tests, 8 assertions, 0 failures, 0 errors, 0 skips
  30. bintest require 'open3' require 'tmpdir' BIN_PATH = File.join(File.dirname(__FILE__), ".. /mruby/bin/hello")

    assert('setup') do output, status = Open3.capture2("#{BIN_PATH}") assert_true status.success?, "Process did not exit cleanly" assert_include output, "Hello World" end
  31. jruby.cpp /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES

    OR THIS HEADER. * * Copyright 1997-2008, 2010 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2
  32. Building a CLI App • download the mruby-cli binary •

    install Docker Toolbox • mruby-cli --setup calculator • add new functionality ◦ modify to build a calculator or just start with addition • docker-compose run compile • modify, recompile • tweet at me (@hone02)