// rdmd - a program to compile, cache and execute D programming // language source files via either the command-line or as a // 'pseudo shell script' on POSIX conforming Linux systems. // // Written by Dave Fladebo and released into the public domain as // explained by http://creativecommons.org/licenses/publicdomain // // This software is provided "AS IS" and without any express or // implied warranties, including and without limitation to, any // warranty of merchantability or fitness for any purpose. // // Windows additions by Roberto Mariottini // // version 1.1 version (Windows) { import std.c.windows.windows; char[] fileSeparator = "\\"; char[] pathSeparator = ";"; char[] objectExtension = ".obj"; char[] exeExtension = ".exe"; } else { import std.c.linux.linux; char[] fileSeparator = "/"; char[] pathSeparator = ":"; char[] objectExtension = ".o"; char[] exeExtension = ""; } bool verbose = false; import std.c.stdlib, std.file, std.md5, std.process, std.stdio, std.string; int main(char[][] args) { int retval = -1; bool havefile = false, force = false; char[][] cmpv, argv; // cmpv = compiler arguments, argv = program arguments char[] exepath, dfilepath, compiler = "dmd", tmpdir = "/tmp"; version (Windows) { tmpdir = toString(getenv("TEMP")); } .myname = args[0]; foreach(int i, char[] arg; args) { if(i == 0) continue; if(find(arg,".d") >= 0 || find(arg,".ds") >= 0) { havefile = true; dfilepath = arg; } else { if(havefile == false) { bool skip = false; if(arg == "--help") usage; else if(arg == "--force") skip = force = true; else if(arg == "--verbose") skip = verbose = true; else { const char[] cs = "--compiler="; if(arg.length > cs.length && arg[0..cs.length] == cs) { compiler = split(arg,"=")[1]; skip = true; } const char[] td = "--tmpdir="; if(arg.length > td.length && arg[0..td.length] == td) { tmpdir = split(arg,"=")[1]; skip = true; } } if(!skip) cmpv ~= arg; } else argv ~= arg; } } if(!havefile) error("Couldn't find any D source code file to compile or execute.", retval); if(compile(tmpdir,compiler,force,dfilepath,cmpv,exepath)) { char[][] exeargv; exeargv ~= exepath; foreach(char[] arg; argv) exeargv ~= arg; if(verbose) { fwritef(stderr,"running: "); foreach(char[] arg; exeargv) { fwritef(stderr,arg," "); } fwritefln(stderr); } // execute version (Windows) { retval = spawnvp(std.c.process._P_WAIT, exepath, exeargv); } else { retval = spawnapp(exepath,exeargv); } } else { try { std.file.remove(exepath); } catch {} error("Couldn't compile or execute " ~ dfilepath ~ ".", retval); } return retval; } char[] myname; void error(char[] errmsg, int errno) { fwritefln(stderr,myname,": ",errmsg); exit(errno); } void usage() { fwritefln(stderr,"Usage:"); fwritefln(stderr," ",myname," [D compiler arguments] [",myname," arguments] progfile.d [program arguments]"); fwritefln(stderr); fwritefln(stderr,myname," arguments:"); fwritefln(stderr," --help\t\tThis message"); fwritefln(stderr," --force\t\tForce re-compilation of source code [default = do not force]"); fwritefln(stderr," --verbose\t\tShow detailed info of operations [default = do not show]"); fwritefln(stderr," --compiler=(dmd|gdmd)\tSpecify compiler [default = dmd]"); fwritefln(stderr," --tmpdir=tmp_dir_path\tSpecify directory to store cached program and other temporaries [default = /tmp]"); fwritefln(stderr); fwritefln(stderr,"Notes:"); fwritefln(stderr," dmd or gdmd must be in the current user context $PATH"); fwritefln(stderr," ",myname," does not support execution of D source code via stdin"); fwritefln(stderr," ",myname," will only compile and execute files with a '.d' file extension"); exit(EXIT_SUCCESS); } bool compile(char[] tmpdir, char[] compiler, bool force, char[] dfilepath, char[][] cmpv, inout char[] exepath) { int retval = 0; struct_stat dfilestat; // D source code file status info. int filrv = stat(dfilepath,&dfilestat); char[][] pathcomps = split(dfilepath,fileSeparator); char[] exefile = split(pathcomps[$-1],".")[0]; char[] cmdline = compiler ~ " -quiet"; foreach(char[] str; cmpv) if(str != "") cmdline ~= " " ~ str; // MD5 sum of compiler arguments ubyte[16] digest; sum(digest,cast(void[])cmdline); // directory for temp. files if(!tmpdir.length) tmpdir = "/tmp/"; else if(tmpdir[$-1] != fileSeparator[0]) tmpdir ~= fileSeparator; // exe filename format is basename-uid-filesysdev-inode-MD5 // append MD5 sum of the compiler arguments onto the file name to force recompile if they have changed exepath = tmpdir ~ exefile ~ "-" ~ toString(getuid()) ~ "-" ~ toString(dfilestat.st_dev) ~ "-" ~ toString(dfilestat.st_ino) ~ "-" ~ digestToString(digest) ~ exeExtension; struct_stat exestat; // temp. executable status info. int exerv = stat(exepath,&exestat); if(force || // force compilation exerv || // stat returned an error (e.g.: no exefile) dfilestat.st_mtime > exestat.st_mtime || // source code file is newer than executable progstat(.myname).st_mtime > exestat.st_mtime || // this program is newer than executable progstat(compiler).st_mtime > exestat.st_mtime) // compiler is newer than executable { cmdline ~= " " ~ dfilepath ~ " -of" ~ exepath ~ " -od" ~ tmpdir; if(verbose) { fwritefln(stderr,"running: ",cmdline); } retval = system(cmdline); // compile chmod(exepath,0700); } // remove object file try { std.file.remove(tmpdir ~ exefile ~ objectExtension); } catch {} return cast(bool)(retval == 0); } struct_stat progstat(char[] program) { struct_stat progstat; // D source code file status info. try { int prgrv; if(find(program,fileSeparator) >= 0) prgrv = stat(program, &progstat); else { // There's got to be a better way... char[][] pathdirs = split(toString(getenv("PATH")),pathSeparator); foreach(char[] dir; pathdirs) { prgrv = stat(dir ~ fileSeparator ~ program, &progstat); if (prgrv == 0) { break; } } } } catch {} return progstat; } version(linux) { int spawnapp(char[] pathname, char[][] argv) { int retval = 0; pid_t pid = fork(); if(pid != -1) { if(pid == 0) { execv(pathname,argv); goto Lerror; } while(1) { int status; pid_t wpid = waitpid(pid, &status, 0); if(exited(status)) { retval = exitstatus(status); break; } else if(signaled(status)) { retval = -termsig(status); break; } else if(stopped(status)) // ptrace support continue; else goto Lerror; } return retval; } Lerror: retval = getErrno; error("Cannot spawn " ~ toString(pathname) ~ "; " ~ toString(strerror(retval)) ~ " [errno " ~ toString(retval) ~ "]", retval); return retval; } extern(C) { char* strerror(int); ushort getuid(); } bool stopped(int status) { return cast(bool)((status & 0xff) == 0x7f); } bool signaled(int status) { return cast(bool)((cast(char)((status & 0x7f) + 1) >> 1) > 0); } int termsig(int status) { return status & 0x7f; } bool exited(int status) { return cast(bool)((status & 0x7f) == 0); } int exitstatus(int status) { return (status & 0xff00) >> 8; } } // version(linux) version (Windows) { extern (Windows) { BOOL GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime); BOOL GetUserNameA(LPTSTR lpBuffer, LPDWORD nSize); } // fake struct stat struct struct_stat { ulong st_dev; uint st_ino; ulong st_mtime; } // fake stat function int stat(char* name, struct_stat* st) { int retval = -1; HANDLE h = CreateFileA(name, FILE_GENERIC_READ, 0, null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null); if (h != INVALID_HANDLE_VALUE) { FILETIME lastWriteTime; if (GetFileTime(h, null, null, &lastWriteTime)) { st.st_mtime = lastWriteTime.dwHighDateTime; st.st_mtime <<= 32; st.st_mtime |= lastWriteTime.dwLowDateTime; retval = 0; } CloseHandle(h); } if(verbose) { fwritefln(stderr,"stat: ",toString(name)," : ",retval); } return retval; } // fake getuid function char[] getuid() { char[] buffer; DWORD size = buffer.length = 64; if(GetUserNameA(buffer, &size)) { buffer.length = size; return buffer; } return ""; } // fake chmod function void chmod(...) { } }