Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/cli/cli_common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef cli_common_h
#define cli_common_h

#include <stdlib.h>
#include <string.h>

inline char* cli_strdup(const char* s) {
size_t len = strlen(s) + 1;
char* m = (char*)malloc(len);
if (m == NULL) return NULL;
return memcpy(m, s, len);
}

inline char* cli_strndup(const char* s, size_t n) {
char* m;
size_t len = strlen(s);
if (n < len) len = n;
m = (char*)malloc(len + 1);
if (m == NULL) return NULL;
m[len] = '\0';
return memcpy(m, s, len);
}

#endif
2 changes: 2 additions & 0 deletions src/cli/modules.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ extern void processCwd(WrenVM* vm);
extern void processPid(WrenVM* vm);
extern void processPpid(WrenVM* vm);
extern void processVersion(WrenVM* vm);
extern void processExec(WrenVM* vm);
extern void statPath(WrenVM* vm);
extern void statBlockCount(WrenVM* vm);
extern void statBlockSize(WrenVM* vm);
Expand Down Expand Up @@ -180,6 +181,7 @@ static ModuleRegistry modules[] =
STATIC_METHOD("pid", processPid)
STATIC_METHOD("ppid", processPpid)
STATIC_METHOD("version", processVersion)
STATIC_METHOD("exec_(_,_,_,_,_)", processExec)
END_CLASS
END_MODULE
MODULE(repl)
Expand Down
123 changes: 123 additions & 0 deletions src/module/os.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#include "os.h"
#include "uv.h"
#include "wren.h"
#include "vm.h"
#include "scheduler.h"
#include "cli_common.h"

#include <stdint.h>

#if __APPLE__
#include "TargetConditionals.h"
Expand Down Expand Up @@ -128,3 +133,121 @@ void processVersion(WrenVM* vm) {
wrenEnsureSlots(vm, 1);
wrenSetSlotString(vm, 0, WREN_VERSION_STRING);
}

// Called when the UV handle for a process is done, so we can free it
static void processOnClose(uv_handle_t* req)
{
free((void*)req);
}

// Called when a process is finished running
static void processOnExit(uv_process_t* req, int64_t exit_status, int term_signal)
{
ProcessData* data = (ProcessData*)req->data;
WrenHandle* fiber = data->fiber;

uv_close((uv_handle_t*)req, processOnClose);

int index = 0;
char* arg = data->options.args[index];
while (arg != NULL)
{
free(arg);
index += 1;
arg = data->options.args[index];
}

index = 0;
if (data->options.env) {
char* env = data->options.env[index];
while (env != NULL)
{
free(env);
index += 1;
env = data->options.env[index];
}
}

free((void*)data);

schedulerResume(fiber, true);
wrenSetSlotDouble(getVM(), 2, (double)exit_status);
schedulerFinishResume();
}

// 1 2 3 4 5
// exec_(cmd, args, cwd, env, fiber)
void processExec(WrenVM* vm)
{
ProcessData* data = (ProcessData*)malloc(sizeof(ProcessData));
memset(data, 0, sizeof(ProcessData));

//:todo: add env + cwd + flags args

char* cmd = cli_strdup(wrenGetSlotString(vm, 1));

if (wrenGetSlotType(vm, 3) != WREN_TYPE_NULL) {
const char* cwd = wrenGetSlotString(vm, 3);
data->options.cwd = cwd;
}

data->options.file = cmd;
data->options.exit_cb = processOnExit;
data->fiber = wrenGetSlotHandle(vm, 5);

wrenEnsureSlots(vm, 6);

if (wrenGetSlotType(vm, 4) == WREN_TYPE_NULL) {
// no environment specified
} else if (wrenGetSlotType(vm, 4) == WREN_TYPE_LIST) {
int envCount = wrenGetListCount(vm, 4);
int envSize = sizeof(char*) * (envCount + 1);

data->options.env = (char**)malloc(envSize);
data->options.env[envCount] = NULL;

for (int i = 0; i < envCount ; i++)
{
wrenGetListElement(vm, 4, i, 6);
if (wrenGetSlotType(vm, 6) != WREN_TYPE_STRING) {
wrenSetSlotString(vm, 0, "arguments to env are supposed to be strings");
wrenAbortFiber(vm, 0);
}
char* envKeyPlusValue = cli_strdup(wrenGetSlotString(vm, 6));
data->options.env[i] = envKeyPlusValue;
}
}

int argCount = wrenGetListCount(vm, 2);
int argsSize = sizeof(char*) * (argCount + 2);

// First argument is the cmd, last+1 is NULL
data->options.args = (char**)malloc(argsSize);
data->options.args[0] = cmd;
data->options.args[argCount + 1] = NULL;

for (int i = 0; i < argCount; i++)
{
wrenGetListElement(vm, 2, i, 3);
if (wrenGetSlotType(vm, 3) != WREN_TYPE_STRING) {
wrenSetSlotString(vm, 0, "arguments to args are supposed to be strings");
wrenAbortFiber(vm, 0);
}
char* arg = cli_strdup(wrenGetSlotString(vm, 3));
data->options.args[i + 1] = arg;
}

uv_process_t* child_req = (uv_process_t*)malloc(sizeof(uv_process_t));
memset(child_req, 0, sizeof(uv_process_t));

child_req->data = data;

int r;
if ((r = uv_spawn(getLoop(), child_req, &data->options)))
{
// should be stderr??? but no idea how to make tests work/pass with that
fprintf(stdout, "Could not launch %s, reason: %s\n", cmd, uv_strerror(r));
wrenSetSlotString(vm, 0, "Could not spawn process.");
wrenAbortFiber(vm, 0);
}
}
6 changes: 6 additions & 0 deletions src/module/os.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
#ifndef process_h
#define process_h

#include "uv.h"
#include "wren.h"

#define WREN_PATH_MAX 4096

typedef struct {
WrenHandle* fiber;
uv_process_options_t options;
} ProcessData;

// Stores the command line arguments passed to the CLI.
void osSetArguments(int argc, const char* argv[]);

Expand Down
30 changes: 30 additions & 0 deletions src/module/os.wren
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "scheduler" for Scheduler

class Platform {
foreign static homePath
foreign static isPosix
Expand All @@ -10,6 +12,34 @@ class Process {
// TODO: This will need to be smarter when wren supports CLI options.
static arguments { allArguments.count >= 2 ? allArguments[2..-1] : [] }

static exec(cmd) {
return exec(cmd, null, null, null)
}

static exec(cmd, args) {
return exec(cmd, args, null, null)
}

static exec(cmd, args, cwd) {
return exec(cmd, args, cwd, null)
}

static exec(cmd, args, cwd, envMap) {
var env = []
args = args || []
if (envMap is Map) {
for (entry in envMap) {
env.add([entry.key, entry.value].join("="))
}
} else if (envMap == null) {
env = null
} else {
Fiber.abort("environment vars must be passed as a Map")
}
return Scheduler.await_ { exec_(cmd, args, cwd, env, Fiber.current) }
}

foreign static exec_(cmd, args, cwd, env, fiber)
foreign static allArguments
foreign static cwd
foreign static pid
Expand Down
30 changes: 30 additions & 0 deletions src/module/os.wren.inc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// from `src/module/os.wren` using `util/wren_to_c_string.py`

static const char* osModuleSource =
"import \"scheduler\" for Scheduler\n"
"\n"
"class Platform {\n"
" foreign static homePath\n"
" foreign static isPosix\n"
Expand All @@ -14,6 +16,34 @@ static const char* osModuleSource =
" // TODO: This will need to be smarter when wren supports CLI options.\n"
" static arguments { allArguments.count >= 2 ? allArguments[2..-1] : [] }\n"
"\n"
" static exec(cmd) {\n"
" return exec(cmd, null, null, null)\n"
" }\n"
"\n"
" static exec(cmd, args) {\n"
" return exec(cmd, args, null, null)\n"
" }\n"
"\n"
" static exec(cmd, args, cwd) { \n"
" return exec(cmd, args, cwd, null) \n"
" }\n"
" \n"
" static exec(cmd, args, cwd, envMap) { \n"
" var env = []\n"
" args = args || []\n"
" if (envMap is Map) {\n"
" for (entry in envMap) {\n"
" env.add([entry.key, entry.value].join(\"=\"))\n"
" }\n"
" } else if (envMap == null) {\n"
" env = null\n"
" } else {\n"
" Fiber.abort(\"environment vars must be passed as a Map\")\n"
" }\n"
" return Scheduler.await_ { exec_(cmd, args, cwd, env, Fiber.current) }\n"
" }\n"
"\n"
" foreign static exec_(cmd, args, cwd, env, fiber)\n"
" foreign static allArguments\n"
" foreign static cwd\n"
" foreign static pid\n"
Expand Down
57 changes: 57 additions & 0 deletions test/os/process/exec.wren
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import "os" for Platform, Process

var TRY = Fn.new { |fn|
var fiber = Fiber.new {
fn.call()
}
return fiber.try()
}

var result
if(Platform.name == "Windows") {
result = Process.exec("cmd.exe")
} else {
result = Process.exec("true")
}
System.print(result) // expect: 0

// basics

if (Platform.isWindows) {
// TODO: more windows argument specific tests
} else {
// known output of success/fail based on only command name
System.print(Process.exec("true")) // expect: 0
System.print(Process.exec("false")) // expect: 1
// these test that our arguments are being passed as it proves
// they effect the result code returned
System.print(Process.exec("test", ["2", "-eq", "2"])) // expect: 0
System.print(Process.exec("test", ["2", "-eq", "3"])) // expect: 1
}

// cwd

if (Platform.isWindows) {
// TODO: can this be done with dir on windows?
} else {
// tests exists in our project folder
System.print(Process.exec("ls", ["test"])) // expect: 0
// but does not in our `src` folder
System.print(Process.exec("ls", ["test"], "./src/")) // expect: 1
}

// env

if (Platform.name == "Windows") {
// TODO: how?
} else {
System.print(Process.exec("true",[],null,{})) // expect: 0
var result = TRY.call {
Process.exec("ls",[],null,{"PATH": "/whereiscarmen/"})
}
System.print(result)
// TODO: should be on stderr
// expect: Could not launch ls, reason: no such file or directory
// TODO: should this be a runtime error?????
// expect: Could not spawn process.
}