Delay auto-generating the main function until the end of the first semantic pass
[delight/core.git] / rdmd.d
blob1da7ded1a759d0ed0d56b3207e9d5d459a033e71
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.
4 //
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.
8 //
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.
13 // version 1.2
15 version (Windows)
17 import std.c.windows.windows;
19 string fileSeparator = "\\";
20 string pathSeparator = ";";
21 string objectExtension = ".obj";
22 string exeExtension = ".exe";
24 else version (linux)
26 import std.c.linux.linux;
28 string fileSeparator = "/";
29 string pathSeparator = ":";
30 string objectExtension = ".o";
31 string exeExtension = "";
33 else version (Unix)
35 import std.c.unix.unix;
37 string fileSeparator = "/";
38 string pathSeparator = ":";
39 string objectExtension = ".o";
40 string exeExtension = "";
42 else static assert(0);
44 version (DigitalMars)
45 version (linux)
46 version = Unix;
48 bool verbose = false;
50 import std.c.stdlib, std.file, std.md5, std.process, std.stdio, std.string;
52 version (Unix)
54 extern(C) ushort getuid(); // moved to top, because of mac linker issues
57 int main(string[] args)
59 int retval = -1;
60 bool havefile = false, force = false;
61 string[] cmpv, argv; // cmpv = compiler arguments, argv = program arguments
62 string exepath, dfilepath, compiler = "dmd", tmpdir = "/tmp";
64 version (GNU)
66 compiler = "gdmd";
68 version (Windows)
70 tmpdir = toString(getenv("TEMP"));
73 .myname = args[0];
74 .defcmp = compiler;
76 foreach(int i, string arg; args)
78 if(i == 0)
79 continue;
81 if(find(arg,".d") >= 0 || find(arg,".ds") >= 0)
83 havefile = true;
84 dfilepath = arg;
86 else
88 if(havefile == false)
90 bool skip = false;
91 if(arg == "--help")
92 usage;
93 else if(arg == "--force")
94 skip = force = true;
95 else if(arg == "--verbose")
96 skip = verbose = true;
97 else
99 const string cs = "--compiler=";
100 if(arg.length > cs.length && arg[0..cs.length] == cs)
102 compiler = split(arg,"=")[1];
103 skip = true;
105 const string td = "--tmpdir=";
106 if(arg.length > td.length && arg[0..td.length] == td)
108 tmpdir = split(arg,"=")[1];
109 skip = true;
113 if(!skip)
114 cmpv ~= arg;
116 else
117 argv ~= arg;
121 if(!havefile)
122 error("Couldn't find any D source code file to compile or execute.", retval);
124 if(compile(tmpdir,compiler,force,dfilepath,cmpv,exepath))
126 string[] exeargv;
127 version (Windows)
129 exeargv ~= "\"" ~ exepath ~ "\"";
130 foreach(string arg; argv) exeargv ~= "\"" ~ arg ~ "\"";
132 else
134 exeargv ~= exepath;
135 foreach(string arg; argv) exeargv ~= arg;
138 if(verbose)
140 fwritef(stderr,"running: ");
141 foreach(string arg; exeargv)
143 fwritef(stderr,arg," ");
145 fwritefln(stderr);
148 // execute
149 version (Windows)
151 retval = spawnvp(std.c.process._P_WAIT, exepath, exeargv);
153 else
155 retval = spawnapp(exepath,exeargv);
158 else
160 try { std.file.remove(exepath); } catch {}
161 error("Couldn't compile or execute " ~ dfilepath ~ ".", retval);
164 return retval;
167 string myname;
168 string defcmp;
169 void error(string errmsg, int errno)
171 fwritefln(stderr,myname,": ",errmsg);
172 exit(errno);
175 void usage()
177 fwritefln(stderr,"Usage:");
178 fwritefln(stderr," ",myname," [D compiler arguments] [",myname," arguments] progfile.d [program arguments]");
179 fwritefln(stderr);
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]");
186 fwritefln(stderr);
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");
191 exit(EXIT_SUCCESS);
194 bool compile(string tmpdir, string compiler, bool force, string dfilepath, string[] cmpv, ref string exepath)
196 int retval = 0;
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)
206 if(str != "")
207 cmdline ~= " " ~ str;
209 // MD5 sum of compiler arguments
210 ubyte[16] digest;
211 sum(digest,cast(void[])cmdline);
213 // directory for temp. files
214 if(!tmpdir.length)
215 tmpdir = "/tmp/";
216 else
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
222 string uid_str;
223 version(Windows)
224 uid_str = getuid();
225 else
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;
238 if(verbose)
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.
258 int prgrv;
259 if(find(program,fileSeparator) >= 0)
260 prgrv = stat(toStringz(program), &progstat);
261 else
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);
268 if (prgrv == 0)
270 break;
275 catch {}
277 return progstat;
280 version (Unix)
282 extern(C) char* strerror(int);
284 int spawnapp(string pathname, string[] argv)
286 int retval = 0;
287 pid_t pid = fork();
289 if(pid != -1)
291 if(pid == 0)
293 execv(pathname,argv);
294 goto Lerror;
297 while(1)
299 int status;
300 pid_t wpid = waitpid(pid, &status, 0);
301 if(exited(status))
303 retval = exitstatus(status);
304 break;
306 else if(signaled(status))
308 retval = -termsig(status);
309 break;
311 else if(stopped(status)) // ptrace support
312 continue;
313 else
314 goto Lerror;
317 return retval;
320 Lerror:
321 retval = getErrno;
322 error("Cannot spawn " ~ pathname ~ "; " ~ toString(strerror(retval)) ~ " [errno " ~ toString(retval) ~ "]", retval);
323 return 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; }
332 } // version (Unix)
334 version (Windows)
336 extern (Windows)
338 BOOL GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime,
339 LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime);
340 BOOL GetUserNameA(LPTSTR lpBuffer, LPDWORD nSize);
343 // fake struct stat
344 struct struct_stat
346 ulong st_dev;
347 uint st_ino;
348 ulong st_mtime;
351 // fake stat function
352 int stat(char* name, struct_stat* st)
354 int retval = -1;
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;
362 st.st_mtime <<= 32;
363 st.st_mtime |= lastWriteTime.dwLowDateTime;
364 retval = 0;
366 CloseHandle(h);
368 if(verbose)
370 fwritefln(stderr,"stat: ",toString(name)," : ",retval);
372 return retval;
375 // fake getuid function
376 char[] getuid()
378 char[] buffer;
379 DWORD size = buffer.length = 64;
380 if(GetUserNameA(buffer.ptr, &size))
382 buffer.length = size;
383 return buffer;
385 return "";
388 // fake chmod function
389 void chmod(...)