Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(enh) adds Process.exec(cmd, [args], [cwd],[env]) #94

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
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
132 changes: 132 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,130 @@ 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.args[index];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in later commit.

}
}

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) {
// fprintf(stderr,"got list\n");
int envCount = wrenGetListCount(vm, 4);
int envSize = sizeof(char*) * (envCount + 1);

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

// fprintf(stderr,"envsize %d\n", envCount);
for (int i = 0; i < envCount ; i++)
{

wrenGetListElement(vm, 4, i, 6);
//:todo: ensure this is a string, and report an error if not

if (wrenGetSlotType(vm, 6) != WREN_TYPE_STRING) {
wrenSetSlotString(vm, 0, "aruments to params are suppose to be string");
wrenAbortFiber(vm, 0);
}
char* envKeyPlusValue = cli_strdup(wrenGetSlotString(vm, 6));
// fprintf(stderr,"key: %s\n", envKeyPlusValue);
// fprintf(stderr,"setting %s\n", envKeyPlusValue);
// char* equalSplit = strchr(envKeyPlusValue, '=');
// *equalSplit = '\0';
// char* key = envKeyPlusValue;
// char* value = equalSplit + 1;

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);
//:todo: ensure this is a string, and report an error if not
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)
}

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 = []
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")
}
exec_(cmd, args, cwd, env, Fiber.current)
return Scheduler.runNextScheduled_()
}

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)\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"
" 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"
" exec_(cmd, args, cwd, env, Fiber.current)\n"
" return Scheduler.runNextScheduled_()\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
51 changes: 51 additions & 0 deletions test/os/process/exec.wren
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "os" for Platform, Process

var result
if(Platform.name == "Windows") {
result = Process.exec("cmd.exe", [])
} else {
// works on Mac
result = Process.exec("/usr/bin/true", [])
}
System.print(result) // expect: 0

// basics

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

// cwd

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

// env

if (Platform.isPosix) {
System.print(Process.exec("/usr/bin/true",[],null,{})) // expect: 0

var fiber = Fiber.new {
Process.exec("ls",[],null,{"PATH": "/binx/"})
}
var r = fiber.try()
System.print(r)
// expect: Could not launch ls, reason: no such file or directory
// expect: Could not spawn process.
} else if (Platform.name == "Windows") {

}