1 ///	
2 // Written in the D programming language.
3 /**
4 This is a build tool,compile *.d to exe or lib,and help to build dfl2 gui (or other you like).
5 now default DC is dmd ,default platform is windows.
6 
7 If your DC is dmd, dco can start only 'dco.ini' config file. 
8 
9 Compiler dco.d :dmd dco.d -release,and dco ↓,
10 
11 Usage:
12   Config some info to 'dco.ini' file,';' or '#' means you can do as it.Then copy dco.ini to your PATH: such as dmd's config file:sc.ini.
13   And dco.exe can auto copy itsself to EnvPath,that also is  dmd.exe 's path: dmd2\window\bin.
14   After that,you can run the 'dco.exe'  anywhere.
15   If not found 'dco.ini',run: dco -ini,please.
16   
17 For example:
18     to get the debug version( -release to get another)
19 
20 	build some *.d to lib or exe 			 : dco ↓
21 	build some *.d to lib or exe for 64 bit	 : dco -m64 ↓
22 	build  one app.d in many *.d    		 : dco app or  dco app.d
23 	build for libs 	such as dfl,dgui         : dco  -lib
24 	build for app.d use dfl2				 : dco  -gui
25 	build app.d use dfl2 for console		 : dco  -con
26 	build lib and copy to libs				 : dco -lib -copy
27 	build by custom	and copy to libs         : dco -arg -addlib -lib -copy
28 
29     if your exe's file works on console,you should add '-con' or '-console'. 
30     
31 Copyright: Copyright FrankLIKE 2014-.
32 
33 License:   $(LGPL-3.0).
34 
35 Authors:   FrankLIKE
36 
37 Source: $(dco.d)
38  
39 Created Time:2014-10-27
40 Modify Time:2014-10-31~2014-12-29
41 */
42 module dco;
43 /// dco 
44 import	std.stdio;
45 import	std.datetime;
46 import	std.process; 
47 import	std..string;
48 import	std.file;
49 import	std.path;
50 import	std.exception;
51 import  std.json;
52 import std.exception;
53 
54 string strVersion ="v0.0.8";
55 string	strAddArgs,strAddArgsdfl = " -de -w -property ";
56 string	strDebug,strDebugDefault=" -debug";
57 string	strTargetLflags,strConsole=" -L-su:console:4 ",strWindows = " -L-Subsystem:Windows ",strWindows64 = " -L-Subsystem:Windows -L-ENTRY:mainCRTStartup ";
58 string	strTargetLib,SpecialLib = "dfl",strWinLibs=" ole32.lib oleAut32.lib gdi32.lib Comctl32.lib Comdlg32.lib advapi32.lib uuid.lib ws2_32.lib kernel32.lib ",strWinLibs64 =" user32.lib "; 
59 string	strDFile;
60 string	strAddLib;
61 string	strOtherArgs;
62 string	strImportDefault = " -I$(DMDInstallDir)windows/import ";
63 string	strTargetPath,strTargetFileName,strTargetTypeSwitch,targetTypeDefault = "lib";
64 string	strDCEnv,strDCEnvFile;
65 SysTime sourceLastUpdateTime,targetTime;
66 string	compileType; 
67 
68 bool	bUseSpecialLib =false,bDebug =true,bBuildSpecialLib =false;
69 bool	bCopy =false ,bDisplayBuildStr=false,bDisplayCopyInfo =true;
70 bool	bForce = false;
71 bool 	bAssignTarget =false;
72 
73 //ini
74 string configFile ="dco.ini";
75 string[string] configKeyValue;
76 
77 //ini args
78 string strPackageName,strArgs,strTargetName,strTargetType ="exe",strDC,strDCStandardEnvBin ="dmd2\\windows\\bin",strLibs ,strImport,strLflags,strDflags;
79  
80 
81 void main(string[] args)
82 {
83 	if(!findDCEnv()) return;
84 	// readInJson();
85 	if(!checkArgs(args))
86 	{
87 		if(!findFiles())
88 		{
89 			ShowUsage();
90 			return;
91 		}
92 		
93 	}
94 	if(args.length ==1)
95 	{
96 		if(!CheckBinFolderAndCopy()) return;
97     }
98 	if(args.length == 2 && (toLower(args[1]) == "-h" || toLower(args[1]) == "-help"))
99 	{
100 		ShowUsage();
101 		return;
102 	}
103 	if(strPackageName =="")
104 	{
105 		strPackageName = strTargetName;
106 	}
107  
108 	buildExe(args);
109 }
110 
111 bool findDCEnv()
112 {
113 	 if(!readConfig(configFile)) return false;
114 	string strNoDC = "Not found '"~strDC~"' in your computer,please setup it.",strTemp,strTempFile;
115 	string strDCExe = "\\" ~ strDC.stripRight() ~ ".exe";
116 	string strFireWall = " Maybe FirWall stop checking the " ~ strDCExe ~ ",please stop it.";
117 	
118 	auto len = strDCStandardEnvBin.length;
119 
120 	auto path = environment["PATH"];
121 	string[] strDCs = path.split(";");
122 	foreach(s;strDCs)
123 	{
124 		ptrdiff_t i = path.indexOf(strDCStandardEnvBin);
125 		if(i != -1)
126 		{
127 			if(exists(s ~ strDCExe))
128 			{ 
129 				strDCEnv = s;
130 				strDCEnvFile =  s ~ strDCExe;
131 					
132 				strTempFile = s ~ "\\dco.exe";
133 				if(exists(strTempFile))break;
134 			}
135 		}
136 	}
137 
138 	if(strDCEnvFile =="")
139 	{
140 		writeln(strNoDC);
141 		return false;
142 	}
143 	else
144 	{
145 		//writeln(strDC ~ " is " ~ strDCEnvFile);
146 		return true;
147 	}
148 }
149 
150 bool readConfig(string configFile)
151 { 
152 	try
153 	{ 
154 		string strConfigPath = thisExePath();
155 		strConfigPath = strConfigPath[0..strConfigPath.lastIndexOf("\\")].idup;
156 		strConfigPath ~= "\\" ~ configFile;
157 
158 		if(!enforce(exists(strConfigPath),"'FireWall' stop to access the 'dco.ini',please stop it."))
159 		{
160 			writeln("dco not found dco.ini, it will help you to  create a init dco.ini file ,but you should input something in it.");
161 			initNewConfigFile();
162 			return false;
163 		}  
164 	   
165 		auto file = File(strConfigPath); 
166 		scope(failure) file.close();
167 		auto range = file.byLine();
168 		foreach (line; range)
169 		{
170 			if (!line.init && line[0] != '#' && line[0] != ';' && line.indexOf("=") != -1)
171 			{ 
172 				ptrdiff_t i =line.indexOf("=");
173 				configKeyValue[line.strip()[0..i].idup] = line.strip()[i+1..$].idup;
174 			}
175 		}
176 	 
177 		 file.close();
178   
179 		strDC = configKeyValue.get("DC","dmd"); 
180 		 
181 		strDCStandardEnvBin = configKeyValue.get("DCStandardEnvBin",strDCStandardEnvBin); 
182 		SpecialLib = configKeyValue.get("SpecialLib",SpecialLib);  
183 		strImport = configKeyValue.get("importPath","");
184 		strLflags = configKeyValue.get("lflags",strConsole); 
185 		strDflags = configKeyValue.get("dfalgs",""); 
186 		strLibs = configKeyValue.get("libs",""); 
187 		return true;
188   }
189   catch(Exception e) 
190   {
191 		writeln(" Read ini file err,you should input something in ini file.",e.msg);
192 		return false;
193   }
194 }
195 
196 bool checkArgs(string[] args)
197 {
198 	string c;
199 	size_t p;
200 	bool bDFile =false;
201 	foreach(int i,arg;args)
202 	{
203 		if(i == 0) continue;
204 		c = toLower(arg);
205 		p = c.indexOf('-');
206 		if(p == -1 || c.indexOf(".d") != -1)
207 		{
208 
209 			strTargetName = c;
210  			strDFile ~= " ";
211 			strDFile ~= c;
212 			bDFile = true;
213 		}
214 		else
215 		{
216 			c = c[p+1 .. $];
217 		}
218 
219 		if(i ==0) continue;
220  
221 		if(c == "force")
222 		{
223 			bForce = true;
224 		}
225 		else if(c.indexOf("of") != -1)
226 		{
227 			bAssignTarget = true;
228 			strTargetName = c[(c.indexOf("of")+1)..$];
229 		}
230 		else if(c == strPackageName || c == strPackageName ~ "lib")
231 		{
232 			bAssignTarget = true;
233 			bBuildSpecialLib = true;
234 			strTargetTypeSwitch = " -" ~ targetTypeDefault;
235 			strTargetName = c ~ ".lib";
236 		}
237 		else if (c == "ini")
238 		{
239 			initNewConfigFile();
240 			return false;
241 		}
242 	}
243 	return bDFile;
244 }
245 
246 bool CheckBinFolderAndCopy() 
247 {
248 	if(checkIsUpToDate())
249 	{
250 		writeln(strTargetName ~" file is up to date.");
251 		return false;
252 	}
253 	return true;
254 }
255 
256 bool checkIsUpToDate()
257 {
258 	 getTargetInfo();
259      if(exists(strTargetFileName))
260      {
261 		targetTime = getTargetTime(strTargetFileName);
262  
263         if(strTargetFileName.indexOf("dco.exe") != -1)
264         {
265 			if(!checkIsUpToDate(strDCEnvFile ,targetTime))
266 			{
267 				auto files = dirEntries(".","dco.{exe,ini}",SpanMode.shallow);
268 				foreach(d;files)
269 				{
270 					string strcopy ="copy " ~ d ~" " ~ strDCEnv;
271 					writeln(strcopy);
272 					auto pid = enforce(spawnShell(strcopy.dup()),"spawnShell(strcopy.dup()) is err!");
273 					if (wait(pid) != 0)
274 					{
275 						writeln("copy failed.");
276 					}
277 				}
278 			 //copy(strTargetFileName,strDCEnvFile);
279 			}
280  	    }
281  		 
282 		bool bUpToDate = (targetTime >= sourceLastUpdateTime);
283 		 
284 		if(!bUpToDate || bForce)
285 		{
286 			removeExe(strTargetFileName);
287 		}
288  		return bUpToDate;
289     }
290  
291     return false;
292 }
293 
294 SysTime getTargetTime(string strPathFile)
295 {
296 	 return DirEntry(strPathFile).timeLastModified;
297 }
298 
299 void removeExe(string strPathExe)
300 {
301     if(exists(strPathExe))
302 	{
303 		auto pid = enforce(spawnShell("del " ~ strPathExe.dup()),"del " ~ strPathExe.dup() ~ " Err");
304 		if (wait(pid) != 0)
305         {
306 			writeln(strPathExe ~ ", remove  failed!");
307 			return;
308 		}
309 		else
310 		{
311 			writeln(strPathExe ~ ", remove  ok!");
312 		}
313 	}
314 }
315 
316 bool checkIsUpToDate(string strPathFile,SysTime targettime)
317 {
318 	 if(!exists(strPathFile)) return false;
319     auto testFile = DirEntry(strPathFile);
320     auto createTime = testFile.timeLastModified;
321    
322     return (targettime <= createTime);
323 }
324 
325 void buildExe(string[] args)
326 {
327 	string c;
328 	size_t p;
329 	foreach(int i,arg;args)
330 	{
331 		if(i ==0) continue;
332 		c = toLower(arg);
333 		p = c.indexOf('-');
334 		if(p != -1)
335 		{
336 			c = c[p+1 .. c.length];
337 
338 		switch(c)
339     	{
340 			case "h","help":
341 				ShowUsage();
342 				break;
343 			case "gui":
344 				strTargetLflags = strWindows;
345 				bUseSpecialLib = true;
346     			strAddArgs = strAddArgsdfl;
347 				break;
348 			case "use":
349 				bUseSpecialLib = true;
350     			strAddArgs = strAddArgsdfl;
351 				break;
352 			case "win","windows","winexe":
353 				strTargetLflags = strWindows;
354 				break;
355     		case "debug":
356 				bDebug = true;
357 				break;
358 			case "release":
359 				bDebug = false;
360 				strDebug = " -" ~ c.idup;
361 				break;
362 
363     		case "console","con","exe":
364     			strTargetLflags = strConsole;
365     			break;
366 			case "all":
367 				bUseSpecialLib = false;
368 				strAddLib = strLibs;
369     			strAddArgs = strAddArgsdfl;
370     			strImport = strImportDefault;
371     			strTargetLflags = strConsole;
372     			break;
373 			case "addlib":
374     			strAddLib = strLibs~" ";
375     			strImport = strImportDefault;
376     			strTargetLflags = strConsole;
377     			break;
378 			case "arg":
379     			strAddArgs = strAddArgsdfl;
380     			break;
381 			case "lib":
382 				strTargetTypeSwitch = " -" ~ targetTypeDefault;
383 				break;
384 			case "dfl","dfllib":
385 				 bBuildSpecialLib = true;
386 				strTargetTypeSwitch = " -" ~ targetTypeDefault;
387 				break;
388 			case "copy":
389 				bCopy = true;
390 				break;
391 			case "force":
392 				bForce = true;
393 				break;
394 			case "init":
395 				
396 				break;
397     		default:
398     		    if(c == "m64" || c == "m32mscoff")
399     		    { 
400 					compileType =c[1..$];
401 				}
402 				strOtherArgs ~= " ";
403 				strOtherArgs ~= arg;
404 				break;
405     		}
406     	}
407 	}
408 
409    strTargetLib = bDebug ? SpecialLib ~ "_debug" ~ compileType ~ ".lib" : SpecialLib ~ compileType ~ ".lib" ;
410  
411    if(bBuildSpecialLib)
412    {
413 	   strOtherArgs ~= " -of" ~ strTargetLib;
414 	   strAddLib = strLibs;
415 	  strTargetFileName = getcwd() ~ "\\" ~ strTargetLib;
416    }
417    else
418    {
419 		strTargetFileName = getcwd() ~ "\\" ~ strTargetName;
420    }
421  
422 	if(bUseSpecialLib)
423 	{
424 		if(SpecialLib == "dfl")
425 		{
426 			strLibs =strWinLibs;
427 		}
428 		strAddLib = strLibs ~" " ~ strTargetLib ;
429 	}
430 	else
431 	{
432 		strAddLib = strLibs;
433 	}
434   
435     if(strDflags !="")
436     {
437     	strOtherArgs ~= " ";
438     	strOtherArgs ~= strDflags;
439     }
440     if(strTargetLflags == "" && strLflags !="")
441 	{
442 		strTargetLflags = strLflags;
443 	}
444 	if(compileType == "64" && bUseSpecialLib)
445 	{
446 		strTargetLflags = strWindows64;
447 	}
448 	if(strTargetLflags == "" && strLflags =="")
449 	{
450 		if(bUseSpecialLib) 
451 			strTargetLflags = strWindows;
452 		else
453 			strTargetLflags = strConsole;
454 	}
455  
456 	if(compileType == "64")
457 	{
458 		if(strip(strTargetLflags) !=strip(strConsole))
459 		{
460 			if(strAddLib.indexOf(strip(strWinLibs64)) == -1)
461 			{
462 				strAddLib ~= strWinLibs64;
463 			}
464 		}
465 		else //console x64 not set
466 		{
467 			strTargetLflags= "";
468 		}
469    }  
470 	buildExe();
471 }
472 
473 void buildExe()
474 {
475 	if(bForce)
476 	{
477 		removeExe(strTargetFileName);
478 	}
479 	strDC ~= " ";
480 	strDC ~= strTargetTypeSwitch;
481 	string strCommon = strOtherArgs ~" " ~ strImportDefault ~ strImport ~ " " ~ strAddLib ~ strTargetLflags ~ strDFile ~ strDebug;
482     string buildstr = strDC ~ strAddArgsdfl ~ strCommon ~ "\r\n";
483 	buildstr = bUseSpecialLib ? buildstr : strDC ~ strCommon;
484 	//if(bDisplayBuildStr)
485 	{
486 		writeln(buildstr);
487 	}
488  
489 	StopWatch sw;
490 	sw.start();
491 	auto pid =  enforce(spawnShell(buildstr.dup()),"build function is error! ");
492 
493 	if (wait(pid) != 0)
494 	{
495 		writeln("Compilation failed:\n", pid);
496 	}
497 	else
498 	{
499 		sw.stop();
500    
501 		writeln("\nCompile time :" , sw.peek().msecs/1000.0,"secs");
502 
503 		if(bCopy)
504 		{
505 			copyFile();
506 		}
507 	}
508 	writeln("End.");
509 }
510 
511 void copyFile()
512 {
513 	string strcopy;
514 	 if(!exists(strTargetFileName)) 
515 	 {
516 	 	writeln(strTargetFileName," is not exists,stop copy.");
517 	   return;
518 	}
519  
520 	if(strTargetFileName.indexOf("exe") != -1)
521 	{
522 		//copy(strTargetFileName,strDCEnv); //
523 		strcopy = "copy " ~ strTargetFileName~" " ~ strDCEnv;
524 	}
525 	else
526 	{ 
527 		string strDCLibPath = strDCEnv[0..(strDCEnv.length - "bin".length)].idup ~ "lib" ~ compileType; 
528 		//copy(strDCEnv,strDCLibPath);
529 		strcopy = "copy " ~ strTargetFileName ~ " " ~ strDCLibPath;
530 	}
531 	if(bDisplayCopyInfo)
532 	{
533 		writeln(strcopy);
534 	}
535 		 
536 	auto pid =  enforce(spawnShell(strcopy.dup()),"copyFile() error");
537 	if (wait(pid) != 0)
538 	{
539 		writeln("Copy failed.");
540 	}
541 }
542 
543 bool findFiles()
544 { 
545 	int i = 0;
546 	bool bPackage = false; 
547 	auto packages = dirEntries(".","{package.d,all.d}",SpanMode.depth);
548 	foreach(p; packages){i++;}
549 	bPackage = (i > 0);
550 	 
551 	auto dFiles = dirEntries(".","*.{d,di}",SpanMode.depth);
552 	int icount =0;
553     SysTime fileTime;
554     DirEntry rootDE ;
555 
556 	foreach(d; dFiles)
557 	{	 
558 	    if(!bAssignTarget)
559 	    {
560 			if(icount == 0)
561 			{
562 				strTargetName = d.name[(d.name.lastIndexOf("\\")+1) .. d.name.lastIndexOf(".")];
563 				strTargetName ~= "." ~ strTargetType; 
564 			}
565 		}
566 		if(icount == 0 )
567 		{
568 			ReadDFile(d,bPackage);
569 		}
570 		
571 		if(d.toLower().indexOf("ignore") != -1) continue;
572  
573 		strDFile ~= " ";
574 		strDFile ~= d.name[2 ..$].idup;
575 		 
576 		//sourceLastUpdateTime 
577 		rootDE = DirEntry(d);
578         if(rootDE.timeLastModified > fileTime)
579         {
580         	fileTime = rootDE.timeLastModified;
581         } 
582         icount++;
583 	}
584     sourceLastUpdateTime = fileTime;
585     
586 	strDFile = strDFile.stripRight().idup;
587 	
588 	if(icount <= 0)  
589 	{
590 		writeln("Not found any *.d files in current folder.If there is a 'source' or 'src' folder,dco will find the '*.d' from there.");
591 		 return false;
592 	}
593 	bCopy = (strDFile.indexOf("dco.d") != -1) ? true : false;
594 	return true;
595 }
596 
597 void getTargetInfo()
598 { 
599 	string root_path = getcwd();
600     string strPath;
601 	auto dFiles = dirEntries(root_path,strTargetName ~ ".{lib,exe}",SpanMode.shallow);
602 	int i =0;
603 	foreach(d;dFiles)
604 	{
605 		i++;
606 		strTargetFileName =d;
607 	   strTargetType = d.name[d.name.lastIndexOf(".")+1..$];
608 	   break;
609 
610 	}
611 	if(i ==0)
612 	{
613 		if(strTargetName.indexOf("." ~ strTargetType) == -1)
614 		{
615 			strTargetName = strTargetName ~ "." ~ strTargetType;
616 		}
617 		strTargetFileName = root_path ~ "\\" ~ strTargetName;
618 	}
619 		 
620 	return;
621 }
622  
623 void ShowUsage()
624 {
625 	writeln("
626 dco build tool " ~ strVersion ~ "
627 written by FrankLIKE.
628 Usage:
629   dco [<switches...>] <files...>
630 		  
631 for example:     dco  
632 	     or: dco app.d 
633 		 
634 build for dfl2:	 dco  
635 	     or: dco -gui
636 	     or: dco *.d -gui
637 build for other: dco -lib
638 	     or: dco *.d -lib
639 	     or: dco *.d -release
640 	     or: dco *.d -arg -addlib
641 
642 Switches:
643     -h	       Print help(usage: -h,or -help).
644     -copy      Copy new exe or lib to 'windows/bin' or 'lib' Folder. 
645     -release   Build files's Release version(Default version is 'debug').
646     -gui       Make a Windows GUI exe without a console(For DFL or Dgui).
647     -use       Use the Sepetail Lib to create exe with console.
648     -win       Make a Windows GUI exe without a console
649 		(For any other: the same to -winexe,-windows).
650     -lib       Build lib files.
651     -ini       Create the ini file for config. 
652     -all       Build files by args,libs(Default no dfl_debug.lib) in Console.
653     -arg       Build files by args(-de -w -property -X).
654     -addlib    Build files by add libs(user32.lib ole32.lib oleAut32.lib gdi32.lib 
655 		Comctl32.lib Comdlg32.lib advapi32.lib uuid.lib ws2_32.lib).
656     -m64       Generate 64bit lib or exe.
657     -m32mscoff Generate x86 ms coff lib or exe,and please set some info in sc.ini. 
658 		
659 IgnoreFiles:
660 	      If you have some files to ignore,please put them in Folder 'ignoreFiles'.
661     ");
662 } 
663 
664 void ReadDFile(string dFile,bool bPackage)
665 { 
666 	 auto file = File(dFile); 
667 	 scope(exit)  file.close();
668 	 auto range = file.byLine();
669 	 int icount = 0;
670     foreach (line; range)
671     {
672         if (!line.init && line.indexOf("import") != -1)
673         { 
674           
675           bBuildSpecialLib = bPackage;
676          
677           
678         	if(line.indexOf("dfl") != -1)
679         	{
680         		SpecialLib = "dfl";
681         		
682         		bUseSpecialLib = !bPackage;
683         		
684         		 if(bUseSpecialLib) 
685 					strTargetLflags = strWindows;
686         		 else
687 					strTargetLflags = strConsole;
688 				break;
689 			}
690 			else if(line.indexOf("dgui") != -1)
691 			{
692 				strArgs = strAddArgsdfl = " -g -de -w -property -X ";
693 				SpecialLib = "dgui";
694 				break;
695 			}
696         }
697         else if(line.indexOf("WinMain") != -1)
698         {
699 			strTargetLflags = strWindows;
700 			break;
701         }
702         icount++;
703         if(icount >100) break;
704     }
705 }
706 
707 void initNewConfigFile()
708 {
709 	auto ini = File("dco.ini","w"); 
710 	scope(failure) ini.close();
711 	ini.writeln(";DC=dmd");
712 	ini.writeln("DC=");
713 	ini.writeln(";DCStandardEnvBin=" ~ strDCStandardEnvBin);
714 	ini.writeln("DCStandardEnvBin=");
715 	ini.writeln(";SpecialLib=" ~ SpecialLib);
716 	ini.writeln("SpecialLib=");
717 	ini.writeln(";importPath=" ~ strImportDefault);
718 	ini.writeln("importPath=");
719 	ini.writeln(";lflags=" ~ strConsole);
720 	ini.writeln(";lflags=" ~ strWindows);
721 	ini.writeln("lflags=");
722 	ini.writeln(";dflags=");
723 	ini.writeln(";libs=");
724 	ini.close();
725  
726 	auto pid = spawnProcess(["notepad.exe","dco.ini"]);
727     auto dmd = tryWait(pid);
728 	if (dmd.terminated)
729 	{
730 		if (dmd.status == 0) writeln("open dco.ini succeeded!");
731 		else writeln("open dco.ini failed");
732 	}
733 	else writeln("Still opening...");
734 	
735 }
736 
737 void readInJson()
738 {
739 	if(!exists("dub.json") & !exists("package.json") ) return;
740 	/*
741 	strPackageName = configKeyValue.get("name","");
742 	strArgs = configKeyValue.get("args",strAddArgs);
743 	strLibs = configKeyValue.get("libs","");
744 	strTargetName = configKeyValue.get("targetName","");
745 	strTargetType = configKeyValue.get("targetType",strTargetType); 
746 	*/
747 }
748