1 // rdmd - a program to compile, cache and execute D programming
2 // language source files via either the command-line or as a
3 // 'pseudo shell script' on POSIX conforming Linux/Unix systems.
5 // Written by Dave Fladebo and released into the public domain as
6 // explained by http://creativecommons.org/licenses/publicdomain
7 // Windows version by Roberto Mariottini, GDC/Unix version by afb.
9 // This software is provided "AS IS" and without any express or
10 // implied warranties, including and without limitation to, any
11 // warranty of merchantability or fitness for any purpose.
17 import std
.c
.windows
.windows
;
19 string fileSeparator
= "\\";
20 string pathSeparator
= ";";
21 string objectExtension
= ".obj";
22 string exeExtension
= ".exe";
26 import std
.c
.linux
.linux
;
28 string fileSeparator
= "/";
29 string pathSeparator
= ":";
30 string objectExtension
= ".o";
31 string exeExtension
= "";
35 import std
.c
.unix
.unix
;
37 string fileSeparator
= "/";
38 string pathSeparator
= ":";
39 string objectExtension
= ".o";
40 string exeExtension
= "";
42 else static assert(0);
50 import std
.c
.stdlib
, std
.file
, std
.md5
, std
.process
, std
.stdio
, std
.string
;
54 extern(C
) ushort getuid(); // moved to top, because of mac linker issues
57 int main(string
[] args
)
60 bool havefile
= false, force
= false;
61 string
[] cmpv
, argv
; // cmpv = compiler arguments, argv = program arguments
62 string exepath
, dfilepath
, compiler
= "dmd", tmpdir
= "/tmp";
70 tmpdir
= toString(getenv("TEMP"));
76 foreach(int i
, string arg
; args
)
81 if(find(arg
,".d") >= 0 ||
find(arg
,".ds") >= 0)
93 else if(arg
== "--force")
95 else if(arg
== "--verbose")
96 skip
= verbose
= true;
99 const string cs
= "--compiler=";
100 if(arg
.length
> cs
.length
&& arg
[0..cs
.length
] == cs
)
102 compiler
= split(arg
,"=")[1];
105 const string td
= "--tmpdir=";
106 if(arg
.length
> td
.length
&& arg
[0..td
.length
] == td
)
108 tmpdir
= split(arg
,"=")[1];
122 error("Couldn't find any D source code file to compile or execute.", retval
);
124 if(compile(tmpdir
,compiler
,force
,dfilepath
,cmpv
,exepath
))
129 exeargv
~= "\"" ~ exepath
~ "\"";
130 foreach(string arg
; argv
) exeargv
~= "\"" ~ arg
~ "\"";
135 foreach(string arg
; argv
) exeargv
~= arg
;
140 fwritef(stderr
,"running: ");
141 foreach(string arg
; exeargv
)
143 fwritef(stderr
,arg
," ");
151 retval
= spawnvp(std
.c
.process
._P_WAIT
, exepath
, exeargv
);
155 retval
= spawnapp(exepath
,exeargv
);
160 try { std
.file
.remove(exepath
); } catch {}
161 error("Couldn't compile or execute " ~ dfilepath
~ ".", retval
);
169 void error(string errmsg
, int errno
)
171 fwritefln(stderr
,myname
,": ",errmsg
);
177 fwritefln(stderr
,"Usage:");
178 fwritefln(stderr
," ",myname
," [D compiler arguments] [",myname
," arguments] progfile.d [program arguments]");
180 fwritefln(stderr
,myname
," arguments:");
181 fwritefln(stderr
," --help\t\tThis message");
182 fwritefln(stderr
," --force\t\tForce re-compilation of source code [default = do not force]");
183 fwritefln(stderr
," --verbose\t\tShow detailed info of operations [default = do not show]");
184 fwritefln(stderr
," --compiler=(dmd|gdmd)\tSpecify compiler [default = "~ .defcmp
~"]");
185 fwritefln(stderr
," --tmpdir=tmp_dir_path\tSpecify directory to store cached program and other temporaries [default = /tmp]");
187 fwritefln(stderr
,"Notes:");
188 fwritefln(stderr
," dmd or gdmd must be in the current user context $PATH");
189 fwritefln(stderr
," ",myname
," does not support execution of D source code via stdin");
190 fwritefln(stderr
," ",myname
," will only compile and execute files with a '.d' file extension");
194 bool compile(string tmpdir
, string compiler
, bool force
, string dfilepath
, string
[] cmpv
, ref string exepath
)
198 struct_stat dfilestat
; // D source code file status info.
199 int filrv
= stat(toStringz(dfilepath
),&dfilestat
);
201 string
[] pathcomps
= split(dfilepath
,fileSeparator
);
202 string exefile
= split(pathcomps
[$-1],".")[0];
204 string cmdline
= compiler
~ " -quiet";
205 foreach(string
str; cmpv
)
207 cmdline
~= " " ~ str;
209 // MD5 sum of compiler arguments
211 sum(digest
,cast(void[])cmdline
);
213 // directory for temp. files
217 if(tmpdir
[$-1] != fileSeparator
[0])
218 tmpdir
= tmpdir
~ fileSeparator
;
220 // exe filename format is basename-uid-filesysdev-inode-MD5
221 // append MD5 sum of the compiler arguments onto the file name to force recompile if they have changed
226 uid_str
= toString(getuid());
227 exepath
= tmpdir
~ exefile
~ "-" ~ uid_str
~ "-" ~ toString(dfilestat
.st_dev
) ~ "-" ~ toString(dfilestat
.st_ino
) ~ "-" ~ digestToString(digest
) ~ exeExtension
;
229 struct_stat exestat
; // temp. executable status info.
230 int exerv
= stat(toStringz(exepath
),&exestat
);
231 if(force ||
// force compilation
232 exerv ||
// stat returned an error (e.g.: no exefile)
233 dfilestat
.st_mtime
> exestat
.st_mtime ||
// source code file is newer than executable
234 progstat(.myname
).st_mtime
> exestat
.st_mtime ||
// this program is newer than executable
235 progstat(compiler
).st_mtime
> exestat
.st_mtime
) // compiler is newer than executable
237 cmdline
~= " " ~ dfilepath
~ " -of" ~ exepath
~ " -od" ~ tmpdir
;
240 fwritefln(stderr
,"running: ",cmdline
);
242 retval
= std
.process
.system(cmdline
); // compile ("system" is also in std.c.stdlib)
243 chmod(toStringz(exepath
),0700);
246 // remove object file
247 try { std
.file
.remove(tmpdir
~ exefile
~ objectExtension
); } catch {}
249 return cast(bool)(retval
== 0);
252 struct_stat
progstat(string program
)
254 struct_stat progstat
; // D source code file status info.
259 if(find(program
,fileSeparator
) >= 0)
260 prgrv
= stat(toStringz(program
), &progstat
);
263 // There's got to be a better way...
264 string
[] pathdirs
= split(toString(getenv("PATH")),pathSeparator
);
265 foreach(string dir
; pathdirs
)
267 prgrv
= stat(toStringz(dir
~ fileSeparator
~ program
), &progstat
);
282 extern(C
) char* strerror(int);
284 int spawnapp(string pathname
, string
[] argv
)
293 execv(pathname
,argv
);
300 pid_t wpid
= waitpid(pid
, &status
, 0);
303 retval
= exitstatus(status
);
306 else if(signaled(status
))
308 retval
= -termsig(status
);
311 else if(stopped(status
)) // ptrace support
322 error("Cannot spawn " ~ pathname
~ "; " ~ toString(strerror(retval
)) ~ " [errno " ~ toString(retval
) ~ "]", retval
);
326 bool stopped(int status
) { return cast(bool)((status
& 0xff) == 0x7f); }
327 bool signaled(int status
) { return cast(bool)((cast(char)((status
& 0x7f) + 1) >> 1) > 0); }
328 int termsig(int status
) { return status
& 0x7f; }
329 bool exited(int status
) { return cast(bool)((status
& 0x7f) == 0); }
330 int exitstatus(int status
) { return (status
& 0xff00) >> 8; }
338 BOOL
GetFileTime(HANDLE hFile
, LPFILETIME lpCreationTime
,
339 LPFILETIME lpLastAccessTime
, LPFILETIME lpLastWriteTime
);
340 BOOL
GetUserNameA(LPTSTR lpBuffer
, LPDWORD nSize
);
351 // fake stat function
352 int stat(char* name
, struct_stat
* st
)
355 HANDLE h
= CreateFileA(name
, FILE_GENERIC_READ
, 0, null, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, null);
356 if (h
!= INVALID_HANDLE_VALUE
)
358 FILETIME lastWriteTime
;
359 if (GetFileTime(h
, null, null, &lastWriteTime
))
361 st
.st_mtime
= lastWriteTime
.dwHighDateTime
;
363 st
.st_mtime |
= lastWriteTime
.dwLowDateTime
;
370 fwritefln(stderr
,"stat: ",toString(name
)," : ",retval
);
375 // fake getuid function
379 DWORD size
= buffer
.length
= 64;
380 if(GetUserNameA(buffer
.ptr
, &size
))
382 buffer
.length
= size
;
388 // fake chmod function