Browse Source

Merge branch 'master' into net2

v0.7.4-release
Ryan Dahl 15 years ago
parent
commit
96f42745ff
  1. 36
      ChangeLog
  2. 184
      deps/coupling/coupling.c
  3. 28
      doc/api.txt
  4. 4
      doc/index.html
  5. 2
      lib/multipart.js
  6. 7
      src/node.cc
  7. 81
      src/node.js
  8. 0
      test/mjsunit/disabled/test-dns.js
  9. 0
      test/mjsunit/disabled/test-fs-sendfile.js
  10. 12
      test/mjsunit/fixtures/echo.js
  11. 1
      test/mjsunit/test-module-loading.js
  12. 15
      test/mjsunit/test-readdir.js
  13. 35
      test/mjsunit/test-stdio.js
  14. 1
      tools/test.py
  15. 2
      wscript

36
ChangeLog

@ -1,4 +1,38 @@
2010.01.20, Version 0.1.26 2010.02.03, Version 0.1.27
* Implemented __dirname (Felix Geisendörfer)
* Downcase process.ARGV, process.ENV, GLOBAL
(now process.argv, process.env, global)
* Bug Fix: Late promise promise callbacks firing
(Felix Geisendörfer, Jonas Pfenniger)
* Make assert.AssertionError instance of Error
* Removed inline require call for querystring
(self@cloudhead.net)
* Add support for MX, TXT, and SRV records in DNS module.
(Blaine Cook)
* Bugfix: HTTP client automatically reconnecting
* Adding OS X .dmg build scripts. (Standa Opichal)
* Bugfix: ObjectWrap memory leak
* Bugfix: Multipart handle Content-Type headers with charset
(Felix Geisendörfer)
* Upgrade http-parser to fix header overflow attack.
* Upgrade V8 to 2.1.0
* Various other bug fixes, performance improvements.
2010.01.20, Version 0.1.26, da00413196e432247346d9e587f8c78ce5ceb087
* Bugfix, HTTP eof causing crash (Ben Williamson) * Bugfix, HTTP eof causing crash (Ben Williamson)

184
deps/coupling/coupling.c

@ -143,98 +143,176 @@ ring_buffer_push (ring_buffer *ring, int fd)
return r; return r;
} }
/* PULL PUMP
*
* This is used to read data from a blocking file descriptor and pump it into
* a non-blocking pipe (or other non-blocking fd). The algorithm is this:
*
* while (true) {
* read(STDIN_FILENO) // blocking
*
* while (!ring.empty) {
* write(pipe) // non-blocking
* select(pipe, writable)
* }
* }
*
*/
static void static void
pump (int is_pull, int pullfd, int pushfd) pull_pump (int pullfd, int pushfd)
{ {
int r; int r;
ring_buffer ring; ring_buffer ring;
fd_set readfds, writefds, exceptfds;
ring_buffer_init(&ring); fd_set writefds, exceptfds;
int maxfd;
while (pushfd >= 0 && (pullfd >= 0 || !ring_buffer_empty_p(&ring))) {
FD_ZERO(&exceptfds); FD_ZERO(&exceptfds);
FD_ZERO(&readfds);
FD_ZERO(&writefds); FD_ZERO(&writefds);
maxfd = -1;
if (is_pull) {
if (!ring_buffer_empty_p(&ring)) {
maxfd = pushfd;
FD_SET(pushfd, &exceptfds); FD_SET(pushfd, &exceptfds);
FD_SET(pushfd, &writefds); FD_SET(pushfd, &writefds);
ring_buffer_init(&ring);
while (pullfd >= 0) {
/* Blocking read from STDIN_FILENO */
r = ring_buffer_pull(&ring, pullfd);
if (r == 0) {
/* eof */
close(pullfd);
pullfd = -1;
} else if (r < 0 && errno != EINTR && errno != EAGAIN) {
/* error */
perror("pull_pump read()");
close(pullfd);
pullfd = -1;
} }
/* Push all of the data in the ring buffer out. */
while (!ring_buffer_empty_p(&ring)) {
/* non-blocking write() to the pipe */
r = ring_buffer_push(&ring, pushfd);
if (r < 0 && errno != EAGAIN && errno != EINTR) {
if (errno == EPIPE) {
/* This happens if someone closes the other end of the pipe. This
* is a normal forced close of STDIN. Hopefully there wasn't data
* in the ring buffer. Just close both ends and exit.
*/
close(pushfd);
close(pullfd);
pushfd = pullfd = -1;
} else { } else {
if (pullfd >= 0) { perror("pull_pump write()");
if (!ring_buffer_filled_p(&ring)) { close(pushfd);
maxfd = pullfd; close(pullfd);
FD_SET(pullfd, &exceptfds);
FD_SET(pullfd, &readfds);
}
} }
return;
} }
if (maxfd >= 0) { /* Select for writablity on the pipe end.
r = select(maxfd+1, &readfds, &writefds, &exceptfds, NULL); * Very rarely will this stick.
*/
r = select(pushfd+1, NULL, &writefds, &exceptfds, NULL);
if (r < 0 || (pullfd >= 0 && FD_ISSET(pushfd, &exceptfds))) { if (r < 0 || FD_ISSET(pushfd, &exceptfds)) {
close(pushfd); close(pushfd);
close(pullfd); close(pullfd);
pushfd = pullfd = -1; pushfd = pullfd = -1;
return; return;
} }
} }
if (pullfd >= 0 && FD_ISSET(pullfd, &exceptfds)) {
close(pullfd);
pullfd = -1;
} }
assert(pullfd < 0);
assert(ring_buffer_empty_p(&ring));
close(pushfd);
}
/* PUSH PUMP
*
* This is used to push data out to a blocking file descriptor. It pulls
* data from a non-blocking pipe (pullfd) and pushes to STDOUT_FILENO
* (pushfd).
* When the pipe is closed, then the rest of the data is pushed out and then
* STDOUT_FILENO is closed.
*
* The algorithm looks roughly like this:
*
* while (true) {
* r = read(pipe) // nonblocking
*
* while (!ring.empty) {
* write(STDOUT_FILENO) // blocking
* }
*
* select(pipe, readable);
* }
*/
static void
push_pump (int pullfd, int pushfd)
{
int r;
ring_buffer ring;
fd_set readfds, exceptfds;
FD_ZERO(&exceptfds);
FD_ZERO(&readfds);
FD_SET(pullfd, &exceptfds);
FD_SET(pullfd, &readfds);
ring_buffer_init(&ring);
/* The pipe is open or there is data left to be pushed out
* NOTE: if pushfd (STDOUT_FILENO) ever errors out, then we just exit the
* loop.
*/
while (pullfd >= 0 || !ring_buffer_empty_p(&ring)) {
if (pullfd >= 0 && (is_pull || FD_ISSET(pullfd, &readfds))) { /* Pull from the non-blocking pipe */
r = ring_buffer_pull(&ring, pullfd); r = ring_buffer_pull(&ring, pullfd);
if (r == 0) { if (r == 0) {
/* eof */ /* eof */
close(pullfd); close(pullfd);
pullfd = -1; pullfd = -1;
} else if (r < 0 && errno != EINTR && errno != EAGAIN) {
} else if (r < 0) { perror("push_pump read()");
if (errno != EINTR && errno != EAGAIN) goto error; close(pullfd);
} pullfd = -1;
return;
} }
if (!is_pull || FD_ISSET(pushfd, &writefds)) { /* Push everything out to STDOUT */
while (!ring_buffer_empty_p(&ring)) {
/* Blocking write() to pushfd (STDOUT_FILENO) */
r = ring_buffer_push(&ring, pushfd); r = ring_buffer_push(&ring, pushfd);
if (r < 0) {
switch (errno) { /* If there was a problem, just exit the entire function */
case EINTR:
case EAGAIN: if (r < 0 && errno != EINTR) {
continue;
case EPIPE:
/* TODO catch SIGPIPE? */
close(pushfd); close(pushfd);
close(pullfd); close(pullfd);
pushfd = pullfd = -1; pushfd = pullfd = -1;
return; return;
default:
goto error;
}
}
} }
} }
if (pullfd >= 0) {
/* select for readability on the pullfd */
r = select(pullfd+1, &readfds, NULL, &exceptfds, NULL);
if (r < 0 || FD_ISSET(pullfd, &exceptfds)) {
close(pushfd); close(pushfd);
close(pullfd); close(pullfd);
pushfd = pullfd = -1;
return; return;
}
error: }
}
/* If we got here then we got eof on pullfd and pushed all the data out.
* so now just close pushfd */
assert(pullfd < 0);
assert(ring_buffer_empty_p(&ring));
close(pushfd); close(pushfd);
close(pullfd);
perror("(coupling) pump");
} }
static inline int static inline int
@ -262,7 +340,11 @@ pump_thread (void *data)
{ {
struct coupling *c = (struct coupling*)data; struct coupling *c = (struct coupling*)data;
pump(c->is_pull, c->pullfd, c->pushfd); if (c->is_pull) {
pull_pump(c->pullfd, c->pushfd);
} else {
push_pump(c->pullfd, c->pushfd);
}
return NULL; return NULL;
} }

28
doc/api.txt

@ -1,7 +1,7 @@
NODE(1) NODE(1)
======= =======
Ryan Dahl <ry@tinyclouds.org> Ryan Dahl <ry@tinyclouds.org>
Version, 0.1.26, 2010.01.20 Version, 0.1.27, 2010.02.03
== NAME == NAME
@ -48,7 +48,7 @@ execution.
=== Global Objects === Global Objects
+GLOBAL+ :: +global+ ::
The global namespace object. The global namespace object.
+process+ :: +process+ ::
@ -100,10 +100,10 @@ more information.
signal names such as SIGINT, SIGUSR1, etc. signal names such as SIGINT, SIGUSR1, etc.
|========================================================= |=========================================================
+process.ARGV+ :: +process.argv+ ::
An array containing the command line arguments. An array containing the command line arguments.
+process.ENV+ :: +process.env+ ::
An object containing the user environment. See environ(7). An object containing the user environment. See environ(7).
+process.pid+ :: +process.pid+ ::
@ -565,7 +565,7 @@ Node provides a tridirectional +popen(3)+ facility through the class
+"error"+ callbacks will no longer be made. +"error"+ callbacks will no longer be made.
|========================================================= |=========================================================
+process.createChildProcess(command, args=[], env=ENV)+:: +process.createChildProcess(command, args=[], env=process.env)+::
Launches a new process with the given +command+, command line arguments, and Launches a new process with the given +command+, command line arguments, and
environmental variables. For example: environmental variables. For example:
+ +
@ -1459,14 +1459,14 @@ resolution.addCallback(function (addresses, ttl, cname) {
reversing.addCallback( function (domains, ttl, cname) { reversing.addCallback( function (domains, ttl, cname) {
sys.puts("reverse for " + a + ": " + JSON.stringify(domains)); sys.puts("reverse for " + a + ": " + JSON.stringify(domains));
}); });
reversing.addErrback( function (code, msg) { reversing.addErrback( function (e) {
sys.puts("reverse for " + a + " failed: " + msg); puts("reverse for " + a + " failed: " + e.message);
}); });
} }
}); });
resolution.addErrback(function (code, msg) { resolution.addErrback(function (e) {
sys.puts("error: " + msg); puts("error: " + e.message);
}); });
------------------------------------------------------------------------- -------------------------------------------------------------------------
@ -1482,8 +1482,9 @@ This function returns a promise.
canonical name for the query. canonical name for the query.
The type of each item in +addresses+ is determined by the record type, and The type of each item in +addresses+ is determined by the record type, and
described in the documentation for the corresponding lookup methods below. described in the documentation for the corresponding lookup methods below.
- on error: returns +code, msg+. +code+ is one of the error codes listed - on error: Returns an instanceof Error object, where the "errno" field is one
below and +msg+ is a string describing the error in English. of the error codes listed below and the "message" field is a string
describing the error in English.
+dns.resolve4(domain)+:: +dns.resolve4(domain)+::
@ -1521,8 +1522,9 @@ Reverse resolves an ip address to an array of domain names.
- on success: returns +domains, ttl, cname+. +ttl+ (time-to-live) is an integer - on success: returns +domains, ttl, cname+. +ttl+ (time-to-live) is an integer
specifying the number of seconds this result is valid for. +cname+ is the specifying the number of seconds this result is valid for. +cname+ is the
canonical name for the query. +domains+ is an array of domains. canonical name for the query. +domains+ is an array of domains.
- on error: returns +code, msg+. +code+ is one of the error codes listed - on error: Returns an instanceof Error object, where the "errno" field is one
below and +msg+ is a string describing the error in English. of the error codes listed below and the "message" field is a string
describing the error in English.
Each DNS query can return an error code. Each DNS query can return an error code.

4
doc/index.html

@ -97,9 +97,9 @@ server.listen(7000, "localhost");</pre>
<a href="http://github.com/ry/node/tree/master">git repo</a> <a href="http://github.com/ry/node/tree/master">git repo</a>
</p> </p>
<p> <p>
2010.01.20 2010.02.03
<a <a
href="http://s3.amazonaws.com/four.livejournal/20100120/node-v0.1.26.tar.gz">node-v0.1.26.tar.gz</a> href="http://s3.amazonaws.com/four.livejournal/20100203/node-v0.1.27.tar.gz">node-v0.1.27.tar.gz</a>
</p> </p>
<h2 id="build">Build</h2> <h2 id="build">Build</h2>

2
lib/multipart.js

@ -183,7 +183,7 @@ Part.prototype.write = function(chunk) {
var header = this.buffer.substr(0, offset).split(/: ?/); var header = this.buffer.substr(0, offset).split(/: ?/);
this.headers[header[0].toLowerCase()] = header[1]; this.headers[header[0].toLowerCase()] = header[1];
this.buffer = this.buffer.substr(offset+2); this.buffer = this.buffer.substr(offset+2);
} else if (offset === false) { } else if (offset === -1) {
return; return;
} }
} }

7
src/node.cc

@ -918,7 +918,7 @@ static void Load(int argc, char *argv[]) {
#define str(s) #s #define str(s) #s
process->Set(String::NewSymbol("platform"), String::New(xstr(PLATFORM))); process->Set(String::NewSymbol("platform"), String::New(xstr(PLATFORM)));
// process.ARGV // process.argv
int i, j; int i, j;
Local<Array> arguments = Array::New(argc - dash_dash_index + 1); Local<Array> arguments = Array::New(argc - dash_dash_index + 1);
arguments->Set(Integer::New(0), String::New(argv[0])); arguments->Set(Integer::New(0), String::New(argv[0]));
@ -928,8 +928,9 @@ static void Load(int argc, char *argv[]) {
} }
// assign it // assign it
process->Set(String::NewSymbol("ARGV"), arguments); process->Set(String::NewSymbol("ARGV"), arguments);
process->Set(String::NewSymbol("argv"), arguments);
// create process.ENV // create process.env
Local<Object> env = Object::New(); Local<Object> env = Object::New();
for (i = 0; environ[i]; i++) { for (i = 0; environ[i]; i++) {
// skip entries without a '=' character // skip entries without a '=' character
@ -945,6 +946,8 @@ static void Load(int argc, char *argv[]) {
} }
// assign process.ENV // assign process.ENV
process->Set(String::NewSymbol("ENV"), env); process->Set(String::NewSymbol("ENV"), env);
process->Set(String::NewSymbol("env"), env);
process->Set(String::NewSymbol("pid"), Integer::New(getpid())); process->Set(String::NewSymbol("pid"), Integer::New(getpid()));
// define various internal methods // define various internal methods

81
src/node.js

@ -41,11 +41,20 @@ node.dns.createConnection = removed("node.dns.createConnection() has moved. Use
// Module // Module
var internalModuleCache = {};
function Module (id, parent) { function Module (id, parent) {
this.id = id; this.id = id;
this.exports = {}; this.exports = {};
this.parent = parent; this.parent = parent;
this.moduleCache = {};
if (parent) {
process.mixin(this.moduleCache, parent.moduleCache);
this.moduleCache[parent.id] = parent;
}
this.filename = null; this.filename = null;
this.loaded = false; this.loaded = false;
this.loadPromise = null; this.loadPromise = null;
@ -53,23 +62,11 @@ function Module (id, parent) {
this.children = []; this.children = [];
}; };
var moduleCache = {};
function createModule (id, parent) {
if (id in moduleCache) {
debug("found " + JSON.stringify(id) + " in cache");
return moduleCache[id];
}
debug("didn't found " + JSON.stringify(id) + " in cache. creating new module");
var m = new Module(id, parent);
moduleCache[id] = m;
return m;
};
function createInternalModule (id, constructor) { function createInternalModule (id, constructor) {
var m = createModule(id); var m = new Module(id);
constructor(m.exports); constructor(m.exports);
m.loaded = true; m.loaded = true;
internalModuleCache[id] = m;
return m; return m;
}; };
@ -86,7 +83,7 @@ process.inherits = function (ctor, superCtor) {
process.createChildProcess = function (file, args, env) { process.createChildProcess = function (file, args, env) {
var child = new process.ChildProcess(); var child = new process.ChildProcess();
args = args || []; args = args || [];
env = env || process.ENV; env = env || process.env;
var envPairs = []; var envPairs = [];
for (var key in env) { for (var key in env) {
if (env.hasOwnProperty(key)) { if (env.hasOwnProperty(key)) {
@ -493,7 +490,7 @@ GLOBAL.clearInterval = GLOBAL.clearTimeout;
// Modules // Modules
var debugLevel = 0; var debugLevel = 0;
if ("NODE_DEBUG" in process.ENV) debugLevel = 1; if ("NODE_DEBUG" in process.env) debugLevel = 1;
function debug (x) { function debug (x) {
if (debugLevel > 0) { if (debugLevel > 0) {
@ -744,12 +741,12 @@ var path = pathModule.exports;
process.paths = [ path.join(process.installPrefix, "lib/node/libraries") process.paths = [ path.join(process.installPrefix, "lib/node/libraries")
]; ];
if (process.ENV["HOME"]) { if (process.env["HOME"]) {
process.paths.unshift(path.join(process.ENV["HOME"], ".node_libraries")); process.paths.unshift(path.join(process.env["HOME"], ".node_libraries"));
} }
if (process.ENV["NODE_PATH"]) { if (process.env["NODE_PATH"]) {
process.paths = process.ENV["NODE_PATH"].split(":").concat(process.paths); process.paths = process.env["NODE_PATH"].split(":").concat(process.paths);
} }
@ -803,11 +800,7 @@ function findModulePath (id, dirs, callback) {
searchLocations(); searchLocations();
} }
function loadModule (request, parent) { function resolveModulePath(request, parent) {
// This is the promise which is actually returned from require.async()
var loadPromise = new events.Promise();
// debug("loadModule REQUEST " + (request) + " parent: " + JSON.stringify(parent));
var id, paths; var id, paths;
if (request.charAt(0) == "." && (request.charAt(1) == "/" || request.charAt(1) == ".")) { if (request.charAt(0) == "." && (request.charAt(1) == "/" || request.charAt(1) == ".")) {
@ -823,12 +816,24 @@ function loadModule (request, parent) {
paths = process.paths; paths = process.paths;
} }
if (id in moduleCache) { return [id, paths];
}
function loadModule (request, parent) {
var
// The promise returned from require.async()
loadPromise = new events.Promise(),
resolvedModule = resolveModulePath(request, parent),
id = resolvedModule[0],
paths = resolvedModule[1];
// debug("loadModule REQUEST " + (request) + " parent: " + JSON.stringify(parent));
var cachedModule = internalModuleCache[id] || parent.moduleCache[id];
if (cachedModule) {
debug("found " + JSON.stringify(id) + " in cache"); debug("found " + JSON.stringify(id) + " in cache");
// In cache process.nextTick(function() {
var module = moduleCache[id]; loadPromise.emitSuccess(cachedModule.exports);
process.nextTick(function () {
loadPromise.emitSuccess(module.exports);
}); });
} else { } else {
debug("looking for " + JSON.stringify(id) + " in " + JSON.stringify(paths)); debug("looking for " + JSON.stringify(id) + " in " + JSON.stringify(paths));
@ -837,7 +842,7 @@ function loadModule (request, parent) {
if (!filename) { if (!filename) {
loadPromise.emitError(new Error("Cannot find module '" + request + "'")); loadPromise.emitError(new Error("Cannot find module '" + request + "'"));
} else { } else {
var module = createModule(id, parent); var module = new Module(id, parent);
module.load(filename, loadPromise); module.load(filename, loadPromise);
} }
}); });
@ -968,19 +973,19 @@ process.exit = function (code) {
var cwd = process.cwd(); var cwd = process.cwd();
// Make process.ARGV[0] and process.ARGV[1] into full paths. // Make process.argv[0] and process.argv[1] into full paths.
if (process.ARGV[0].indexOf('/') > 0) { if (process.argv[0].indexOf('/') > 0) {
process.ARGV[0] = path.join(cwd, process.ARGV[0]); process.argv[0] = path.join(cwd, process.argv[0]);
} }
if (process.ARGV[1].charAt(0) != "/" && !(/^http:\/\//).exec(process.ARGV[1])) { if (process.argv[1].charAt(0) != "/" && !(/^http:\/\//).exec(process.argv[1])) {
process.ARGV[1] = path.join(cwd, process.ARGV[1]); process.argv[1] = path.join(cwd, process.argv[1]);
} }
// Load the main module--the command line argument. // Load the main module--the command line argument.
process.mainModule = createModule("."); process.mainModule = new Module(".");
var loadPromise = new events.Promise(); var loadPromise = new events.Promise();
process.mainModule.load(process.ARGV[1], loadPromise); process.mainModule.load(process.argv[1], loadPromise);
// All our arguments are loaded. We've evaluated all of the scripts. We // All our arguments are loaded. We've evaluated all of the scripts. We
// might even have created TCP servers. Now we enter the main eventloop. If // might even have created TCP servers. Now we enter the main eventloop. If

0
test/mjsunit/test-dns.js → test/mjsunit/disabled/test-dns.js

0
test/mjsunit/test-fs-sendfile.js → test/mjsunit/disabled/test-fs-sendfile.js

12
test/mjsunit/fixtures/echo.js

@ -0,0 +1,12 @@
process.mixin(require("../common"));
process.stdio.open();
print("hello world\r\n");
process.stdio.addListener("data", function (data) {
print(data);
});
process.stdio.addListener("close", function () {
process.stdio.close();
});

1
test/mjsunit/test-module-loading.js

@ -41,7 +41,6 @@ assert.notEqual(one.hello, two.hello);
debug("test cycles containing a .. path"); debug("test cycles containing a .. path");
var root = require("./fixtures/cycles/root"), var root = require("./fixtures/cycles/root"),
foo = require("./fixtures/cycles/folder/foo"); foo = require("./fixtures/cycles/folder/foo");
assert.equal(root.foo, foo);
assert.equal(root.sayHello(), root.hello); assert.equal(root.sayHello(), root.hello);
var errorThrown = false; var errorThrown = false;

15
test/mjsunit/test-readdir.js

@ -7,9 +7,18 @@ puts("readdir " + fixturesDir);
promise.addCallback(function (files) { promise.addCallback(function (files) {
p(files); p(files);
assert.deepEqual(["a.js", "b","cycles", "multipart.js", assert.deepEqual(['a.js'
"nested-index","test_ca.pem", , 'b'
"test_cert.pem", "test_key.pem", "throws_error.js", "x.txt"], files.sort()); , 'cycles'
, 'echo.js'
, 'multipart.js'
, 'nested-index'
, 'test_ca.pem'
, 'test_cert.pem'
, 'test_key.pem'
, 'throws_error.js'
, 'x.txt'
], files.sort());
}); });
promise.addErrback(function () { promise.addErrback(function () {

35
test/mjsunit/test-stdio.js

@ -0,0 +1,35 @@
process.mixin(require("./common"));
var sub = path.join(fixturesDir, 'echo.js');
var gotHelloWorld = false;
var gotEcho = false;
var child = process.createChildProcess(process.argv[0], [sub]);
child.addListener("error", function (data){
puts("parent stderr: " + data);
});
child.addListener("output", function (data){
if (data) {
puts('child said: ' + JSON.stringify(data));
if (!gotHelloWorld) {
assert.equal("hello world\r\n", data);
gotHelloWorld = true;
child.write('echo me\r\n');
} else {
assert.equal("echo me\r\n", data);
gotEcho = true;
child.close();
}
} else {
puts('child eof');
}
});
process.addListener('exit', function () {
assert.ok(gotHelloWorld);
assert.ok(gotEcho);
});

1
tools/test.py

@ -101,7 +101,6 @@ class ProgressIndicator(object):
# ...and then reraise the exception to bail out # ...and then reraise the exception to bail out
raise raise
self.Done() self.Done()
print "\r\nPlatform: {0} {1}".format(platform.system(), platform.release())
return not self.failed return not self.failed
def RunSingle(self): def RunSingle(self):

2
wscript

@ -7,7 +7,7 @@ from os.path import join, dirname, abspath
from logging import fatal from logging import fatal
cwd = os.getcwd() cwd = os.getcwd()
VERSION="0.1.26" VERSION="0.1.27"
APPNAME="node.js" APPNAME="node.js"
import js2c import js2c

Loading…
Cancel
Save