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