1 /**
2 	Generator for direct compiler builds.
3 	
4 	Copyright: © 2013-2013 rejectedsoftware e.K.
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Sönke Ludwig
7 */
8 module dub.generators.build;
9 
10 import dub.compilers.compiler;
11 import dub.generators.generator;
12 import dub.internal.std.process;
13 import dub.internal.utils;
14 import dub.internal.vibecompat.core.file;
15 import dub.internal.vibecompat.core.log;
16 import dub.internal.vibecompat.inet.path;
17 import dub.package_;
18 import dub.packagemanager;
19 import dub.project;
20 
21 import std.algorithm;
22 import std.array;
23 import std.conv;
24 import std.exception;
25 import std.file;
26 import std.string;
27 
28 
29 class BuildGenerator : ProjectGenerator {
30 	private {
31 		Project m_project;
32 		PackageManager m_pkgMgr;
33 	}
34 	
35 	this(Project app, PackageManager mgr)
36 	{
37 		m_project = app;
38 		m_pkgMgr = mgr;
39 	}
40 	
41 	void generateProject(GeneratorSettings settings)
42 	{
43 		auto cwd = Path(getcwd());
44 
45 		auto buildsettings = settings.buildSettings;
46 		m_project.addBuildSettings(buildsettings, settings.platform, settings.config);
47 		m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType);
48 
49 		auto generate_binary = !(buildsettings.options & BuildOptions.syntaxOnly);
50 		auto is_static_library = buildsettings.targetType == TargetType.staticLibrary || buildsettings.targetType == TargetType.library;
51 
52 		// make file paths relative to shrink the command line
53 		foreach(ref f; buildsettings.sourceFiles){
54 			auto fp = Path(f);
55 			if( fp.absolute ) fp = fp.relativeTo(Path(getcwd()));
56 			f = fp.toNativeString();
57 		}
58 
59 		// find the temp directory
60 		auto tmp = getTempDir();
61 
62 		if( settings.config.length ) logInfo("Building configuration \""~settings.config~"\", build type "~settings.buildType);
63 		else logInfo("Building default configuration, build type "~settings.buildType);
64 
65 		prepareGeneration(buildsettings);
66 
67 		// determine the absolute target path
68 		if( !Path(buildsettings.targetPath).absolute )
69 			buildsettings.targetPath = (m_project.mainPackage.path ~ Path(buildsettings.targetPath)).toNativeString();
70 
71 		// make all target/import paths relative
72 		string makeRelative(string path) { auto p = Path(path); if (p.absolute) p = p.relativeTo(cwd); return p.toNativeString(); }
73 		buildsettings.targetPath = makeRelative(buildsettings.targetPath);
74 		foreach (ref p; buildsettings.importPaths) p = makeRelative(p);
75 		foreach (ref p; buildsettings.stringImportPaths) p = makeRelative(p);
76 
77 		Path exe_file_path;
78 		if( generate_binary ){
79 			if( settings.run ){
80 				import std.random;
81 				auto rnd = to!string(uniform(uint.min, uint.max));
82 				buildsettings.targetPath = (tmp~"dub/"~rnd).toNativeString();
83 			}
84 			exe_file_path = Path(buildsettings.targetPath) ~ getTargetFileName(buildsettings, settings.platform);
85 		}
86 		logDiagnostic("Application output name is '%s'", exe_file_path.toNativeString());
87 
88 		finalizeGeneration(buildsettings, generate_binary);
89 
90 		if( buildsettings.preBuildCommands.length ){
91 			logInfo("Running pre-build commands...");
92 			runBuildCommands(buildsettings.preBuildCommands, buildsettings);
93 		}
94 
95 		// assure that we clean up after ourselves
96 		Path[] cleanup_files;
97 		scope (exit) {
98 			foreach (f; cleanup_files)
99 				if (existsFile(f))
100 					remove(f.toNativeString());
101 			if (generate_binary && settings.run)
102 				rmdirRecurse(buildsettings.targetPath);
103 		}
104 
105 		/*
106 			NOTE: for DMD experimental separate compile/link is used, but this is not yet implemented
107 			      on the other compilers. Later this should be integrated somehow in the build process
108 			      (either in the package.json, or using a command line flag)
109 		*/
110 		if (settings.platform.compilerBinary != "dmd" || !generate_binary || is_static_library) {
111 			// setup for command line
112 			if( generate_binary ) settings.compiler.setTarget(buildsettings, settings.platform);
113 			settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine);
114 
115 			// invoke the compiler
116 			logInfo("Running %s...", settings.platform.compilerBinary);
117 			if( settings.run ) cleanup_files ~= exe_file_path;
118 			settings.compiler.invoke(buildsettings, settings.platform);
119 		} else {
120 			// determine path for the temporary object file
121 			version(Windows) enum tempobjname = "temp.obj";
122 			else enum tempobjname = "temp.o";
123 			Path tempobj = Path(buildsettings.targetPath) ~ tempobjname;
124 
125 			// setup linker command line
126 			auto lbuildsettings = buildsettings;
127 			lbuildsettings.sourceFiles = lbuildsettings.sourceFiles.filter!(f => f.endsWith(".lib"))().array();
128 			settings.compiler.prepareBuildSettings(lbuildsettings, BuildSetting.commandLineSeparate|BuildSetting.sourceFiles);
129 
130 			// setup compiler command line
131 			buildsettings.libs = null;
132 			buildsettings.lflags = null;
133 			buildsettings.addDFlags("-c", "-of"~tempobj.toNativeString());
134 			buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => !f.endsWith(".lib"))().array();
135 			settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.commandLine);
136 
137 			logInfo("Compiling...");
138 			settings.compiler.invoke(buildsettings, settings.platform);
139 
140 			logInfo("Linking...");
141 			if( settings.run ) cleanup_files ~= exe_file_path;
142 			settings.compiler.invokeLinker(lbuildsettings, settings.platform, [tempobj.toNativeString()]);
143 		}
144 
145 		// run post-build commands
146 		if( buildsettings.postBuildCommands.length ){
147 			logInfo("Running post-build commands...");
148 			runBuildCommands(buildsettings.postBuildCommands, buildsettings);
149 		}
150 
151 		// copy files and run the executable
152 		if (generate_binary && settings.run) {
153 			if (buildsettings.targetType == TargetType.executable) {
154 				if (buildsettings.workingDirectory.length) {
155 					logDiagnostic("Switching to %s", (cwd ~ buildsettings.workingDirectory).toNativeString());
156 					chdir((cwd ~ buildsettings.workingDirectory).toNativeString());
157 				}
158 				scope(exit) chdir(cwd.toNativeString());
159 				logInfo("Running %s...", exe_file_path.toNativeString());
160 				auto prg_pid = spawnProcess(exe_file_path.toNativeString() ~ settings.runArgs);
161 				auto result = prg_pid.wait();
162 				enforce(result == 0, "Program exited with code "~to!string(result));
163 			} else logInfo("Target is a library. Skipping execution.");
164 		}
165 	}
166 }