1 /**
2 	Generator for project files
3 	
4 	Copyright: © 2012-2013 Matthias Dondorff
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff
7 */
8 module dub.generators.generator;
9 
10 import dub.compilers.compiler;
11 import dub.generators.build;
12 import dub.generators.visuald;
13 import dub.internal.vibecompat.core.file;
14 import dub.internal.vibecompat.core.log;
15 import dub.internal.vibecompat.inet.path;
16 import dub.package_;
17 import dub.packagemanager;
18 import dub.project;
19 
20 import std.array;
21 import std.exception;
22 import std.file;
23 import std..string;
24 
25 
26 /**
27 	Common interface for project generators/builders.
28 */
29 class ProjectGenerator
30 {
31 	struct TargetInfo {
32 		Package pack;
33 		Package[] packages;
34 		string config;
35 		BuildSettings buildSettings;
36 		string[] dependencies;
37 		string[] linkDependencies;
38 	}
39 
40 	protected {
41 		Project m_project;
42 	}
43 
44 	this(Project project)
45 	{
46 		m_project = project;
47 	}
48 
49 	void generate(GeneratorSettings settings)
50 	{
51 		if (!settings.config.length) settings.config = m_project.getDefaultConfiguration(settings.platform);
52 
53 		TargetInfo[string] targets;
54 		string[string] configs = m_project.getPackageConfigs(settings.platform, settings.config);
55 
56 		string[] mainfiles;
57 		collect(settings, m_project.mainPackage, targets, configs, mainfiles, null);
58 		downwardsInheritSettings(m_project.mainPackage.name, targets, targets[m_project.mainPackage.name].buildSettings);
59 		auto bs = &targets[m_project.mainPackage.name].buildSettings;
60 		if (bs.targetType == TargetType.executable) bs.addSourceFiles(mainfiles);
61 
62 		generateTargets(settings, targets);
63 	}
64 
65 	abstract void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets);
66 
67 	private BuildSettings collect(GeneratorSettings settings, Package pack, ref TargetInfo[string] targets, in string[string] configs, ref string[] main_files, string bin_pack)
68 	{
69 		if (auto pt = pack.name in targets) return pt.buildSettings;
70 
71 		// determine the actual target type
72 		auto shallowbs = pack.getBuildSettings(settings.platform, configs[pack.name]);
73 		TargetType tt = shallowbs.targetType;
74 		if (pack is m_project.mainPackage) {
75 			if (tt == TargetType.autodetect || tt == TargetType.library) tt = TargetType.staticLibrary;
76 		} else {
77 			if (tt == TargetType.autodetect || tt == TargetType.library) tt = settings.combined ? TargetType.sourceLibrary : TargetType.staticLibrary;
78 			else if (tt == TargetType.dynamicLibrary) {
79 				logWarn("Dynamic libraries are not yet supported as dependencies - building as static library.");
80 				tt = TargetType.staticLibrary;
81 			}
82 		}
83 		shallowbs.targetType = tt;
84 		bool generates_binary = tt != TargetType.sourceLibrary && tt != TargetType.none;
85 
86 		// start to build up the build settings
87 		BuildSettings buildsettings = settings.buildSettings.dup;
88 		processVars(buildsettings, pack.path.toNativeString(), shallowbs, true);
89 		buildsettings.addVersions("Have_" ~ stripDlangSpecialChars(pack.name));
90 
91 		// remove any mainSourceFile from library builds
92 		if (buildsettings.targetType != TargetType.executable && buildsettings.mainSourceFile.length) {
93 			buildsettings.sourceFiles = buildsettings.sourceFiles.filter!(f => f != buildsettings.mainSourceFile)().array;
94 			main_files ~= buildsettings.mainSourceFile;
95 		}
96 
97 		logDiagnostic("Generate target %s (%s %s %s)", pack.name, buildsettings.targetType, buildsettings.targetPath, buildsettings.targetName);
98 		if (generates_binary)
99 			targets[pack.name] = TargetInfo(pack, [pack], configs[pack.name], buildsettings, null);
100 
101 		foreach (depname, depspec; pack.dependencies) {
102 			if (!pack.hasDependency(depname, configs[pack.name])) continue;
103 			auto dep = m_project.getDependency(depname, depspec.optional);
104 			if (!dep) continue;
105 
106 			auto depbs = collect(settings, dep, targets, configs, main_files, generates_binary ? pack.name : bin_pack);
107 
108 			if (depbs.targetType != TargetType.sourceLibrary && depbs.targetType != TargetType.none) {
109 				// add a reference to the target binary and remove all source files in the dependency build settings
110 				depbs.sourceFiles = depbs.sourceFiles.filter!(f => f.isLinkerFile()).array;
111 				depbs.importFiles = null;
112 			}
113 
114 			buildsettings.add(depbs);
115 
116 			if (generates_binary) {
117 				auto pt = pack.name in targets;
118 				assert(pt !is null);
119 				if (auto pdt = depname in targets) {
120 					pt.dependencies ~= depname;
121 					pt.linkDependencies ~= depname;
122 					if (depbs.targetType == TargetType.staticLibrary)
123 						pt.linkDependencies = pt.linkDependencies.filter!(d => !pdt.linkDependencies.canFind(d)).array ~ pdt.linkDependencies;
124 				} else pt.packages ~= dep;
125 			} else targets[bin_pack].packages ~= dep;
126 		}
127 
128 		if (generates_binary) {
129 			// add build type settings and convert plain DFLAGS to build options
130 			m_project.addBuildTypeSettings(buildsettings, settings.platform, settings.buildType);
131 			settings.compiler.extractBuildOptions(buildsettings);
132 			enforceBuildRequirements(buildsettings);
133 			targets[pack.name].buildSettings = buildsettings.dup;
134 		}
135 
136 		return buildsettings;
137 	}
138 
139 	private void downwardsInheritSettings(string target, TargetInfo[string] targets, in BuildSettings root_settings)
140 	{
141 		auto ti = &targets[target];
142 		ti.buildSettings.addVersions(root_settings.versions);
143 		ti.buildSettings.addDebugVersions(root_settings.debugVersions);
144 		ti.buildSettings.addOptions(root_settings.options);
145 		ti.buildSettings.prependStringImportPaths(root_settings.stringImportPaths);
146 
147 		foreach (d; ti.dependencies)
148 			downwardsInheritSettings(d, targets, root_settings);
149 	}
150 }
151 
152 
153 struct GeneratorSettings {
154 	BuildPlatform platform;
155 	Compiler compiler;
156 	string config;
157 	string buildType;
158 	BuildSettings buildSettings;
159 
160 	bool combined; // compile all in one go instead of each dependency separately
161 
162 	// only used for generator "build"
163 	bool run, force, direct, clean, rdmd;
164 	string[] runArgs;
165 }
166 
167 
168 /**
169 	Creates a project generator of the given type for the specified project.
170 */
171 ProjectGenerator createProjectGenerator(string generator_type, Project app, PackageManager mgr)
172 {
173 	assert(app !is null && mgr !is null, "Project and package manager needed to create a generator.");
174 
175 	generator_type = generator_type.toLower();
176 	switch(generator_type) {
177 		default:
178 			throw new Exception("Unknown project generator: "~generator_type);
179 		case "build":
180 			logDebug("Creating build generator.");
181 			return new BuildGenerator(app, mgr);
182 		case "mono-d":
183 			throw new Exception("The Mono-D generator has been removed. Use Mono-D's built in DUB support instead.");
184 		case "visuald":
185 			logDebug("Creating VisualD generator.");
186 			return new VisualDGenerator(app, mgr);
187 	}
188 }
189 
190 
191 /**
192 	Runs pre-build commands and performs other required setup before project files are generated.
193 */
194 void prepareGeneration(in BuildSettings buildsettings)
195 {
196 	if( buildsettings.preGenerateCommands.length ){
197 		logInfo("Running pre-generate commands...");
198 		runBuildCommands(buildsettings.preGenerateCommands, buildsettings);
199 	}
200 }
201 
202 /**
203 	Runs post-build commands and copies required files to the binary directory.
204 */
205 void finalizeGeneration(in BuildSettings buildsettings, bool generate_binary)
206 {
207 	if (buildsettings.postGenerateCommands.length) {
208 		logInfo("Running post-generate commands...");
209 		runBuildCommands(buildsettings.postGenerateCommands, buildsettings);
210 	}
211 
212 	if (generate_binary) {
213 		if (!exists(buildsettings.targetPath))
214 			mkdirRecurse(buildsettings.targetPath);
215 		
216 		if (buildsettings.copyFiles.length) {
217 			logInfo("Copying files...");
218 			foreach (f; buildsettings.copyFiles) {
219 				auto src = Path(f);
220 				auto dst = Path(buildsettings.targetPath) ~ Path(f).head;
221 				logDiagnostic("  %s to %s", src.toNativeString(), dst.toNativeString());
222 				try {
223 					copyFile(src, dst, true);
224 				} catch logWarn("Failed to copy to %s", dst.toNativeString());
225 			}
226 		}
227 	}
228 }
229 
230 void runBuildCommands(in string[] commands, in BuildSettings build_settings)
231 {
232 	import std.process;
233 	import dub.internal.utils;
234 
235 	string[string] env = environment.toAA();
236 	// TODO: do more elaborate things here
237 	// TODO: escape/quote individual items appropriately
238 	env["DFLAGS"] = join(cast(string[])build_settings.dflags, " ");
239 	env["LFLAGS"] = join(cast(string[])build_settings.lflags," ");
240 	env["VERSIONS"] = join(cast(string[])build_settings.versions," ");
241 	env["LIBS"] = join(cast(string[])build_settings.libs," ");
242 	env["IMPORT_PATHS"] = join(cast(string[])build_settings.importPaths," ");
243 	env["STRING_IMPORT_PATHS"] = join(cast(string[])build_settings.stringImportPaths," ");
244 	runCommands(commands, env);
245 }