 Thanks for the mic, Eric, and the introduction. So I'm here to talk about, and Ruby like Eric was saying, but of course, if you've seen me talk before, I'm a big Friday hug evangelist. And I know it's Saturday, but we just had a long break, and I thought it'd be great if we could capture a picture. I know we got one yesterday from Rails Girls and J RubyConf, but your camp is its own and separate conference, so I'd love to have a picture of all of you standing up and hugging the camera. So if we could all get up, and I'd love to get one. Oh, sorry, I messed up, hold on. Ha ha ha, my bad. Ready? One, two, three. Yay, happy Friday, Saturday thing. Oh, well, whatever. Thanks, guys. Like Eric was saying, I am a big Corona of Ruby karaoke. I know we're headed back to Berlin tomorrow for the party after your camp, so if you find PJ and I at that party, I think we're gonna try to lead a group to go do some karaoke tomorrow. So, look forward to that. Like I was saying, I'm Terrence Lee. I go by Hone Zero Two on Twitter. I live in Austin, Texas. We have amazing tacos, so if you're ever in town or passing through, definitely reach out to me. I'd be happy to take you out for some awesome tacos. We have Keeper Rewear this year on October 23rd. Davey has helped me organize it as well this year, along with the original crew from last year. So, looking forward to hopefully seeing some of you there this year in lovely Austin, Texas. And I work at Heroku, so if you ever pushed a Ruby app to the platform, that's code that I've written. And if that has messed up for you, then that is my fault. So you should come and talk to me about that. So, let's get on with the rest of the talk. So, packaging in Ruby, like Eric was saying, I think we've had a strong culture in building CLIs in Ruby, like there's been Thor and Commander and a bunch of other stuff, as well as just a strong RubyGems ecosystem for building command line applications there. And I think if we go through kind of the Heroku command line tool, like the story of when it started to where it is today, you can kind of see the problems that I've run into, as well as I think other people's using Ruby to do this. So, to start it off way back in the day, when we first released this, it was actually just a RubyGem, so one of the benefits of doing this was that we didn't have to build our own packaging system, we can just leverage Ruby. Most of our customers at the time, we started off in Ruby with Heroku, so you can only run Ruby apps on the platform. And it just seemed to make a lot of sense. It circumvented a lot of problems we had, problems that we need to solve, but didn't have to by using RubyGems. So people have updated it using RubyGems and whatnot and it worked out pretty okay. But then we realized that it actually required everyone to have Ruby on their laptops. And the other thing was that we couldn't actually guarantee or what version of Ruby people were running, so older versions of OSX for a long time had like, or even modern ones at that period of time was running like I think Ruby 187. And so that meant as Ruby moved forward, like our gem still had to support 187 because people, a lot of our users were Mac users and they were still running system Ruby to execute this gem and if we ever updated it, it would break and then they wouldn't be able to use Heroku, which isn't good for business. So then we kind of moved, if you go to the toolbelt.heroku.com you'll see a page like this and we basically moved over to packaging the Ruby runtime into the package and making basically offering specific packages for people install. So if you came to the page with Windows you would get a Windows specific installer. If you came with Mac, you did Mac 1, et cetera. But then we realized like packaging this up was kind of a pain in the butt. Every time we wanted to upgrade Ruby because of some security vulnerability or anything or even just do a release it required like a special setup. We actually had like a Mac mini in the office for a while at Heroku that would just, its only job was to package the toolbelt for Mac and then we had a similar setup with VMs and stuff for Windows and it was kind of just a pain in the butt to do each release. And I think doing, upping Ruby gems is like really easy. So we actually did more releases when it was just Heroku, the gem, than when we first switched over. And a few years ago, a former employee Blake Gentry who was working at Heroku at the time started working on this project called HK and it was a go implementation of the Heroku clients. And one of the main motivators was that when you ran Heroku version, and all this would do as you would think is actually just return the version string of the constant in memory of what it was. And it took almost two seconds according to this benchmark if you can't read it. And the go implementation that he did took about 16 milliseconds. So you're talking about two orders of magnitude faster. And this isn't doing any network calls or anything but this is kind of just like the baseline boot up time performance we're looking at. And a huge part of that was because Require in Ruby, if you aren't familiar, is actually pretty slow. And so the CLI for Heroku has gotten pretty complicated. We support a lot of features. And in order to even have any sustainability of development you've had to refactor the code and split stuff into separate files. And it's grown over time. And you kind of want to keep that clean refactoring just to be able to develop it. But that didn't work in our favor for performance because that meant it was harder to actually develop. It was harder to actually boot up the app because you had to do a bunch of stuff. Wes, who was the owner of the CLI at the time, has actually done a ton of work to make it faster. But hopefully that's not something you have to do or worry about when you're building CLIs. You shouldn't have to work against the system there to get clean code. And the other thing that it provided that was really cool if you aren't familiar with Go is that you can compile statically compiled binaries. And you can also cross-compile on a single machine. So that meant that when you're building HK we could build like an OSX version from Linux and a Windows version without having to have special machines or setups to do that. You could all do it on your own host machine. And that was a really powerful thing to be able to just distribute it. So that was really cool. But there are some options if you want to stay with Ruby, like traveling Ruby. It handles kind of the packaging of the runtime and native gems and things that you're dealing with and packages is all that up into a single tar ball. But you do have the caveats of just you're forced to deal with the runtimes that they provide as well as the native extensions. So those are pretty big sticking points there. And in short, like I feel like MRI, Ruby doesn't have a great packaging story to be able to distribute CLIs even though it's actually really easy and quick to build CLIs in Ruby, which is a little unfortunate. If you're familiar with the Vagant project they actually went through pretty much the same history that the Heroku Tool Belt did. So back in the day, if you actually go on Ruby gems you can find the Vagrant gem because it used to actually be distributed as a gem and then they started packaging it and now if you go to the GitHub page it's actually just a Go project. And like Eric was mentioning, we met up in Portugal and I talked to him about this and he was talking about exploring crystal and building CLIs in crystal. But in a way like all those things kind of made me a little sad because I think crystal and Go are both great languages and they bring a lot to the table but there's something about this community like the Ruby community and trying to build things in Ruby that makes me love going to work every day and working on this stuff. So I want to build CLIs in Ruby and not in crystal or Go and see if that was possible. So basically I wanted to kind of solve this packaging problem and over the last two months for this talk I've been working on this project with Zachary Scott who has been helping me with this stuff. It's called MRuby CLI for basically trying to solve this problem. So here's the link to the GitHub page. If you go there, there's like a read me with information and you can download it. So we had basically four goals for designing this application or for this project. But first of all, you should be writing mostly in Ruby. Like I talked a lot about building stuff in Ruby and like that means I want to continue to write stuff in Ruby. I don't want to be writing C in other things. So inside of MRuby CLI there's an MRuby Lib project and you can basically stick as many files as you want in there and it will all get compiled into the binary. So this is the MRuby Lib folder of the release MRuby CLI that we have and as you can tell it has a handful of Ruby files and that's basically the core code of what CLI is. And the second thing is performance is a feature and like Eric was saying it's actually boot time performance that we care a lot about for CLIs because you're running it and they're usually quick lived and you care about getting that information fast or making that network call and returning as fast as possible. So to kind of get a baseline performance of what you can expect in MRI on Ruby 2.2 on my laptop if we just have the simple hello world app and I timed it, it takes about 40 milliseconds to run it. That's not insanely slow but obviously over time like as you add requires and other things like it will get much slower. And using MRuby CLI here's the hello world example on MRuby. It takes three milliseconds to boot that. So that's like an order, magnitude, performance, difference and that's definitely faster. So that's within the ballpark of like what you can reasonably expect which seems pretty good enough for me. The other thing that's really great about MRuby is that there's no require so you're not going to pay that penalty of having a bunch of files like that. They all get compiled down into the binary and they get loaded so you're not going to see this huge degradation by adding a bunch of files which is great. The other, the third thing that I wanted was I wanted a single binary forever major platform. Like go, I want to be able to have a single host that can compile all this stuff and not have to have special setup. And that was, that's really important to me. So when you run the compile task in MRuby CLI it builds a binary for each of the hosts that you have set up. So we have the host here which is 64 bit Linux and in the red you see it builds the binary hello. So that's a single file that you can pass around on any path and run it and it will execute the binary. And we have the same thing for OSX building from the same machine as well as Windows. So a single binary that you can distribute was really important for us. And then to give a sense of how big this file is if you're packaging a whole Ruby in there the OSX binary only comes up to 421K for Hello World. So that's like, I think a pretty reasonable size for a binary that does not have really any dependencies. So I was pretty impressed by that. The final thing was a simple setup. Like we don't want you to have to set up all these compilers and things to get up and running. It's pretty important for people to actually adopt this project since it's pretty new is to be able to easily get up and running and actually writing it and not spend like all this jack shaving time to try to get all this stuff set up. Like you wanna write Ruby that's why you've come here. So we're leveraging Docker. The reason for this is I only want to solve basically the one path of going from Linux to compiling Linux binaries, to going from Linux compiling OSX and Linux to Windows and not having to set up cross-compiling tool chains for that three by three matrix of all the operating systems. So we have a Docker container that has all the tools for the cross-compiling that you need. So all you need is Docker plus a text editor to actually build MRBCLI applications, which I think is pretty cool. So to walk through a simple Hello World of how you would use this and set it up, you download the MRBCLI binary, you put in your path, you run MRBCLI and then it takes the setup flag and you just pass in the application name. In this case, it's Hello and it'll generate a Hello folder with a bunch of files generated in it. And once we go inside, we can just run this compile task which is a Docker compose task and then it will build all the binaries for you, build 32 and 64 bit for all the major operating systems and then with Docker shell, we can run a container inside of Linux and then we execute the Linux binary and we see that we actually get Hello World back. So you can actually do this today, like we have shipped a project that you can actually do this on right now. And so I think to actually understand like what is actually going on behind the scenes, we have to look at all these files that are being generated during setup. So Docker file and Docker compose make up the setup stuff I was talking about before so I'm not gonna cover those in more detail. If you don't quite understand the Docker stuff, I'm happy to answer them outside of the talk or I'm sure you can talk to a lot of people here who've done Docker stuff. Well, let's take a step back and talk a little about MRuby. So I've kind of rushed past Lovin just covered a bunch of stuff with the MRuby CLI project itself. So MRuby for those who aren't aware is a lightweight implementation of the Ruby language that's targeted for being embedded in other languages. So if you're familiar with Lua, that's probably the closest I would say competitor in that space or something the most similar to it. So you can embed MRuby and C in many other languages. But just because it's relatively new, it doesn't mean it's a toy project. It was sponsored by the Japanese government and this is the GitHub project to Siemens R&D Lab in Beijing and they actually have the open WRT router with MRuby bits running and they put this on firmwares and stuff of routers. So that's pretty cool that it's actually being used seriously. So it's not just a toy project. Here are some pictures of some contributors. I'm sure if you've heard of this Matt's guy, he was also the creator and designer of this language. I think Matt's is a great language designer. So I was pretty excited to dive into this MRuby stuff. But what are the differences between MRI? What am I giving up or what am I doing to actually use this other basically subset or something lighter version of Ruby? So one of the things that since it's meant to be embedded and targeted for other architectures and platforms, there's no operating system specific libraries built into the core. So that means you don't have file, you don't have socket or any IO. That doesn't mean you can't get access to stuff like that. It's just not built into the core part of MRuby. That means it's not thread safe because the parent language is supposed to handle those kind of bits. There's no thread, no fork. But what you do get is you get a subset of Ruby 2.1 syntax, which means that you get a lot of the things that you've come to love and are familiar with. So that means you have prox, blocks, freedom patching, metaprogramming, and then hash and array literals, things like that. So a lot of things that make Ruby feel like Ruby. And so once you've actually written an MRuby script, like how do you actually execute any of this code? So like Ruby, there's interpreter binary that is built when you compile it. And you can pass any Ruby script to it and run it. So you get that same write and run flow. In addition to that, one of the things that made Ruby feel great to me when I first came to it was that REPL. So there's also an IRB REPL that you can go and edit and do things and get that really fast feedback loop. But I think where it starts to get really interesting is when you start talking about the MRuby bytecode compiler. And this allows you, I think Rubinius had something similar, but it basically allows you to pass into Ruby script and then when you run it through, you get an MRuby file, which is the bytecode representation of the script that you've put in. And that means you don't have to do this during runtime. So when you run it through the MRuby interpreter, if you pass the dash b flag, you can execute that bytecode. And if you take this a step further, you can now embed this stuff in other languages. So as part of the bytecode compiler, you can actually make a C representation of it. It doesn't actually matter what this C code is, but you get some C code that you can then call in a wrapper C program that will allow you to compile it to a single binary. So inside of MRuby CLI, we actually have a C wrapper that we generate for you that you don't have to touch at all, that gets generated for your project, so you can just not worry about C and just write Ruby. So one of the only caveats about this is that we need a way to hook into your Ruby code. So we basically call this underscore underscore main function underscore underscore and we pass in argv, which are any of the arguments that are passed into the C code. So you get access to all that stuff and then you can basically do whatever you want in here. And then you can lay as many files as you want MRuby lib. The only restriction is it doesn't support directories currently, so there's that, but you can basically just write Ruby and worry about the Ruby stuff. And the entire build system for MRuby is in Ruby. So it uses rake, all the compile tasks and everything and if you just pop on the MRuby project, you can see how MRuby actually compiles and does all this stuff, which is really cool. One of the things, since this is a compiled language, there's a build config file that allows you to configure the different builds you have. So you can specify the different tools that you'd want to use. It has a bunch of pre-existing ones. So GCC is a common compiler that you would use and this build basically sets up the host build. In our case, it's the Linux docker container that we're using, so that's what we're using as the host. And for enable bin test gem config, I'll come back to that later. And as well as we're allowed to set up basic cross build. So for OSX, we can set up like stuff for clang, there's a clang tool chain and then you can set up all the commands that go to that. And then you basically, when you compile it, you get the binaries for that. Inside of MRuby gem rake, MRuby gem is basically the equivalent of Ruby gems in MRI and so you have all the metadata. The only thing really important in the top metadata is probably the bins and this basically determines the binaries that are gonna be compiled. So this should match the C wrapper but we auto-generate that when you run the MRuby CLI command. And where it starts to get interesting is basically this is how you do dependency management and MRuby right now. So it takes two arguments, it takes the name of what you're doing and then basically where you get it. So for core, it actually just means the core libraries and what that is is inside of MRuby, there's an MRuby gem's directory and there's a set of core libraries that are part of it. And as I was saying before, there's no IO built in but you need MRuby print to get the puts method and that does stuff in just standard IO and that's part of the core library. So by default, none of that stuff is compiled in so you can keep the binary as small as possible and only require the things that you actually need to run your CLI. The next thing is Mgem. There's an Mgem listing on GitHub basically it's just a list of known third party MRuby gems that you can pull from similar to like Ruby gems, you can just gem install Rails and it knows where to find it. And this is the same thing, this is just locations of other Mgem's out there or MRuby gems. And the third thing is if you ever want to build your own or fork you can actually just pull straight from GitHub so you can put up a project, fork a project and just link to the GitHub thing and they have a shorthand for that. So that's how you do dependencies. So it's pretty simple to be able to add stuff. And then so I'm coming back to this gem config thing and so as part of it you need to actually include your own gem that you're building for the CLI so it actually compiles the MRuby lib stuff. But the second line I have here is if there's ever a conflict like say you depend on in this case MRuby Ml but you want to use your fork or there's two different sources it's pulling from build config allows you to actually overriding that stuff so you can take so you can say I actually want this version and that way it always uses that and it's always using the right one that you actually want for a compile. So that's nice because that's something in Bundler we get a request a lot it's like how can I override the Bundler resolver to use my specific fork of something and not have to like go rewrite the parent gem that depends on it. And of course since we're RubyS like testing is an important thing. So we have built in unit testing there's an MRuby gem for it and it allows you to do kind of mini test style tests and what's neat is that all this stuff gets compiled down into a separate binary so it's not included in the actual CLI that you ship to your users but it's a separate binary that gets run and all that code's written in MRuby and then there's been tests and as you can see there's required here so that means this code is actually written MRI and so these tests get basically run around the binary that you've built so you can run tests against it to make sure it's actually doing the things that you expect it to. So there's an example of that. So some gotchas. So one of the things that kind of sucks about this is you can't leverage the huge already existing CLI ecosystem or other gems that are in RubyGems and it's not always the easiest thing to port an MRuby gem or a Ruby gem over to MRuby because a lot of RubyGems assume that you have all these standard library libs that may or may not exist in MRuby and or some of the MRuby libraries are trimmed down versions to be smaller or they didn't want to implement all the functionality so that's kind of a downside and the other thing is if you're using any C extensions you have to make sure they cross compile cleanly on all three operating systems. I've done a ton of work on like three or four MRuby gems now to make that work but there's a ton of like Ruby only MRuby gems that just work fine out of the box. So as a roadmap of where we're going with this project I'd love to add a release task that basically goes and builds tar balls of each specific architecture that you're building for and so you don't have to rummage around in the build folder to find them. They all just come in a nice thing and you can just ship them immediately from there. Maybe we'll support Debs or RPM, like Linux specific things, maybe have a DMG package or even like a Windows installer. I think having all that baked in would make it super easy to be able to ship all this stuff. The next thing is upgrading. Like as we had features it'd be nice to have a command to be able to upgrade and have features in place and not have to basically monkey patch or pull in stuff on your own as we add features to MRuby CLI. Being able to compile for a single architecture is something we'd love to do that's coming in the pipeline. As we build out a set of MRuby gems that makes sense to build MRuby CLIs it'll be nice to just have a curated set of them to bring in so you can just get up and running as soon as possible. Similar to like a Rails gem file that has default gems in there. We'd want to upstream as many of the stuff we can into the MRuby project itself so other people can take advantage of the work we've done and then as well as like, I think this might be a separate MRuby gem but basically some type of documentation like Yard or RDoC or something or man pages and have that built in to part of the project so you can easily document the CLI itself. And so for the last session, like what can you do with MRuby CLI and how do you proceed from here today? So the community is really young. Zach and I shipped the first release of MRuby CLI this last Monday and that's why I started working on slides for this talk. So it's a pretty young ecosystem and I would love help in building out the MRuby gems and things that people would want to use to build CLIs. There's a handful of stuff out there but for option parsing I'm using like get ops which is not my favorite option parser out there and I would love help in building something better there as well as other things. But like I said, we've actually shipped a release and if you go to the GitHub page which is on the next slide, you can actually go to the releases page and there's a binary for your operating system. You can download that and run it. You can, if you don't have Docker, Docker Compose, there's links in the readme for going to get those tools that you need to get it up and running. You can run MRuby CLI, generate a hello world, compile it and then keep iterating on it and it's a pretty fast loop cycle there and if anyone actually builds anything that's really cool or it doesn't even have to be amazing, like if you're just using it and building something, I'd love to hear about it. So yeah, thank you. Oh, I also have these blue hat stickers if anyone would like one. So just come say hi and I'm happy to hand them out. Thank you for the talk. Have you run it against several benchmarks to have an idea how it compares with other languages? I mean, besides the, I didn't really benchmark beyond the boot up time because to me that was the most important thing that was really, I think, hampering Ruby as a CLI. I think the performance in general is pretty similar to MRI itself. So if you're okay with that performance, that's around, like once it's booted up, that's around the performance you'll see except there's some special stuff they've done with floating point numbers. So if you're doing floating point math, I believe it's faster in Ruby. Gone. Okay. Thanks Terence. Another round of applause, everybody.