# rake-compiler turns native extensions into a convention instead of a Rakefile

> rake-compiler is a Ruby gem that removes the custom rake tasks a native extension normally needs, by fixing a directory layout and asking for two lines. The result is that a C extension, a JRuby extension and a packaged gem with platform-specific binaries all build from the same conventions, and the same Rakefile can produce a pure Ruby gem and a set of native ones.

**rake-compiler/rake-compiler** — Provide a standard and simplified way to build and package Ruby C and Java extensions using Rake as glue.

- Repository: https://github.com/rake-compiler/rake-compiler
- Stars: 574 · Forks: 129
- Language: Ruby
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/rake-compiler-rake-compiler

## It is a productivity tool with a convention

rake-compiler describes itself as first and foremost a productivity tool for Ruby developers, and its goal is stated plainly: to make the busy developer's life easier by simplifying the building and packaging of Ruby extensions by simplifying code and reducing duplication.

The design principle is stated next, and it is the one that matters. It follows convention over configuration by advocating a standardized build and package structure for both C and Java based RubyGems.

So this is not a library you call. It is a layout you adopt, and the payoff is that the Rake tasks come for free.

The origin story explains why the convention is shaped the way it is. It is the result of many hard-won experiences dealing with several diverse RubyGems that provided native extensions for different platforms and different user configurations in different ways. Details such as differences in code portability, differences in code clarity, and differences in project directory structure often made it very difficult for newcomers to those RubyGems.

The single-minded goal, in the project's own phrasing, is making the busy RubyGem developer's life much less difficult.

The feature list is seven items and each is a concrete thing you would otherwise write yourself. No custom rake tasks required, with less code duplication and errors. Painless building of extensions on different platforms, meaning Linux, OSX and Windows. Painless building of extensions for different Ruby implementations, naming JRuby, Rubinius and MRI. Allowing multiple extensions to be compiled inside the same gem. Painless building of fat native gems for Windows users from Linux or OSX. Mimicking the RubyGems standard installation process, which helps as a test environment. And simplifying cross platform extension compilation, targeting Windows from Linux.

That last one and the fat gem one are the same capability seen from two sides, and both are the reason a project like this exists: building a Windows binary of a C extension normally means cross compiling with a MinGW toolchain, and that is exactly the kind of thing every gem author ends up writing by hand.

The project is Ruby, MIT licensed, with the last push on 2026-10-08 and releases v1.3.2 from the same day, v1.3.1 and v1.3.0 from 2025.

## The directory layout is the contract

The documentation's example is an extension called hello_world, and the tree it asks for is this:

    |-- ext
    |   `-- hello_world
    |       |-- extconf.rb
    |       |-- HelloWorldService.java
    |       `-- hello_world.c
    |-- lib
    `-- Rakefile

So there is an ext/ directory holding one subdirectory per extension, that subdirectory holds the extconf.rb and the sources, there is a lib/ directory for the Ruby side, and there is a Rakefile at the root.

The tip that follows is about the convention rather than the mechanics. Having a consistent project directory structure will help developers and newcomers find and understand your code, making it easier for them to contribute back to your project.

That is the second reason for the convention, and it is the one that matters for an open source gem. A predictable layout means a newcomer can find the extension sources without reading a README, which is worth more for a gem that wants contributions than the build simplification is to the one person writing it.

The Java file in the tree is not a mistake. The same layout serves a C extension and a JRuby extension written in Java, with the .java file sitting next to the .c file in the same directory, and the two are built by different tasks.

Once the layout is in place, the Rakefile changes are two lines for a C extension:

```ruby
# File: extconf.rb

# these lines must exist already
require 'mkmf'
create_makefile('hello_world')

# File: Rakefile

require 'rake/extensiontask'

Rake::ExtensionTask.new('hello_world')
```

And the documentation's reaction to that is telling. That's it? Yes, that's it. No other lines of code are needed for rake-compiler to work its magic.

The important detail is the constraint on the name. You need to make sure the parameter to create_makefile and ExtensionTask.new are the same, or rake-compiler will not mimic the RubyGems standard install process. And you can override that standard behaviour if needed, with a section on non-standard project structure later in the documentation.

For JRuby, the Java extension task is the same shape:

```ruby
# File: Rakefile

require 'rake/javaextensiontask'

Rake::JavaExtensionTask.new('hello_world')
```

So the difference between a C extension and a Java extension in the Rakefile is which class you instantiate.

## What the two lines actually generate

The documentation shows the tasks that appear once the extension task is declared, and it is a short list:

    $ rake -T
    (in /home/user/my_extension)
    rake compile                # Compile the extension(s)
    rake compile:hello_world    # Compile just the hello_world extension

So you get a compile task that builds all the extensions and a per-extension task that builds just one.

Calling the compile task performs the entire compile and build process and places the resulting extension inside the lib directory of your project. That last detail is the important one for testing, because the extension ends up where the Ruby code expects to require it, so you can run your test suite against the freshly built binary without any packaging step in between.

Passing configuration to the compilation means using the double dash convention:

    $ rake compile -- --with-foo-[dir|lib|bin|...]=/path/to/foo

Everything after the double dash is passed through to the configure step, which is the same convention as running extconf.rb by hand with those flags. That means the options your extension already supports continue to work without rake-compiler having to know about them, which is the right division of labour.

Two environment notes come next and they are the practical gotchas. Building C extensions requires the proper development environment for your platform, including libraries, headers and build tools, and the documentation points at your distro or vendor documentation for how to install them. Building Java extensions requires the javac part of the Java Development Kit, which should be included by default on Mac OS X and downloadable elsewhere.

The RubyGems installation behaviour is also worth spelling out, because it is the part that makes the tool safe to use. It mimics the RubyGems standard installation process, which helps as a test environment. That is, a gem built with rake-compiler installs the same way a gem with an extconf.rb installs, so a user running gem install gets the same result they would get from a conventionally built gem.

## One gem spec, several platform gems

The section on generating native RubyGems is the reason a gem author reaches for this tool, and the mechanism is a small amount of extra code in the Rakefile.

```ruby
# somewhere in your Rakefile, define your gem spec
spec = Gem::Specification.new do |s|
  s.name = "my_gem"
  s.platform = Gem::Platform::RUBY
  s.extensions = FileList["ext/**/extconf.rb"]
end

# add your default gem packing task
Gem::PackageTask.new(spec) do |pkg|
end

# feed the ExtensionTask with your spec
Rake::ExtensionTask.new('hello_world', spec)
```

The three pieces are ordinary RubyGems and Rake idioms. A Gem::Specification with the extensions set to a FileList over ext/**/extconf.rb, a Gem::PackageTask for the standard packaging, and the ExtensionTask fed with that spec so it knows what it is building for.

The output is the interesting part. Building the plain gem the usual way gives one file:

    $ rake gem
    (in /projects/oss/my_gem.git)
    mkdir -p pkg
      Successfully built RubyGem
      Name: my_gem
      Version: 0.1.0
      File: my_gem-0.1.0.gem
    mv my_gem-0.1.0.gem pkg/my_gem-0.1.0.gem

And the native gem task gives you the platform-specific ones:

    # rake native gem
    (... compilation output ...)
    mkdir -p pkg
      Successfully built RubyGem
      Name: my_gem
      Version: 0.1.0
      File: my_gem-0.1.0-x86-mingw32.gem
    mv my_gem-0.1.0-x86-mingw32.gem pkg/my_gem-0.1.0-x86-mingw32.gem

The x86-mingw32 in the filename is the platform triple, and that is the fat gem: one gem containing the Windows binary alongside the Ruby code.

And the JRuby version is the same shape with a java suffix, my_gem-0.1.0-java.gem.

The documentation sums that up with a line that is funny and accurate. Did you notice that you get two gems for the price of one? How's that for a time saver?

That is the whole value proposition in one sentence. One specification, three artefacts, and the platform binaries are built by the same task that builds the extension.

The gem platform is set to Gem::Platform::RUBY in the example, which is what you want for the portable gem that carries the platform binaries rather than a gem pinned to one platform.

## Non-standard layouts are allowed, and the repository shows how it is tested

The last section of the documentation available here answers the question every experienced gem author asks, which is what happens if my layout does not match your convention.

The answer is that you can. While the conventional project structure is recommended, you may want, or need, to tweak those conventions, and rake-compiler allows you to customize several settings for Rake::ExtensionTask:

```ruby
Rake::ExtensionTask.new do |ext|
  ext.name = 'hello_world'                # indicate the name of the extension.
  ext.ext_dir 
```

The block form is the configuration surface. Instead of passing a name to the constructor, you get the extension task object and set its attributes, and the first two shown are the name and the extension directory, which is the one that matters for a non-standard layout.

The repository layout is the other thing worth noting. There is a lib/ directory for the library, a tasks/ directory for the rake tasks it provides, a spec/ directory, and a features/ directory with cucumber.yml, which means the tool is tested through both RSpec and Cucumber, the latter being the tool for describing behaviour in plain language.

There is a .rspec file, a Rakefile, a Gemfile, a rake-compiler.gemspec and an appveyor.yml for Windows CI, plus a bin/ directory and a History.md for the changelog.

The bin/ directory is the one to look at. A gem that installs a command line tool needs an executable in the gem's bin path, and this one has a bin/ directory for it.

And the docs/ directory at the root of the project, along with the Doxyfile, is for generating API documentation for the library itself.

So the project that helps other people follow a convention is itself built with a conventional layout, has a changelog, has Windows CI, and has both unit and behaviour tests. That is not a criticism of the tool, it is the point: the tool is for people who want less to think about, and the people who made it think about it.

## Conclusion

Adopt rake-compiler if you ship a Ruby gem with a native extension and your Rakefile has grown into a wall of custom tasks, since the convention replaces most of it. Skip it if you have one extension and no packaging complexity, since the default extconf.rb and rake-compiler might be enough. Verify first that your extension name matches between create_makefile and ExtensionTask.new, because a mismatch breaks the standard install behaviour, that you have the platform development headers and build tools for a C extension, and that you are on a Ruby and gem version the current release supports.

## FAQ

### What does rake-compiler actually do for a Ruby gem with a native extension?

It provides a standardized build and package structure for C and Java based RubyGems so you need no custom rake tasks. You put the extension under ext/<name>/ with its extconf.rb and sources, keep a lib/ directory and a Rakefile, and then two lines in the Rakefile: require 'rake/extensiontask' and Rake::ExtensionTask.new('hello_world'), which generates the compile tasks and puts the built extension in the project's lib directory.

### How do I build a JRuby extension with rake-compiler?

The same layout, with the Java sources in the same directory as the C sources, and a different task class. Require 'rake/javaextensiontask' and use Rake::JavaExtensionTask.new('hello_world'). Building Java extensions needs the javac part of the JDK, and building C extensions needs the proper development environment for your platform including libraries, headers and build tools.

### What is the catch with the extension name in rake-compiler?

The parameter to create_makefile and the name passed to ExtensionTask.new have to be the same, or rake-compiler will not mimic the RubyGems standard install process. That behaviour is the point of the tool, since it lets a conventionally built gem install the same way whether you used rake-compiler or wrote the tasks by hand, and you can override it if you need a non-standard project structure by setting the attributes on the extension task object.

## Sources

- [Issues](https://github.com/rake-compiler/rake-compiler/issues)
- [License: MIT](https://github.com/rake-compiler/rake-compiler/blob/master/LICENSE)
- [rake-compiler/rake-compiler on GitHub](https://github.com/rake-compiler/rake-compiler)
- [README](https://github.com/rake-compiler/rake-compiler/blob/master/README.md)
- [Releases](https://github.com/rake-compiler/rake-compiler/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rake-compiler-rake-compiler
