Wednesday, December 14, 2011

Node.js modules cross platform compilation using gyp

Update: I have made a pull request where you can find the updated tools discussed in this article, located here
Node.js has been using waf (node-waf) to configure and build modules up to version 0.4. From v0.6 and on, the team has moved on to gyp (Generate Your Projects) which seems to be a bit more promising when it comes to cross platform compilation. This post shows how to create a simply gyp file to build your own custom native node.js modules and provides some scripts to automate the project generation process.

A bit of history

Gyp is a google project that was created to support cross platform building of the opensource chromium project. The main target of this project is to “generate native Visual Studio, Xcode and SCons and/or make build files from a platform-independent input format”. The project is still on its very first steps and there is little documentation on how to use it. Moreover, up to this point, there are thousand of feature requests and the maintaining team seems to implement only those directly related to the chromium project, which makes it a bit hard to upstream any new feature unless you are persistent.
On the bright side, gyp works pretty well once you get a handle of it. If you read the gyp language specification and use the source as your guide (“may the source be with you”) you will be able to write your own gyp files for your personal cross platform projects in no time.

Node-waf vs gyp

Up to version 0.4 the node.js team offered node-waf (a waf 1.5.3 wrapper script) to configure and build modules for node.js. This was fine since in windows there was no native support and you had to use Cygwin to make your builds. From version 0.5 and on, there node supports native windows builds which brings visual studio in to play. The problem is that waf started supporting visual studio’s msbuild from version 1.6 and on and this was a major setback. In the meanwhile the node.js team decided to move on to gyp and abandoned the node-waf script. If you are tempted to create a node-waf wrapping waf 1.6, try to resist. A lot of things have changed in waf 1.6 and when I finished modifying the script, I still couldn’t generate proper windows builds (I would have to hardwire the linking arguments in order to link the object to node.lib just for windows). On the other hand, gyp supports custom arguments depending on the building platform which makes the gyp files easier to maintain.

Node Module’s gyp file

I have edited a simple gyp file (see the end of this post for source code) to compile the simple hello world native nodejs module I have used in my previous posts (here and here). This gyp files specifies a single target that uses the gyp variable module_name to set its name (using the <(variable_name) syntax).  The target contains the basic and most common defines that are required for each node module and the include dirs that contain the node.h, v8.h and uv.h header files.
After that, I have added some conditional specification based on the compiling OS. For example, in windows, there is no uint type so I define it in the “defines” section. Moreover, I specify the node.lib file to which the final obj file will link to and I set the output directory using visual studio specific configuration options.
As for mac users, the linker should include the “-undefined dynamic_lookup” option which is set as a library because ldflags didn’t work as expected (at least for the version of gyp bundled with nodejs). Theoretically I should have specified:

'link_settings': { 'ldflags': [ ‘-undefined dynamic_lookup’, ], },

but this didn’t work on my tests. Moreover, I didn’t go with xcode to provide the same building instructions for both linux and mac users.

The node-gyp scripts

In the provided gyp file (see the end of this post for source code)  I am using the NODE_ROOT gyp variable which should point to the root of the nodejs source directory (as discussed in my previous posts). In order to pass that variable in gyp and in order to simplify the project generation process I have assembled two shell scripts (one for windows and one for linux and mac) in order to use the environment variable NODE_ROOT (using -DNODE_ROOT=%NODE_ROOT% for windows and -DNODE_ROOT=$NODE_ROOT for the rest of the OSs).  Especially for windows, the module has to link to node.lib which is generated when you compile node. The “node-gyp.bat” script tries to locate that node.lib and sets the node_lib_folder gyp variable too.

Another feature of these scripts is that they try to locate a module.gyp file in case you haven’t specified an argument. I tried to resemble the use of node-waf that locates the wscript. This way, each module can have a module.gyp file in their root directory and the end users may run node-gyp to generate the Makefile or the Visual studio solution.
A final thing to note is that both scripts use the gyp that is bundled with node (in the tools\gyp folder). This version of gyp has an issue that requires you to specify the “--depth=.” option in order to work. This option is used as a bug fix and will be removed in future version of gyp (at least that’s what gyp says).

Source code and script

You may download and use the source code and the provided scripts from the following link:

Hope this helps!


Hugh said...

Thanks for this!

I'm having a problem running this, however. In node-0.6.7 I'm running this:

var addon=require('./Debug/helloworld');

...which works fine. This fails:


The error says:

Assertion failed!

Program: C:\Program Files (x86)\nodejs\node.exe
File: C:\dev\nodejs\node-v...\node_o..._wrap.h
Line: 62

Expression: handle->InternalFieldCount() > 0

Any ideas? Am I just doing it wrong somehow?

Hugh said...

Sorry, my mistake -- I see I was doing something wrong.

I should have invoked it as:

new addon.HelloWorld().hello();

Thanks again for your effort!

Mark Essel said...

This is handy, thank you Andreas. I will endeavor to upgrade my as time permits.